Commit 2362c6cf68 for wordpress.org

commit 2362c6cf6878e1833338df89cc8ed60e2810608d
Author: Mamaduka <mamaduka@git.wordpress.org>
Date:   Tue Oct 27 08:31:57 2026 +0000

    Administration: Map Dashicons to replacement icons from the icon library manifest.

    Introduce the `WP_Icons_Registry::get_admin_icon_name_for_dashicon()` method, which returns the `core-admin` icon that replaces a given Dashicon, based on the `dashicons` property of the icon library manifest. It returns `null` if the Dashicon has no mapping or its replacement icon is not registered.

    Use it in `_wp_menu_output()` in place of the hard-coded list of core menu item Dashicons. Menu items from plugins and custom post types that use a mapped Dashicon now get the SVG icon too. A Dashicon with no mapping still uses the Dashicons font.

    Follow-up to [64583].

    Props fushar, mamaduka, peterwilsoncc.
    See #65089.
    Built from https://develop.svn.wordpress.org/trunk@64798


    git-svn-id: http://core.svn.wordpress.org/trunk@63948 1a063a9b-81f0-0310-95a4-ce76da25c4cd

diff --git a/wp-admin/menu-header.php b/wp-admin/menu-header.php
index fe4cd28bfc..60671727eb 100644
--- a/wp-admin/menu-header.php
+++ b/wp-admin/menu-header.php
@@ -73,26 +73,6 @@ get_admin_page_parent();
 function _wp_menu_output( $menu, $submenu, $submenu_as_parent = true ) {
 	global $self, $parent_file, $submenu_file, $plugin_page, $typenow;

-	/*
-	 * Dashicons back-compat: the `$menu` global keeps the Dashicons values of the core
-	 * menu items. Those Dashicons are only mapped to their replacement icons here, when
-	 * the menu is rendered.
-	 */
-	$dashicon_replacements = array(
-		'dashicons-dashboard'        => 'core-admin/dashboard',
-		'dashicons-admin-post'       => 'core-admin/pin',
-		'dashicons-admin-media'      => 'core-admin/media',
-		'dashicons-admin-links'      => 'core-admin/link',
-		'dashicons-admin-page'       => 'core-admin/page',
-		'dashicons-admin-comments'   => 'core-admin/comment',
-		'dashicons-admin-appearance' => 'core-admin/brush',
-		'dashicons-admin-plugins'    => 'core-admin/plugins',
-		'dashicons-admin-users'      => 'core-admin/people',
-		'dashicons-admin-tools'      => 'core-admin/tool',
-		'dashicons-admin-settings'   => 'core-admin/settings',
-		'dashicons-admin-multisite'  => 'core-admin/sites',
-	);
-
 	$first = true;
 	// 0 = menu_title, 1 = capability, 2 = menu_slug, 3 = page_title, 4 = classes, 5 = hookname, 6 = icon_url.
 	foreach ( $menu as $key => $item ) {
@@ -145,13 +125,17 @@ function _wp_menu_output( $menu, $submenu, $submenu_as_parent = true ) {
 		 * If the string 'none' (previously 'div') is passed instead of a URL, don't output
 		 * the default menu image so an icon can be added to div.wp-menu-image as background
 		 * with CSS. Dashicons and base64-encoded data:image/svg_xml URIs are also handled
-		 * as special cases. Dashicons that have a replacement, and namespaced icon names, are
-		 * rendered as inline SVG, or leave the menu image empty if the icon is not found.
-		 * Any other value is treated as an image URL.
+		 * as special cases. Namespaced icon names are rendered as inline SVG, or leave the
+		 * menu image empty if the icon is not found. Any other value is treated as an image URL.
+		 *
+		 * Dashicons back-compat: the `$menu` global keeps the Dashicons of the menu items,
+		 * including the core ones. Dashicons with a registered replacement icon are only
+		 * mapped to it here, when the menu is rendered, and other Dashicons are rendered
+		 * with the Dashicons font.
 		 */
 		if ( ! empty( $item[6] ) ) {
 			$img       = '<img src="' . esc_url( $item[6] ) . '" alt="" />';
-			$icon_name = $dashicon_replacements[ $item[6] ] ?? $item[6];
+			$icon_name = WP_Icons_Registry::get_instance()->get_admin_icon_name_for_dashicon( $item[6] ) ?? $item[6];

 			if ( 'none' === $item[6] || 'div' === $item[6] ) {
 				$img = '<br />';
diff --git a/wp-includes/class-wp-icons-registry.php b/wp-includes/class-wp-icons-registry.php
index 6186a0b489..2a109fb641 100644
--- a/wp-includes/class-wp-icons-registry.php
+++ b/wp-includes/class-wp-icons-registry.php
@@ -28,6 +28,16 @@ class WP_Icons_Registry {
 	 */
 	protected static $instance = null;

+	/**
+	 * Names of the icons that replace Dashicons, keyed by Dashicon class name.
+	 *
+	 * Built from the icon library manifest on first use.
+	 *
+	 * @since 7.2.0
+	 * @var array<string, string>|null
+	 */
+	protected $dashicon_replacements = null;
+
 	/**
 	 * Constructor.
 	 *
@@ -483,6 +493,49 @@ class WP_Icons_Registry {
 		return isset( $this->registered_icons[ $icon_name ] );
 	}

+	/**
+	 * Returns the name of the icon that replaces a Dashicon in the admin interface.
+	 *
+	 * The replacements come from the `dashicons` property of the `core-admin`
+	 * icons in the icon library manifest.
+	 *
+	 * @since 7.2.0
+	 *
+	 * @param string $dashicon Dashicon class name, such as `dashicons-admin-post`.
+	 * @return string|null Namespaced name of the replacement icon, such as `core-admin/pin`,
+	 *                     or null if the Dashicon has no replacement or the replacement
+	 *                     is not registered.
+	 */
+	public function get_admin_icon_name_for_dashicon( $dashicon ) {
+		if ( ! is_string( $dashicon ) ) {
+			return null;
+		}
+
+		if ( null === $this->dashicon_replacements ) {
+			$this->dashicon_replacements = array();
+
+			$manifest_path = ABSPATH . WPINC . '/assets/icon-library-manifest.php';
+			$manifest      = is_readable( $manifest_path ) ? include $manifest_path : array();
+
+			foreach ( is_array( $manifest ) ? $manifest : array() as $icon_name => $icon_data ) {
+				if (
+					empty( $icon_data['dashicons'] ) ||
+					! in_array( 'core-admin', $icon_data['collections'] ?? array(), true )
+				) {
+					continue;
+				}
+
+				foreach ( $icon_data['dashicons'] as $replaced_dashicon ) {
+					$this->dashicon_replacements[ $replaced_dashicon ] = 'core-admin/' . $icon_name;
+				}
+			}
+		}
+
+		$icon_name = $this->dashicon_replacements[ $dashicon ] ?? null;
+
+		return null !== $icon_name && $this->is_registered( $icon_name ) ? $icon_name : null;
+	}
+
 	/**
 	 * Utility method to retrieve the main instance of the class.
 	 *
diff --git a/wp-includes/icons.php b/wp-includes/icons.php
index 65b7166a67..a4e6aa9ec2 100644
--- a/wp-includes/icons.php
+++ b/wp-includes/icons.php
@@ -134,7 +134,8 @@ function _wp_register_default_icons() {
 	 *     label: string,
 	 *     filePath: non-empty-string,
 	 *     collections: non-empty-list<lowercase-string&non-falsy-string>,
-	 *     keywords?: list<string>
+	 *     keywords?: list<string>,
+	 *     dashicons?: non-empty-list<non-falsy-string>
 	 * }> $collection
 	 */
 	$collection = include $manifest_path;
diff --git a/wp-includes/version.php b/wp-includes/version.php
index efa4692942..13d4528856 100644
--- a/wp-includes/version.php
+++ b/wp-includes/version.php
@@ -16,7 +16,7 @@
  *
  * @global string $wp_version
  */
-$wp_version = '7.2-beta1-64797';
+$wp_version = '7.2-beta1-64798';

 /**
  * Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.