-
Notifications
You must be signed in to change notification settings - Fork 4.8k
Icons API: Support searching in labels; extend classes post-7.0 work #75878
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 3 commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
d8d085e
Try: Extend classes of Icons APIs for post-7.0 work
mcsf a362028
Prefer `gutenberg_dir_path`
mcsf 35adbd3
Move rest_api_init hook to 7.1 compat directory
t-hamano 7cad2b7
WP_Icons_Registry: Port changes from Core
mcsf 1b9874e
Trim extended class thanks to parent's `protected` visibility
mcsf 4445a89
Add test case for searching in icon labels
mcsf File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
250 changes: 250 additions & 0 deletions
250
lib/compat/wordpress-7.1/class-gutenberg-icons-registry-7-1.php
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 = 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 ] ); | ||
| } | ||
| } | ||
82 changes: 82 additions & 0 deletions
82
lib/compat/wordpress-7.1/class-gutenberg-rest-icons-controller-7-1.php
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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; | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 ); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
::registerpublic in 7.1, we won't be as concerned with the exposure that this hook would entail. :)