Skip to content

Commit 421782e

Browse files
mcsft-hamano
andauthored
Icons API: Support searching in labels; extend classes post-7.0 work (#75878)
Enhance the Icons registry via compat classes to: - Also search within icon labels, not just icon names - Point to Gutenberg's own manifest.php file rather than Core's - Set a precedent for future Gutenberg-side iteration --------- Co-authored-by: Aki Hamano <[email protected]> Co-authored-by: Aki Hamano <[email protected]> Co-authored-by: mcsf <[email protected]> Co-authored-by: t-hamano <[email protected]>
1 parent 0086dd2 commit 421782e

6 files changed

Lines changed: 223 additions & 7 deletions

File tree

lib/compat/wordpress-7.0/class-wp-icons-registry.php

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,29 @@ class WP_Icons_Registry {
77
*
88
* @var array[]
99
*/
10-
private $registered_icons = array();
10+
protected $registered_icons = array();
1111

1212

1313
/**
1414
* Container for the main instance of the class.
1515
*
1616
* @var WP_Icons_Registry|null
1717
*/
18-
private static $instance = null;
18+
protected static $instance = null;
1919

2020
/**
2121
* Constructor.
2222
*
23-
* WP_Icons_Registry is a singleton class, so keep this private.
23+
* WP_Icons_Registry is a singleton class, so keep this protected.
24+
*
25+
* For WP 7.0, the Icons Registry is closed for third-party icon
26+
* registry, serving only a subset of core icons.
27+
*
28+
* These icons are defined in @wordpress/packages as SVG files and as
29+
* entries in a single manifest file. On init, the registry is loaded
30+
* with those icons listed in the manifest.
2431
*/
25-
private function __construct() {
32+
protected function __construct() {
2633
$icons_directory = __DIR__ . '/../../../packages/icons/src/';
2734
$icons_directory = trailingslashit( $icons_directory );
2835
$manifest_path = $icons_directory . 'manifest.php';
@@ -83,7 +90,7 @@ private function __construct() {
8390
* }
8491
* @return bool True if the icon was registered with success and false otherwise.
8592
*/
86-
private function register( $icon_name, $icon_properties ) {
93+
protected function register( $icon_name, $icon_properties ) {
8794
if ( ! isset( $icon_name ) || ! is_string( $icon_name ) ) {
8895
_doing_it_wrong(
8996
__METHOD__,
@@ -170,7 +177,7 @@ private function register( $icon_name, $icon_properties ) {
170177
* @param string $icon_content The icon SVG content to sanitize.
171178
* @return string The sanitized icon SVG content.
172179
*/
173-
private function sanitize_icon_content( $icon_content ) {
180+
protected function sanitize_icon_content( $icon_content ) {
174181
$allowed_tags = array(
175182
'svg' => array(
176183
'class' => true,
@@ -205,7 +212,7 @@ private function sanitize_icon_content( $icon_content ) {
205212
* @param string $icon_name Icon name including namespace.
206213
* @return string|null The content of the icon, if found.
207214
*/
208-
private function get_content( $icon_name ) {
215+
protected function get_content( $icon_name ) {
209216
if ( ! isset( $this->registered_icons[ $icon_name ]['content'] ) ) {
210217
$content = file_get_contents(
211218
$this->registered_icons[ $icon_name ]['filePath']
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
<?php
2+
3+
class Gutenberg_Icons_Registry_7_1 extends WP_Icons_Registry {
4+
/**
5+
* Modified to point $manifest_path to Gutenberg packages
6+
*/
7+
protected function __construct() {
8+
$icons_directory = gutenberg_dir_path() . 'packages/icons/src';
9+
$icons_directory = trailingslashit( $icons_directory );
10+
$manifest_path = $icons_directory . 'manifest.php';
11+
12+
if ( ! is_readable( $manifest_path ) ) {
13+
wp_trigger_error(
14+
__METHOD__,
15+
__( 'Core icon collection manifest is missing or unreadable.', 'gutenberg' )
16+
);
17+
return;
18+
}
19+
20+
$collection = include $manifest_path;
21+
22+
if ( empty( $collection ) ) {
23+
wp_trigger_error(
24+
__METHOD__,
25+
__( 'Core icon collection manifest is empty or invalid.', 'gutenberg' )
26+
);
27+
return;
28+
}
29+
30+
foreach ( $collection as $icon_name => $icon_data ) {
31+
if (
32+
empty( $icon_data['filePath'] )
33+
|| ! is_string( $icon_data['filePath'] )
34+
) {
35+
_doing_it_wrong(
36+
__METHOD__,
37+
__( 'Core icon collection manifest must provide valid a "filePath" for each icon.', 'gutenberg' ),
38+
'7.0.0'
39+
);
40+
return;
41+
}
42+
43+
$this->register(
44+
'core/' . $icon_name,
45+
array(
46+
'label' => $icon_data['label'],
47+
'filePath' => $icons_directory . $icon_data['filePath'],
48+
)
49+
);
50+
}
51+
}
52+
53+
/**
54+
* Modified to also search in icon labels
55+
*/
56+
public function get_registered_icons( $search = '' ) {
57+
$icons = array();
58+
59+
foreach ( $this->registered_icons as $icon ) {
60+
if ( ! empty( $search )
61+
&& false === stripos( $icon['name'], $search )
62+
&& false === stripos( $icon['label'], $search )
63+
) {
64+
continue;
65+
}
66+
67+
$icon['content'] = $icon['content'] ?? $this->get_content( $icon['name'] );
68+
$icons[] = $icon;
69+
}
70+
71+
return $icons;
72+
}
73+
74+
/**
75+
* Redefined to break away from base class.
76+
*/
77+
protected static $instance = null;
78+
79+
/**
80+
* Redefined to access new `$instance`
81+
*/
82+
public static function get_instance() {
83+
if ( null === self::$instance ) {
84+
self::$instance = new self();
85+
}
86+
87+
return self::$instance;
88+
}
89+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
<?php
2+
3+
class Gutenberg_REST_Icons_Controller_7_1 extends WP_REST_Icons_Controller {
4+
/**
5+
* Modified to point to the new `get_item` and `get_items`
6+
*/
7+
public function register_routes() {
8+
register_rest_route(
9+
$this->namespace,
10+
'/' . $this->rest_base,
11+
array(
12+
array(
13+
'methods' => WP_REST_Server::READABLE,
14+
'callback' => array( $this, 'get_items' ),
15+
'permission_callback' => array( $this, 'get_items_permissions_check' ),
16+
'args' => $this->get_collection_params(),
17+
),
18+
'schema' => array( $this, 'get_public_item_schema' ),
19+
),
20+
true // Override the core route.
21+
);
22+
23+
register_rest_route(
24+
$this->namespace,
25+
'/' . $this->rest_base . '/(?P<name>[a-z][a-z0-9-]*/[a-z][a-z0-9-]*)',
26+
array(
27+
'args' => array(
28+
'name' => array(
29+
'description' => __( 'Icon name.', 'gutenberg' ),
30+
'type' => 'string',
31+
),
32+
),
33+
array(
34+
'methods' => WP_REST_Server::READABLE,
35+
'callback' => array( $this, 'get_item' ),
36+
'permission_callback' => array( $this, 'get_item_permissions_check' ),
37+
'args' => array(
38+
'context' => $this->get_context_param( array( 'default' => 'view' ) ),
39+
),
40+
),
41+
'schema' => array( $this, 'get_public_item_schema' ),
42+
),
43+
true // Override the core route.
44+
);
45+
}
46+
47+
/**
48+
* Modified to call Gutenberg_Icons_Registry_7_1
49+
*/
50+
public function get_items( $request ) {
51+
$response = array();
52+
$search = $request->get_param( 'search' );
53+
$icons = Gutenberg_Icons_Registry_7_1::get_instance()->get_registered_icons( $search );
54+
foreach ( $icons as $icon ) {
55+
$prepared_icon = $this->prepare_item_for_response( $icon, $request );
56+
$response[] = $this->prepare_response_for_collection( $prepared_icon );
57+
}
58+
return rest_ensure_response( $response );
59+
}
60+
61+
/**
62+
* Modified to call Gutenberg_Icons_Registry_7_1
63+
*/
64+
public function get_icon( $name ) {
65+
$registry = Gutenberg_Icons_Registry_7_1::get_instance();
66+
$icon = $registry->get_registered_icon( $name );
67+
68+
if ( null === $icon ) {
69+
return new WP_Error(
70+
'rest_icon_not_found',
71+
sprintf(
72+
// translators: %s is the name of any user-provided name
73+
__( 'Icon not found: "%s".', 'gutenberg' ),
74+
$name
75+
),
76+
array( 'status' => 404 )
77+
);
78+
}
79+
80+
return $icon;
81+
}
82+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
/**
3+
* WordPress 7.1 compatibility functions for the Gutenberg
4+
* editor plugin changes related to REST API.
5+
*
6+
* @package gutenberg
7+
*/
8+
9+
/**
10+
* Registers the Icons REST API routes.
11+
*/
12+
function gutenberg_register_icons_controller_endpoints() {
13+
$icons_controller = new Gutenberg_REST_Icons_Controller_7_1();
14+
$icons_controller->register_routes();
15+
}
16+
add_action( 'rest_api_init', 'gutenberg_register_icons_controller_endpoints', PHP_INT_MAX );

lib/load.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,11 @@ function gutenberg_is_experiment_enabled( $name ) {
7878
require __DIR__ . '/compat/wordpress-7.0/class-wp-connector-registry.php';
7979
require __DIR__ . '/compat/wordpress-7.0/connectors.php';
8080

81+
// WordPress 7.1 compat.
82+
require __DIR__ . '/compat/wordpress-7.1/class-gutenberg-icons-registry-7-1.php';
83+
require __DIR__ . '/compat/wordpress-7.1/class-gutenberg-rest-icons-controller-7-1.php';
84+
require __DIR__ . '/compat/wordpress-7.1/rest-api.php';
85+
8186
// Plugin specific code.
8287
require_once __DIR__ . '/class-wp-rest-global-styles-controller-gutenberg.php';
8388
require_once __DIR__ . '/class-wp-rest-edit-site-export-controller-gutenberg.php';

phpunit/experimental/class-wp-rest-icon-controller-test.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,23 @@ public function test_get_items_search_filters_results() {
150150
$this->assertContains( 'core/arrow-left', $icon_names, 'Search results should include core/arrow-left icon' );
151151
}
152152

153+
/**
154+
* Test that GET /wp/v2/icons/?search=%s searches icon labels too.
155+
*/
156+
public function test_get_items_search_includes_label() {
157+
wp_set_current_user( self::$editor_id );
158+
159+
$request = new WP_REST_Request( 'GET', '/wp/v2/icons' );
160+
161+
// The '@' character is only found in the *label* for core/at-symbol
162+
$request->set_param( 'search', '@' );
163+
$response = rest_get_server()->dispatch( $request );
164+
$data = $response->get_data();
165+
166+
$this->assertSame( 200, $response->get_status() );
167+
$this->assertEquals( array( 'core/at-symbol' ), array_column( $data, 'name' ) );
168+
}
169+
153170
/**
154171
* Test that search is case-insensitive.
155172
*/

0 commit comments

Comments
 (0)