Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
250 changes: 250 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,250 @@
<?php

class Gutenberg_Icons_Registry_7_1 extends WP_Icons_Registry {
/**
* Modified to point $manifest_path at 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;
}

/*
*
* THE DEFINITIONS BELOW MAY BE REMOVED IF WP_ICONS_REGISTRY CHANGES
* VISIBILITY OF ITS METHODS FROM PRIVATE TO PROTECTED
*
*/

/**
* Redefined to be accessible to `__construct`
*/
protected function register( $icon_name, $icon_properties ) {
if ( ! isset( $icon_name ) || ! is_string( $icon_name ) ) {
_doing_it_wrong(
__METHOD__,
__( 'Icon name must be a string.', 'gutenberg' ),
'7.0.0'
);
return false;
}

$allowed_keys = array_fill_keys( array( 'label', 'content', 'filePath' ), 1 );
foreach ( array_keys( $icon_properties ) as $key ) {
if ( ! array_key_exists( $key, $allowed_keys ) ) {
_doing_it_wrong(
__METHOD__,
sprintf(
// translators: %s is the name of any user-provided key
__( 'Invalid icon property: "%s".', 'gutenberg' ),
$key
),
'7.0.0'
);
return false;
}
}

if ( ! isset( $icon_properties['label'] ) || ! is_string( $icon_properties['label'] ) ) {
_doing_it_wrong(
__METHOD__,
__( 'Icon label must be a string.', 'gutenberg' ),
'7.0.0'
);
return false;
}

if (
( ! isset( $icon_properties['content'] ) && ! isset( $icon_properties['filePath'] ) ) ||
( isset( $icon_properties['content'] ) && isset( $icon_properties['filePath'] ) )
) {
_doing_it_wrong(
__METHOD__,
__( 'Icons must provide either `content` or `filePath`.', 'gutenberg' ),
'7.0.0'
);
return false;
}

if ( isset( $icon_properties['content'] ) ) {
if ( ! is_string( $icon_properties['content'] ) ) {
_doing_it_wrong(
__METHOD__,
__( 'Icon content must be a string.', 'gutenberg' ),
'7.0.0'
);
return false;
}

$sanitized_icon_content = $this->sanitize_icon_content( $icon_properties['content'] );
if ( empty( $sanitized_icon_content ) ) {
_doing_it_wrong(
__METHOD__,
__( 'Icon content does not contain valid SVG markup.', 'gutenberg' ),
'7.0.0'
);
return false;
}
}

$icon = array_merge(
$icon_properties,
array( 'name' => $icon_name )
);

$this->registered_icons[ $icon_name ] = $icon;

return true;
}

/**
* Redefined to be accessible to `get_registered_icon` and `get_registered_icons`
*/
protected function get_content( $icon_name ) {
if ( ! isset( $this->registered_icons[ $icon_name ]['content'] ) ) {
$content = file_get_contents(
$this->registered_icons[ $icon_name ]['filePath']
);
$content = $this->sanitize_icon_content( $content );

if ( empty( $content ) ) {
wp_trigger_error(
__METHOD__,
__( 'Icon content does not contain valid SVG markup.', 'gutenberg' )
);
return null;
}

$this->registered_icons[ $icon_name ]['content'] = $content;
}
return $this->registered_icons[ $icon_name ]['content'];
}

/**
* Redefined to be accessible to `register` and `get_content`
*/
protected function sanitize_icon_content( $icon_content ) {
$allowed_tags = array(
'svg' => array(
'class' => true,
'xmlns' => true,
'width' => true,
'height' => true,
'viewbox' => true,
'aria-hidden' => true,
'role' => true,
'focusable' => true,
),
'path' => array(
'fill' => true,
'fill-rule' => true,
'd' => true,
'transform' => true,
),
'polygon' => array(
'fill' => true,
'fill-rule' => true,
'points' => true,
'transform' => true,
'focusable' => true,
),
);
return wp_kses( $icon_content, $allowed_tags );
}

/**
* Redefined to access the new `$registered_icons`
*/
public function get_registered_icon( $icon_name ) {
if ( ! $this->is_registered( $icon_name ) ) {
return null;
}

$icon = $this->registered_icons[ $icon_name ];
$icon['content'] = $icon['content'] ?? $this->get_content( $icon_name );

return $icon;
}

public function is_registered( $icon_name ) {
return isset( $this->registered_icons[ $icon_name ] );
}
}
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
Loading