Commit bb56ae22a6 for wordpress.org

commit bb56ae22a634316c5605c2fcc55ee76ee8666847
Author: wildworks <wildworks@git.wordpress.org>
Date:   Wed Oct 7 06:46:00 2026 +0000

    Icons: Move `public` to collections and add `core-admin` collection.

    Visibility is now controlled per icon collection instead of per icon. Icons in a non-public collection are not exposed through the REST API or selectable in the Icon block, but remain available to server-side rendering.

    Adds a non-public `core-admin` collection for the icons used by the admin interface, so that a plugin unregistering the `core` collection to hide the default icons from the Icon block no longer removes the icons rendered in the dashboard.

    Developed in: https://github.com/WordPress/wordpress-develop/pull/13625

    Follow-up to [63625].

    Props mcsf, peterwilsoncc, wildworks.
    Fixes #66114.

    Built from https://develop.svn.wordpress.org/trunk@64224


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

diff --git a/wp-includes/class-wp-icon-collections-registry.php b/wp-includes/class-wp-icon-collections-registry.php
index 0c59011977..dcfe842285 100644
--- a/wp-includes/class-wp-icon-collections-registry.php
+++ b/wp-includes/class-wp-icon-collections-registry.php
@@ -37,6 +37,7 @@ class WP_Icon_Collections_Registry {
 	 * Registers an icon collection.
 	 *
 	 * @since 7.1.0
+	 * @since 7.2.0 Added the `public` property.
 	 *
 	 * @param string $collection_slug       Icon collection slug.
 	 * @param array  $collection_properties {
@@ -44,6 +45,10 @@ class WP_Icon_Collections_Registry {
 	 *
 	 *     @type string $label       Required. A human-readable label for the icon collection.
 	 *     @type string $description Optional. A human-readable description for the icon collection.
+	 *     @type bool   $public      Optional. Whether the collection and its icons are exposed through
+	 *                               the REST API, and therefore selectable in the editor's icon picker.
+	 *                               Icons in non-public collections stay available to server-side code
+	 *                               via {@see wp_get_icon()}. Default true.
 	 * }
 	 * @return bool True if the collection was registered successfully, false otherwise.
 	 */
@@ -84,7 +89,7 @@ class WP_Icon_Collections_Registry {
 			return false;
 		}

-		$allowed_keys = array_fill_keys( array( 'label', 'description' ), 1 );
+		$allowed_keys = array_fill_keys( array( 'label', 'description', 'public' ), 1 );
 		foreach ( array_keys( $collection_properties ) as $key ) {
 			if ( ! array_key_exists( $key, $allowed_keys ) ) {
 				_doing_it_wrong(
@@ -118,8 +123,18 @@ class WP_Icon_Collections_Registry {
 			return false;
 		}

+		if ( array_key_exists( 'public', $collection_properties ) && ! is_bool( $collection_properties['public'] ) ) {
+			_doing_it_wrong(
+				__METHOD__,
+				__( 'Icon collection public property must be a boolean.' ),
+				'7.2.0'
+			);
+			return false;
+		}
+
 		$defaults = array(
 			'description' => '',
+			'public'      => true,
 		);

 		$collection = array_merge(
diff --git a/wp-includes/class-wp-icons-registry.php b/wp-includes/class-wp-icons-registry.php
index 745e369e7c..361506a67e 100644
--- a/wp-includes/class-wp-icons-registry.php
+++ b/wp-includes/class-wp-icons-registry.php
@@ -46,7 +46,7 @@ class WP_Icons_Registry {
 	 *
 	 * @since 7.0.0
 	 * @since 7.1.0 The icon name must be namespaced in the form "collection/icon-name".
-	 * @since 7.2.0 Added the `public` and `keywords` properties.
+	 * @since 7.2.0 Added the `keywords` property.
 	 *
 	 * @param string $icon_name       Namespaced icon name in the form "collection/icon-name"
 	 *                                (e.g. "core/arrow-left").
@@ -58,10 +58,6 @@ class WP_Icons_Registry {
 	 *                               If not provided, the content will be retrieved from the `file_path` if set.
 	 *                               If both `content` and `file_path` are not set, the icon will not be registered.
 	 *     @type string   $file_path Optional. The full path to the file containing the icon content.
-	 *     @type bool     $public    Optional. Whether the icon is exposed through the REST API, and
-	 *                               therefore selectable in the editor's icon picker. Non-public icons
-	 *                               stay available to server-side code via {@see wp_get_icon()}.
-	 *                               Default true.
 	 *     @type string[] $keywords  Optional. Additional search terms for the icon, matched by
 	 *                               `get_registered_icons()` alongside the name and label.
 	 * }
@@ -108,7 +104,7 @@ class WP_Icons_Registry {
 			return false;
 		}

-		$allowed_keys = array_fill_keys( array( 'label', 'content', 'file_path', 'public', 'keywords' ), 1 );
+		$allowed_keys = array_fill_keys( array( 'label', 'content', 'file_path', 'keywords' ), 1 );
 		foreach ( array_keys( $icon_properties ) as $key ) {
 			if ( ! array_key_exists( $key, $allowed_keys ) ) {
 				_doing_it_wrong(
@@ -146,15 +142,6 @@ class WP_Icons_Registry {
 			return false;
 		}

-		if ( isset( $icon_properties['public'] ) && ! is_bool( $icon_properties['public'] ) ) {
-			_doing_it_wrong(
-				__METHOD__,
-				__( 'Icon public property must be a boolean.' ),
-				'7.2.0'
-			);
-			return false;
-		}
-
 		if ( array_key_exists( 'keywords', $icon_properties ) ) {
 			if ( ! is_array( $icon_properties['keywords'] ) ) {
 				_doing_it_wrong(
diff --git a/wp-includes/icons.php b/wp-includes/icons.php
index e6916aaed1..05588c3cf4 100644
--- a/wp-includes/icons.php
+++ b/wp-includes/icons.php
@@ -11,6 +11,7 @@
  * Registers a new icon collection.
  *
  * @since 7.1.0
+ * @since 7.2.0 Added the `public` property.
  *
  * @param string $slug Icon collection slug.
  * @param array  $args {
@@ -18,6 +19,10 @@
  *
  *     @type string $label       Required. A human-readable label for the icon collection.
  *     @type string $description Optional. A human-readable description for the icon collection.
+ *     @type bool   $public      Optional. Whether the collection and its icons are exposed through
+ *                               the REST API, and therefore selectable in the editor's icon picker.
+ *                               Icons in non-public collections stay available to server-side code
+ *                               via {@see wp_get_icon()}. Default true.
  * }
  * @return bool True if the icon collection was registered successfully, else false.
  */
@@ -41,13 +46,13 @@ function wp_unregister_icon_collection( $slug ) {
  * Registers a new icon.
  *
  * @since 7.1.0
- * @since 7.2.0 Added the `public` and `keywords` properties.
+ * @since 7.2.0 Added the `keywords` property.
  *
  * @param string $icon_name Namespaced icon name in the form "collection/icon-name"
- *                          (e.g. "my-plugin/arrow-left"). The "core" collection is
- *                          reserved for WordPress core icons; third-party code should
- *                          register icons under its own collection rather than the
- *                          "core" collection.
+ *                          (e.g. "my-plugin/arrow-left"). The "core" and "core-admin"
+ *                          collections are reserved for WordPress core icons; third-party
+ *                          code should register icons under its own collection rather than
+ *                          a reserved one.
  * @param array  $args      {
  *     List of properties for the icon.
  *
@@ -56,10 +61,6 @@ function wp_unregister_icon_collection( $slug ) {
  *                               If not provided, the content will be retrieved from the `file_path` if set.
  *                               If both `content` and `file_path` are not set, the icon will not be registered.
  *     @type string   $file_path Optional. The full path to the file containing the icon content.
- *     @type bool     $public    Optional. Whether the icon is exposed through the REST API, and
- *                               therefore selectable in the editor's icon picker. Non-public icons
- *                               stay available to server-side code via {@see wp_get_icon()}.
- *                               Default true.
  *     @type string[] $keywords  Optional. Additional search terms for the icon, matched by
  *                               `get_registered_icons()` alongside the name and label.
  * }
@@ -96,10 +97,18 @@ function _wp_register_default_icon_collections() {
 			'description' => __( 'Default icon collection.' ),
 		)
 	);
+	wp_register_icon_collection(
+		'core-admin',
+		array(
+			'label'       => __( 'WordPress Admin' ),
+			'description' => __( 'Icon collection used by the WordPress admin interface.' ),
+			'public'      => false,
+		)
+	);
 }

 /**
- * Registers the default core icons from the manifest.
+ * Registers the default core and core-admin icons from the manifest.
  *
  * @since 7.1.0
  * @access private
@@ -139,20 +148,27 @@ function _wp_register_default_icons() {
 			return;
 		}

+		if ( empty( $icon_data['collections'] ) || ! is_array( $icon_data['collections'] ) ) {
+			_doing_it_wrong(
+				__FUNCTION__,
+				__( 'Core icon collection manifest must provide a non-empty "collections" array for each icon.' ),
+				'7.2.0'
+			);
+			return;
+		}
+
 		$icon_args = array(
 			'label'     => $icon_data['label'],
 			'file_path' => $icons_directory . $icon_data['filePath'],
 		);

-		if ( isset( $icon_data['public'] ) ) {
-			$icon_args['public'] = $icon_data['public'];
-		}
-
 		if ( isset( $icon_data['keywords'] ) ) {
 			$icon_args['keywords'] = $icon_data['keywords'];
 		}

-		wp_register_icon( 'core/' . $icon_name, $icon_args );
+		foreach ( $icon_data['collections'] as $collection_slug ) {
+			wp_register_icon( $collection_slug . '/' . $icon_name, $icon_args );
+		}
 	}
 }

diff --git a/wp-includes/rest-api/endpoints/class-wp-rest-icon-collections-controller.php b/wp-includes/rest-api/endpoints/class-wp-rest-icon-collections-controller.php
index 9e8a935b94..ab6c83f2ea 100644
--- a/wp-includes/rest-api/endpoints/class-wp-rest-icon-collections-controller.php
+++ b/wp-includes/rest-api/endpoints/class-wp-rest-icon-collections-controller.php
@@ -120,6 +120,7 @@ class WP_REST_Icon_Collections_Controller extends WP_REST_Controller {
 	 * Retrieves all icon collections.
 	 *
 	 * @since 7.1.0
+	 * @since 7.2.0 Non-public collections are omitted.
 	 *
 	 * @param WP_REST_Request $request Full details about the request.
 	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
@@ -128,6 +129,9 @@ class WP_REST_Icon_Collections_Controller extends WP_REST_Controller {
 		$response    = array();
 		$collections = WP_Icon_Collections_Registry::get_instance()->get_all_registered();
 		foreach ( $collections as $collection ) {
+			if ( ! $collection['public'] ) {
+				continue;
+			}
 			$prepared_collection = $this->prepare_item_for_response( $collection, $request );
 			$response[]          = $this->prepare_response_for_collection( $prepared_collection );
 		}
@@ -156,6 +160,7 @@ class WP_REST_Icon_Collections_Controller extends WP_REST_Controller {
 	 * Retrieves a specific icon collection from the registry.
 	 *
 	 * @since 7.1.0
+	 * @since 7.2.0 Non-public collections are reported as not found.
 	 *
 	 * @param string $slug Icon collection slug.
 	 * @return array|WP_Error Icon collection data on success, or WP_Error object on failure.
@@ -164,7 +169,7 @@ class WP_REST_Icon_Collections_Controller extends WP_REST_Controller {
 		$registry   = WP_Icon_Collections_Registry::get_instance();
 		$collection = $registry->get_registered( $slug );

-		if ( null === $collection ) {
+		if ( null === $collection || ! $collection['public'] ) {
 			return new WP_Error(
 				'rest_icon_collection_not_found',
 				sprintf(
diff --git a/wp-includes/rest-api/endpoints/class-wp-rest-icons-controller.php b/wp-includes/rest-api/endpoints/class-wp-rest-icons-controller.php
index 610cc58230..afcf80f4b2 100644
--- a/wp-includes/rest-api/endpoints/class-wp-rest-icons-controller.php
+++ b/wp-includes/rest-api/endpoints/class-wp-rest-icons-controller.php
@@ -143,15 +143,17 @@ class WP_REST_Icons_Controller extends WP_REST_Controller {
 	 *
 	 * @since 7.0.0
 	 * @since 7.1.0 Supports filtering by collection.
-	 * @since 7.2.0 Icons registered as non-public are omitted.
+	 * @since 7.2.0 Icons belonging to non-public collections are omitted.
 	 *
 	 * @param WP_REST_Request $request Full details about the request.
 	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
 	 */
 	public function get_items( $request ) {
-		$collection = $request->get_param( 'collection' );
+		$collection            = $request->get_param( 'collection' );
+		$collections_registry  = WP_Icon_Collections_Registry::get_instance();
+		$registered_collection = null !== $collection ? $collections_registry->get_registered( $collection ) : null;

-		if ( null !== $collection && ! WP_Icon_Collections_Registry::get_instance()->is_registered( $collection ) ) {
+		if ( null !== $collection && ( null === $registered_collection || ! $registered_collection['public'] ) ) {
 			return new WP_Error(
 				'rest_icon_collection_not_found',
 				sprintf(
@@ -168,10 +170,11 @@ class WP_REST_Icons_Controller extends WP_REST_Controller {
 		$icons    = WP_Icons_Registry::get_instance()->get_registered_icons( $search );

 		foreach ( $icons as $icon ) {
-			if ( false === ( $icon['public'] ?? true ) ) {
+			if ( null !== $collection && ( ! isset( $icon['collection'] ) || $icon['collection'] !== $collection ) ) {
 				continue;
 			}
-			if ( null !== $collection && ( ! isset( $icon['collection'] ) || $icon['collection'] !== $collection ) ) {
+			$icon_collection = isset( $icon['collection'] ) ? $collections_registry->get_registered( $icon['collection'] ) : null;
+			if ( null === $icon_collection || ! $icon_collection['public'] ) {
 				continue;
 			}
 			$prepared_icon = $this->prepare_item_for_response( $icon, $request );
@@ -202,7 +205,7 @@ class WP_REST_Icons_Controller extends WP_REST_Controller {
 	 * Retrieves a specific icon from the registry.
 	 *
 	 * @since 7.0.0
-	 * @since 7.2.0 Icons registered as non-public are reported as not found.
+	 * @since 7.2.0 Icons belonging to non-public collections are reported as not found.
 	 *
 	 * @param string $name Icon name.
 	 * @return array|WP_Error Icon data on success, or WP_Error object on failure.
@@ -211,7 +214,11 @@ class WP_REST_Icons_Controller extends WP_REST_Controller {
 		$registry = WP_Icons_Registry::get_instance();
 		$icon     = $registry->get_registered_icon( $name );

-		if ( null === $icon || false === ( $icon['public'] ?? true ) ) {
+		$collection = null !== $icon && isset( $icon['collection'] )
+			? WP_Icon_Collections_Registry::get_instance()->get_registered( $icon['collection'] )
+			: null;
+
+		if ( null === $icon || null === $collection || ! $collection['public'] ) {
 			return new WP_Error(
 				'rest_icon_not_found',
 				sprintf(
diff --git a/wp-includes/version.php b/wp-includes/version.php
index 9ca6b49545..fec9d394b2 100644
--- a/wp-includes/version.php
+++ b/wp-includes/version.php
@@ -16,7 +16,7 @@
  *
  * @global string $wp_version
  */
-$wp_version = '7.2-alpha-64223';
+$wp_version = '7.2-alpha-64224';

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