Files
hub-insurance/wp-content/plugins/admin-site-enhancements/classes/class-admin-menu-svg-icon-mask.php
T
2026-07-02 15:54:39 -06:00

236 lines
6.4 KiB
PHP

<?php
/**
* Shared helper for rendering SVG admin menu icons with correct colors on first paint.
*
* WordPress renders `menu_icon` SVG data URIs as <img> initially, and then core's
* `svg-painter` inlines/recolors them, causing a visible "black → scheme color" flash.
* This helper pre-empts that by forcing WordPress to use the `.wp-menu-image:before`
* pseudo-element (via a Dashicon placeholder) and then applying the SVG as a CSS mask.
*
* @since 7.6.0
*/
namespace ASENHA\Classes;
class Admin_Menu_Svg_Icon_Mask {
/**
* Registered SVG data URIs keyed by post type key.
*
* @since 7.6.0
* @var array<string,string>
*/
private static $post_type_svgs = array();
/**
* Registered SVG data URIs keyed by top-level menu slug (from add_menu_page()).
*
* @since 7.6.0
* @var array<string,string>
*/
private static $toplevel_menu_slug_svgs = array();
/**
* Registered SVG data URIs keyed by a substring to match against `href`.
*
* This is useful for menu slugs that are full URLs/queries (e.g. `post.php?post=123&action=edit`).
*
* @since 7.6.0
* @var array<string,string>
*/
private static $href_contains_svgs = array();
/**
* Check whether a string is an SVG data URI that WordPress would render as an <img>.
*
* @since 7.6.0
*
* @param mixed $value Potential menu icon.
* @return bool
*/
public static function is_svg_data_uri( $value ) {
return is_string( $value ) && ( 0 === strpos( $value, 'data:image/svg+xml' ) );
}
/**
* Register an SVG icon for a post type's admin menu entry.
*
* @since 7.6.0
*
* @param string $post_type Post type key.
* @param string $svg_data_uri SVG data URI.
* @return void
*/
public static function register_post_type_svg_icon( $post_type, $svg_data_uri ) {
$post_type = sanitize_key( (string) $post_type );
$svg_data_uri = trim( (string) $svg_data_uri );
if ( '' === $post_type || '' === $svg_data_uri ) {
return;
}
if ( ! self::is_svg_data_uri( $svg_data_uri ) ) {
return;
}
self::$post_type_svgs[ $post_type ] = $svg_data_uri;
}
/**
* Register an SVG icon for a top-level menu entry created by add_menu_page().
*
* @since 7.6.0
*
* @param string $menu_slug Menu slug passed to add_menu_page().
* @param string $svg_data_uri SVG data URI.
* @return void
*/
public static function register_toplevel_menu_slug_svg_icon( $menu_slug, $svg_data_uri ) {
$menu_slug = (string) $menu_slug;
$svg_data_uri = trim( (string) $svg_data_uri );
if ( '' === $menu_slug || '' === $svg_data_uri ) {
return;
}
if ( ! self::is_svg_data_uri( $svg_data_uri ) ) {
return;
}
self::$toplevel_menu_slug_svgs[ $menu_slug ] = $svg_data_uri;
}
/**
* Register an SVG icon for a menu entry by matching a substring within the anchor href.
*
* @since 7.6.0
*
* @param string $needle Substring expected in href.
* @param string $svg_data_uri SVG data URI.
* @return void
*/
public static function register_menu_href_contains_svg_icon( $needle, $svg_data_uri ) {
$needle = trim( (string) $needle );
$svg_data_uri = trim( (string) $svg_data_uri );
if ( '' === $needle || '' === $svg_data_uri ) {
return;
}
if ( ! self::is_svg_data_uri( $svg_data_uri ) ) {
return;
}
self::$href_contains_svgs[ $needle ] = $svg_data_uri;
}
/**
* Enqueue inline CSS masks for all registered menu SVG icons.
*
* @since 7.6.0
*
* @return void
*/
public static function enqueue_mask_css() {
if ( empty( self::$post_type_svgs ) && empty( self::$toplevel_menu_slug_svgs ) && empty( self::$href_contains_svgs ) ) {
return;
}
$css = '';
foreach ( self::$post_type_svgs as $post_type => $svg_data_uri ) {
$post_type = sanitize_key( (string) $post_type );
$svg_data_uri = trim( (string) $svg_data_uri );
if ( '' === $post_type || '' === $svg_data_uri || ! self::is_svg_data_uri( $svg_data_uri ) ) {
continue;
}
$selector = '#adminmenu .menu-icon-' . $post_type . ' .wp-menu-image:before';
$css .= self::build_mask_rule( $selector, $svg_data_uri );
}
foreach ( self::$toplevel_menu_slug_svgs as $menu_slug => $svg_data_uri ) {
$menu_slug = (string) $menu_slug;
$svg_data_uri = trim( (string) $svg_data_uri );
if ( '' === $menu_slug || '' === $svg_data_uri || ! self::is_svg_data_uri( $svg_data_uri ) ) {
continue;
}
$selectors = array(
'#toplevel_page_' . sanitize_title( $menu_slug ) . ' .wp-menu-image:before',
'#adminmenu a[href*="' . self::escape_css_attr_value( $menu_slug ) . '"] .wp-menu-image:before',
);
$css .= self::build_mask_rule( implode( ',' . PHP_EOL, $selectors ), $svg_data_uri );
}
foreach ( self::$href_contains_svgs as $needle => $svg_data_uri ) {
$needle = trim( (string) $needle );
$svg_data_uri = trim( (string) $svg_data_uri );
if ( '' === $needle || '' === $svg_data_uri || ! self::is_svg_data_uri( $svg_data_uri ) ) {
continue;
}
$selector = '#adminmenu a[href*="' . self::escape_css_attr_value( $needle ) . '"] .wp-menu-image:before';
$css .= self::build_mask_rule( $selector, $svg_data_uri );
}
if ( '' === $css ) {
return;
}
wp_add_inline_style( 'asenha-wp-admin', $css );
}
/**
* Build a CSS rule that masks a WP admin menu icon pseudo-element.
*
* @since 7.6.0
*
* @param string $selector CSS selector(s).
* @param string $svg_data_uri SVG data URI.
* @return string
*/
private static function build_mask_rule( $selector, $svg_data_uri ) {
$selector = trim( (string) $selector );
$svg_data_uri = trim( (string) $svg_data_uri );
if ( '' === $selector || '' === $svg_data_uri ) {
return '';
}
return $selector . '{' . PHP_EOL .
"\tcontent:\"\";" . PHP_EOL .
"\tbackground-color:currentColor;" . PHP_EOL .
"\t-webkit-mask-image:url(\"" . $svg_data_uri . "\");" . PHP_EOL .
"\tmask-image:url(\"" . $svg_data_uri . "\");" . PHP_EOL .
"\t-webkit-mask-repeat:no-repeat;" . PHP_EOL .
"\tmask-repeat:no-repeat;" . PHP_EOL .
"\t-webkit-mask-position:center;" . PHP_EOL .
"\tmask-position:center;" . PHP_EOL .
"\t-webkit-mask-size:20px 20px;" . PHP_EOL .
"\tmask-size:20px 20px;" . PHP_EOL .
'}' . PHP_EOL;
}
/**
* Escape a value used inside a double-quoted CSS attribute selector.
*
* @since 7.6.0
*
* @param string $value Raw value.
* @return string
*/
private static function escape_css_attr_value( $value ) {
return str_replace(
array( '\\', '"' ),
array( '\\\\', '\"' ),
(string) $value
);
}
}