Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 14 additions & 7 deletions lib/compat/wordpress-7.0/class-wp-icons-registry.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,29 @@ class WP_Icons_Registry {
*
* @var array[]
*/
private $registered_icons = array();
protected $registered_icons = array();


/**
* Container for the main instance of the class.
*
* @var WP_Icons_Registry|null
*/
private static $instance = null;
protected static $instance = null;

/**
* Constructor.
*
* WP_Icons_Registry is a singleton class, so keep this private.
* WP_Icons_Registry is a singleton class, so keep this protected.
*
* For WP 7.0, the Icons Registry is closed for third-party icon
* registry, serving only a subset of core icons.
*
* These icons are defined in @wordpress/packages as SVG files and as
* entries in a single manifest file. On init, the registry is loaded
* with those icons listed in the manifest.
*/
private function __construct() {
protected function __construct() {
$icons_directory = __DIR__ . '/../../../packages/icons/src/';
$icons_directory = trailingslashit( $icons_directory );
$manifest_path = $icons_directory . 'manifest.php';
Expand Down Expand Up @@ -83,7 +90,7 @@ private function __construct() {
* }
* @return bool True if the icon was registered with success and false otherwise.
*/
private function register( $icon_name, $icon_properties ) {
protected function register( $icon_name, $icon_properties ) {
if ( ! isset( $icon_name ) || ! is_string( $icon_name ) ) {
_doing_it_wrong(
__METHOD__,
Expand Down Expand Up @@ -170,7 +177,7 @@ private function register( $icon_name, $icon_properties ) {
* @param string $icon_content The icon SVG content to sanitize.
* @return string The sanitized icon SVG content.
*/
private function sanitize_icon_content( $icon_content ) {
protected function sanitize_icon_content( $icon_content ) {
$allowed_tags = array(
'svg' => array(
'class' => true,
Expand Down Expand Up @@ -205,7 +212,7 @@ private function sanitize_icon_content( $icon_content ) {
* @param string $icon_name Icon name including namespace.
* @return string|null The content of the icon, if found.
*/
private function get_content( $icon_name ) {
protected function get_content( $icon_name ) {
if ( ! isset( $this->registered_icons[ $icon_name ]['content'] ) ) {
$content = file_get_contents(
$this->registered_icons[ $icon_name ]['filePath']
Expand Down
89 changes: 89 additions & 0 deletions lib/compat/wordpress-7.1/class-gutenberg-icons-registry-7-1.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
<?php

class Gutenberg_Icons_Registry_7_1 extends WP_Icons_Registry {
/**
* Modified to point $manifest_path to Gutenberg packages
*/
protected function __construct() {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P.S. I plan to move all the code in the constructor to a hook in the future, so that Gutenbeg can easily override the core icons.

It will probably look something like this. What do you think?

// Probably in WordPress 7.1
function _register_core_icons() {
	$icons_directory = __DIR__ . '/icons/';
	// ...
}
add_action( 'init', '_register_core_icons' );

// On the Gutenberg plugin side, remove the core hook and re-register the core icon
remove_action( 'init', '_register_core_icons' );
add_action( 'init', '_gutenberg_register_core_icons' );

function _gutenberg_register_core_icons() {
	// In WordPress 7.0, the core icon registration process is hard-coded
	// in the constructor, so we will explicitly unregister all of them here.
	$all_icons = WP_Icons_Registry::get_instance()->get_registered_icons();
	foreach ( $all_icons as $icon ) {
		WP_Icons_Registry::get_instance()->unregister( $icon['name'] );
	}

	// Re-register core icons using assets from Gutenberg
	$icons_directory = __DIR__ . '/../../../packages/icons/src/';
	// ...
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that would be alright. Assuming that we'll be ready to make ::register public in 7.1, we won't be as concerned with the exposure that this hook would entail. :)

$icons_directory = gutenberg_dir_path() . 'packages/icons/src';
$icons_directory = trailingslashit( $icons_directory );
$manifest_path = $icons_directory . 'manifest.php';

if ( ! is_readable( $manifest_path ) ) {
wp_trigger_error(
__METHOD__,
__( 'Core icon collection manifest is missing or unreadable.', 'gutenberg' )
);
return;
}

$collection = include $manifest_path;

if ( empty( $collection ) ) {
wp_trigger_error(
__METHOD__,
__( 'Core icon collection manifest is empty or invalid.', 'gutenberg' )
);
return;
}

foreach ( $collection as $icon_name => $icon_data ) {
if (
empty( $icon_data['filePath'] )
|| ! is_string( $icon_data['filePath'] )
) {
_doing_it_wrong(
__METHOD__,
__( 'Core icon collection manifest must provide valid a "filePath" for each icon.', 'gutenberg' ),
'7.0.0'
);
return;
}

$this->register(
'core/' . $icon_name,
array(
'label' => $icon_data['label'],
'filePath' => $icons_directory . $icon_data['filePath'],
)
);
}
}

/**
* Modified to also search in icon labels
*/
public function get_registered_icons( $search = '' ) {
$icons = array();

foreach ( $this->registered_icons as $icon ) {
if ( ! empty( $search )
&& false === stripos( $icon['name'], $search )
&& false === stripos( $icon['label'], $search )
) {
continue;
}

$icon['content'] = $icon['content'] ?? $this->get_content( $icon['name'] );
$icons[] = $icon;
}

return $icons;
}

/**
* Redefined to break away from base class.
*/
protected static $instance = null;

/**
* Redefined to access new `$instance`
*/
public static function get_instance() {
if ( null === self::$instance ) {
self::$instance = new self();
}

return self::$instance;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<?php

class Gutenberg_REST_Icons_Controller_7_1 extends WP_REST_Icons_Controller {
/**
* Modified to point to the new `get_item` and `get_items`
*/
public function register_routes() {
register_rest_route(
$this->namespace,
'/' . $this->rest_base,
array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_items' ),
'permission_callback' => array( $this, 'get_items_permissions_check' ),
'args' => $this->get_collection_params(),
),
'schema' => array( $this, 'get_public_item_schema' ),
),
true // Override the core route.
);

register_rest_route(
$this->namespace,
'/' . $this->rest_base . '/(?P<name>[a-z][a-z0-9-]*/[a-z][a-z0-9-]*)',
array(
'args' => array(
'name' => array(
'description' => __( 'Icon name.', 'gutenberg' ),
'type' => 'string',
),
),
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_item' ),
'permission_callback' => array( $this, 'get_item_permissions_check' ),
'args' => array(
'context' => $this->get_context_param( array( 'default' => 'view' ) ),
),
),
'schema' => array( $this, 'get_public_item_schema' ),
),
true // Override the core route.
);
}

/**
* Modified to call Gutenberg_Icons_Registry_7_1
*/
public function get_items( $request ) {
$response = array();
$search = $request->get_param( 'search' );
$icons = Gutenberg_Icons_Registry_7_1::get_instance()->get_registered_icons( $search );
foreach ( $icons as $icon ) {
$prepared_icon = $this->prepare_item_for_response( $icon, $request );
$response[] = $this->prepare_response_for_collection( $prepared_icon );
}
return rest_ensure_response( $response );
}

/**
* Modified to call Gutenberg_Icons_Registry_7_1
*/
public function get_icon( $name ) {
$registry = Gutenberg_Icons_Registry_7_1::get_instance();
$icon = $registry->get_registered_icon( $name );

if ( null === $icon ) {
return new WP_Error(
'rest_icon_not_found',
sprintf(
// translators: %s is the name of any user-provided name
__( 'Icon not found: "%s".', 'gutenberg' ),
$name
),
array( 'status' => 404 )
);
}

return $icon;
}
}
16 changes: 16 additions & 0 deletions lib/compat/wordpress-7.1/rest-api.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php
/**
* WordPress 7.1 compatibility functions for the Gutenberg
* editor plugin changes related to REST API.
*
* @package gutenberg
*/

/**
* Registers the Icons REST API routes.
*/
function gutenberg_register_icons_controller_endpoints() {
$icons_controller = new Gutenberg_REST_Icons_Controller_7_1();
$icons_controller->register_routes();
}
add_action( 'rest_api_init', 'gutenberg_register_icons_controller_endpoints', PHP_INT_MAX );
5 changes: 5 additions & 0 deletions lib/load.php
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,11 @@ function gutenberg_is_experiment_enabled( $name ) {
require __DIR__ . '/compat/wordpress-7.0/rest-api.php';
require __DIR__ . '/compat/wordpress-7.0/global-styles.php';

// WordPress 7.1 compat.
require __DIR__ . '/compat/wordpress-7.1/class-gutenberg-icons-registry-7-1.php';
require __DIR__ . '/compat/wordpress-7.1/class-gutenberg-rest-icons-controller-7-1.php';
require __DIR__ . '/compat/wordpress-7.1/rest-api.php';

// Plugin specific code.
require_once __DIR__ . '/class-wp-rest-global-styles-controller-gutenberg.php';
require_once __DIR__ . '/class-wp-rest-edit-site-export-controller-gutenberg.php';
Expand Down
17 changes: 17 additions & 0 deletions phpunit/experimental/class-wp-rest-icon-controller-test.php
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,23 @@ public function test_get_items_search_filters_results() {
$this->assertContains( 'core/arrow-left', $icon_names, 'Search results should include core/arrow-left icon' );
}

/**
* Test that GET /wp/v2/icons/?search=%s searches icon labels too.
*/
public function test_get_items_search_includes_label() {
wp_set_current_user( self::$editor_id );

$request = new WP_REST_Request( 'GET', '/wp/v2/icons' );

// The '@' character is only found in the *label* for core/at-symbol
$request->set_param( 'search', '@' );
$response = rest_get_server()->dispatch( $request );
$data = $response->get_data();

$this->assertSame( 200, $response->get_status() );
$this->assertEquals( array( 'core/at-symbol' ), array_column( $data, 'name' ) );
}

/**
* Test that search is case-insensitive.
*/
Expand Down
Loading