Skip to content

Commit c092f31

Browse files
committed
Icons: Add a non-public state to the icon public property.
Icons can now be registered with `'public' => false`, which keeps them in the registry and reachable from server-side code via `wp_get_icon()`, while hiding them from the icons REST API and therefore from the editor's icon picker. This mirrors the tri-state `public` property in the Gutenberg icons manifest: - omitted: the icon stays in the JS library and is not shipped to core - `true`: shipped to core and exposed through the REST API - `false`: shipped to core and registered, but not exposed through the REST API `WP_Icons_Registry::register()` accepts and validates the new property, `_wp_register_default_icons()` forwards it when the core manifest carries it, and `WP_REST_Icons_Controller` omits non-public icons from `get_items()` and reports them as not found from `get_icon()`. Without this, a synced `icon-library-manifest.php` carrying `'public' => false` would fail to register the flagged icons at all, since the registry rejects a registration containing any unknown property.
1 parent 0f36f52 commit c092f31

5 files changed

Lines changed: 135 additions & 8 deletions

File tree

src/wp-includes/class-wp-icons-registry.php

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ protected function __construct() {}
4646
*
4747
* @since 7.0.0
4848
* @since 7.1.0 The icon name must be namespaced in the form "collection/icon-name".
49+
* @since 7.2.0 Added the `public` property.
4950
*
5051
* @param string $icon_name Namespaced icon name in the form "collection/icon-name"
5152
* (e.g. "core/arrow-left").
@@ -57,6 +58,10 @@ protected function __construct() {}
5758
* If not provided, the content will be retrieved from the `file_path` if set.
5859
* If both `content` and `file_path` are not set, the icon will not be registered.
5960
* @type string $file_path Optional. The full path to the file containing the icon content.
61+
* @type bool $public Optional. Whether the icon is exposed through the REST API, and
62+
* therefore selectable in the editor's icon picker. Non-public icons
63+
* stay available to server-side code via {@see wp_get_icon()}.
64+
* Default true.
6065
* }
6166
* @return bool True if the icon was registered with success and false otherwise.
6267
*/
@@ -101,7 +106,7 @@ public function register( $icon_name, $icon_properties ) {
101106
return false;
102107
}
103108

