Skip to content
Draft
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
9 changes: 9 additions & 0 deletions lib/compat/wordpress-7.0/rest-api.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,15 @@ function gutenberg_register_block_patterns_controller_endpoints() {
}
add_action( 'rest_api_init', 'gutenberg_register_block_patterns_controller_endpoints' );

/**
* 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 );

/**
* Registers the Registered Templates REST API routes.
* The template activation experiment registers its own routes, so we only register the registered templates controller if the experiment is not enabled.
Expand Down
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() {
$icons_directory = __DIR__ . '/../../../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;
}
}
4 changes: 4 additions & 0 deletions lib/load.php
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ 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';

// 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
8 changes: 7 additions & 1 deletion packages/icons/src/manifest.php
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,13 @@
'filePath' => 'library/symbol-filled.svg',
),
'table' => array(
'label' => _x( 'Table', 'icon label', 'gutenberg' ),
// FIXME: Label amended to test the search feature:
//
// /wp/v2/icons?search=verse
// -> core/verse
// -> core/table
//
'label' => _x( 'Table Verse', 'icon label', 'gutenberg' ),
'filePath' => 'library/table.svg',
),
'tablet' => array(
Expand Down
Loading