104-
$allowed_keys = array_fill_keys( array( 'label', 'content', 'file_path' ), 1 );
109+
$allowed_keys = array_fill_keys( array( 'label', 'content', 'file_path', 'public' ), 1 );
105110
foreach ( array_keys( $icon_properties ) as $key ) {
106111
if ( ! array_key_exists( $key, $allowed_keys ) ) {
107112
_doing_it_wrong(
@@ -139,6 +144,15 @@ public function register( $icon_name, $icon_properties ) {
139144
return false;
140145
}
141146

147+
if ( isset( $icon_properties['public'] ) && ! is_bool( $icon_properties['public'] ) ) {
148+
_doing_it_wrong(
149+
__METHOD__,
150+
__( 'Icon public property must be a boolean.' ),
151+
'7.2.0'
152+
);
153+
return false;
154+
}
155+
142156
if (
143157
( ! isset( $icon_properties['content'] ) && ! isset( $icon_properties['file_path'] ) ) ||
144158
( isset( $icon_properties['content'] ) && isset( $icon_properties['file_path'] ) )

src/wp-includes/icons.php

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ function wp_unregister_icon_collection( $slug ) {
4141
* Registers a new icon.
4242
*
4343
* @since 7.1.0
44+
* @since 7.2.0 Added the `public` property.
4445
*
4546
* @param string $icon_name Namespaced icon name in the form "collection/icon-name"
4647
* (e.g. "my-plugin/arrow-left"). The "core" collection is
@@ -55,6 +56,10 @@ function wp_unregister_icon_collection( $slug ) {
5556
* If not provided, the content will be retrieved from the `file_path` if set.
5657
* If both `content` and `file_path` are not set, the icon will not be registered.
5758
* @type string $file_path Optional. The full path to the file containing the icon content.
59+
* @type bool $public Optional. Whether the icon is exposed through the REST API, and
60+
* therefore selectable in the editor's icon picker. Non-public icons
61+
* stay available to server-side code via {@see wp_get_icon()}.
62+
* Default true.
5863
* }
5964
* @return bool True if the icon was registered successfully, else false.
6065
*/
@@ -132,13 +137,21 @@ function _wp_register_default_icons() {
132137
return;
133138
}
134139

135-
wp_register_icon(
136-
'core/' . $icon_name,
137-
array(
138-
'label' => $icon_data['label'],
139-
'file_path' => $icons_directory . $icon_data['filePath'],
140-
)
140+
$icon_args = array(
141+
'label' => $icon_data['label'],
142+
'file_path' => $icons_directory . $icon_data['filePath'],
141143
);
144+
145+
/*
146+
* The manifest only carries `public` for non-public icons, and an absent
147+
* property already means public. Forwarding it unconditionally would send
148+
* the key to registries that predate it, which reject unknown properties.
149+
*/
150+
if ( isset( $icon_data['public'] ) ) {
151+
$icon_args['public'] = $icon_data['public'];
152+
}
153+
154+
wp_register_icon( 'core/' . $icon_name, $icon_args );
142155
}
143156
}
144157

src/wp-includes/rest-api/endpoints/class-wp-rest-icons-controller.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ public function get_item_permissions_check( $request ) {
143143
*
144144
* @since 7.0.0
145145
* @since 7.1.0 Supports filtering by collection.
146+
* @since 7.2.0 Icons registered as non-public are omitted.
146147
*
147148
* @param WP_REST_Request $request Full details about the request.
148149
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
@@ -167,6 +168,9 @@ public function get_items( $request ) {
167168
$icons = WP_Icons_Registry::get_instance()->get_registered_icons( $search );
168169

169170
foreach ( $icons as $icon ) {
171+
if ( false === ( $icon['public'] ?? true ) ) {
172+
continue;
173+
}
170174
if ( null !== $collection && ( ! isset( $icon['collection'] ) || $icon['collection'] !== $collection ) ) {
171175
continue;
172176
}
@@ -198,6 +202,7 @@ public function get_item( $request ) {
198202
* Retrieves a specific icon from the registry.
199203
*
200204
* @since 7.0.0
205+
* @since 7.2.0 Icons registered as non-public are reported as not found.
201206
*
202207
* @param string $name Icon name.
203208
* @return array|WP_Error Icon data on success, or WP_Error object on failure.
@@ -206,7 +211,7 @@ public function get_icon( $name ) {
206211
$registry = WP_Icons_Registry::get_instance();
207212
$icon = $registry->get_registered_icon( $name );
208213

209-
if ( null === $icon ) {
214+
if ( null === $icon || false === ( $icon['public'] ?? true ) ) {
210215
return new WP_Error(
211216
'rest_icon_not_found',
212217
sprintf(

tests/phpunit/tests/icons/wpIconsRegistry.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -453,4 +453,27 @@ public function test_get_content_returns_null_for_invalid_file( $contents, $exte
453453

454454
$this->assertNull( $icon['content'] );
455455
}
456+
457+
/**
458+
* Should reject a `public` property that is not a boolean.
459+
*
460+
* @ticket TODO
461+
*
462+
* @covers ::register
463+
*
464+
* @expectedIncorrectUsage WP_Icons_Registry::register
465+
*/
466+
public function test_register_rejects_non_boolean_public_property() {
467+
$result = $this->registry->register(
468+
'test-collection/invalid-visibility',
469+
array(
470+
'label' => 'Icon',
471+
'content' => '<svg></svg>',
472+
'public' => 'yes',
473+
)
474+
);
475+
476+
$this->assertFalse( $result );
477+
$this->assertFalse( $this->registry->is_registered( 'test-collection/invalid-visibility' ) );
478+
}
456479
}

tests/phpunit/tests/icons/wpRestIconsController.php

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -537,4 +537,76 @@ public function test_get_item_requires_authentication() {
537537

538538
$this->assertErrorResponse( 'rest_cannot_view', $response, 401 );
539539
}
540+
541+
/**
542+
* Test that icons registered as non-public are omitted from the collection.
543+
*
544+
* @ticket TODO
545+
*
546+
* @covers ::get_items
547+
*/
548+
public function test_get_items_omits_non_public_icons() {
549+
wp_register_icon_collection( 'rest-visibility-list', array( 'label' => 'REST Visibility' ) );
550+
wp_register_icon(
551+
'rest-visibility-list/visible',
552+
array(
553+
'label' => 'Visible',
554+
'content' => '<svg><path d="M0 0"/></svg>',
555+
)
556+
);
557+
wp_register_icon(
558+
'rest-visibility-list/hidden',
559+
array(
560+
'label' => 'Hidden',
561+
'content' => '<svg><path d="M1 1"/></svg>',
562+
'public' => false,
563+
)
564+
);
565+
566+
wp_set_current_user( self::$editor_id );
567+
568+
$request = new WP_REST_Request( 'GET', '/wp/v2/icons' );
569+
$response = rest_get_server()->dispatch( $request );
570+
571+
$this->assertSame( 200, $response->get_status() );
572+
573+
$names = wp_list_pluck( $response->get_data(), 'name' );
574+
$this->assertContains( 'rest-visibility-list/visible', $names );
575+
$this->assertNotContains( 'rest-visibility-list/hidden', $names );
576+
577+
wp_unregister_icon_collection( 'rest-visibility-list' );
578+
}
579+
580+
/**
581+
* Test that a non-public icon is reported as not found by name, while
582+
* remaining available to server-side code.
583+
*
584+
* @ticket TODO
585+
*
586+
* @covers ::get_item
587+
* @covers ::get_icon
588+
*/
589+
public function test_get_item_returns_404_for_non_public_icon() {
590+
wp_register_icon_collection( 'rest-visibility-single', array( 'label' => 'REST Visibility' ) );
591+
wp_register_icon(
592+
'rest-visibility-single/hidden',
593+
array(
594+
'label' => 'Hidden',
595+
'content' => '<svg><path d="M1 1"/></svg>',
596+
'public' => false,
597+
)
598+
);
599+
600+
wp_set_current_user( self::$editor_id );
601+
602+
$request = new WP_REST_Request( 'GET', '/wp/v2/icons/rest-visibility-single/hidden' );
603+
$response = rest_get_server()->dispatch( $request );
604+
605+
$this->assertErrorResponse( 'rest_icon_not_found', $response, 404 );
606+
607+
// The icon is hidden from the REST API, not unregistered.
608+
$this->assertStringContainsString( '<svg', wp_get_icon( 'rest-visibility-single/hidden' ) );
609+
610+
wp_unregister_icon_collection( 'rest-visibility-single' );
611+
}
540612
}

0 commit comments

Comments
 (0)