Make WordPress Core

Changeset 62748


Ignore:
Timestamp:
07/15/2026 04:25:35 AM (8 weeks ago)
Author:
wildworks
Message:

Icons: Add APIs for collection and icon registration

Introduces a set of public APIs for registering icon collections and individual icons, allowing themes and plugins to register icons for consistent use across the block editor and the wider WordPress admin.

  • Add the WP_Icon_Collections_Registry class, a singleton that manages the registration of icon collections. Each collection groups a set of icons under a namespace and is exposed through the REST API via the new WP_REST_Icon_Collections_Controller.
  • Update the WP_Icons_Registry class to allow registering custom icons through public register() and unregister() methods, surfaced through WP_REST_Icons_Controller.
  • Add four wrapper functions in icons.php: wp_register_icon_collection() and wp_unregister_icon_collection() for collections, and wp_register_icon() and wp_unregister_icon() for individual icons.
  • Register the default collections and icons through _wp_register_default_icon_collections() and _wp_register_default_icons(), both hooked to the init action.

Props mcsf, tyxla, wildworks.
See #64847.

Location:
trunk
Files:
4 added
11 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/class-wp-icons-registry.php

    r62551 r62748  
    11<?php
    2 
    32/**
    43 * Icons API: WP_Icons_Registry class
     
    3534         * WP_Icons_Registry is a singleton class, so keep this protected.
    3635         *
    37          * For 7.0, the Icons Registry is closed for third-party icon registry,
    38          * serving only a subset of core icons.
    39          *
    40          * These icons are defined in @wordpress/packages (Gutenberg repository) as
    41          * SVG files and as entries in a single manifest file. On init, the
    42          * registry is loaded with those icons listed in the manifest.
    43          *
    44          * @since 7.0.0
    45          */
    46         protected function __construct() {
    47                 $icons_directory = __DIR__ . '/images/icon-library/';
    48                 $manifest_path   = __DIR__ . '/assets/icon-library-manifest.php';
    49 
    50                 if ( ! is_readable( $manifest_path ) ) {
    51                         wp_trigger_error(
    52                                 __METHOD__,
    53                                 __( 'Core icon collection manifest is missing or unreadable.' )
    54                         );
    55                         return;
    56                 }
    57 
    58                 $collection = include $manifest_path;
    59 
    60                 if ( empty( $collection ) ) {
    61                         wp_trigger_error(
    62                                 __METHOD__,
    63                                 __( 'Core icon collection manifest is empty or invalid.' )
    64                         );
    65                         return;
    66                 }
    67 
    68                 foreach ( $collection as $icon_name => $icon_data ) {
    69                         if (
    70                                 empty( $icon_data['filePath'] )
    71                                 || ! is_string( $icon_data['filePath'] )
    72                         ) {
    73                                 _doing_it_wrong(
    74                                         __METHOD__,
    75                                         __( 'Core icon collection manifest must provide valid a "filePath" for each icon.' ),
    76                                         '7.0.0'
    77                                 );
    78                                 return;
    79                         }
    80 
    81                         $this->register(
    82                                 'core/' . $icon_name,
    83                                 array(
    84                                         'label'     => $icon_data['label'],
    85                                         'file_path' => $icons_directory . $icon_data['filePath'],
    86                                 )
    87                         );
    88                 }
    89         }
     36         * Icons are populated via `_wp_register_default_icons()` during the
     37         * `init` action. Third-party icons can be registered via
     38         * {@see wp_register_icon()} once their collection is registered.
     39         *
     40         * @since 7.0.0
     41         */
     42        protected function __construct() {}
    9043
    9144        /**
     
    9346         *
    9447         * @since 7.0.0
    95          *
    96          * @param string $icon_name       Icon name including namespace.
     48         * @since 7.1.0 The icon name must be namespaced in the form "collection/icon-name".
     49         *
     50         * @param string $icon_name       Namespaced icon name in the form "collection/icon-name"
     51         *                                (e.g. "core/arrow-left").
    9752         * @param array  $icon_properties {
    9853         *     List of properties for the icon.
     
    10661         * @return bool True if the icon was registered with success and false otherwise.
    10762         */
    108         protected function register( $icon_name, $icon_properties ) {
     63        public function register( $icon_name, $icon_properties ) {
    10964                if ( ! isset( $icon_name ) || ! is_string( $icon_name ) ) {
    11065                        _doing_it_wrong(
     
    11671                }
    11772
    118                 if ( preg_match( '/[A-Z]/', $icon_name ) ) {
     73                // Require a namespaced name in the form "collection/icon-name".
     74                if ( ! str_contains( $icon_name, '/' ) ) {
     75                        _doing_it_wrong(
     76                                __METHOD__,
     77                                __( 'Icon name must be namespaced in the form "collection/icon-name".' ),
     78                                '7.1.0'
     79                        );
     80                        return false;
     81                }
     82
     83                // Split the namespaced name into a collection slug and an unqualified icon name.
     84                list( $collection, $unqualified_name ) = explode( '/', $icon_name, 2 );
     85
     86                if ( preg_match( '/[A-Z]/', $unqualified_name ) ) {
    11987                        _doing_it_wrong(
    12088                                __METHOD__,
     
    12593                }
    12694
    127                 $name_matcher = '/^[a-z][a-z0-9-]*\/[a-z][a-z0-9-]*$/';
    128                 if ( ! preg_match( $name_matcher, $icon_name ) ) {
    129                         _doing_it_wrong(
    130                                 __METHOD__,
    131                                 __( 'Icon names must contain a namespace prefix. Example: my-plugin/my-custom-icon' ),
    132                                 '7.1.0'
    133                         );
    134                         return false;
    135                 }
    136 
    137                 if ( $this->is_registered( $icon_name ) ) {
    138                         _doing_it_wrong(
    139                                 __METHOD__,
    140                                 __( 'Icon is already registered.' ),
     95                if ( ! preg_match( '/^[a-z0-9][a-z0-9_-]*$/', $unqualified_name ) ) {
     96                        _doing_it_wrong(
     97                                __METHOD__,
     98                                __( 'Icon names must start with a lowercase letter or digit and contain only lowercase letters, digits, hyphens, and underscores.' ),
    14199                                '7.1.0'
    142100                        );
     
    150108                                        __METHOD__,
    151109                                        sprintf(
    152                                                 // translators: %s is the name of any user-provided key
     110                                                /* translators: %s: The name of a user-provided key. */
    153111                                                __( 'Invalid icon property: "%s".' ),
    154112                                                $key
     
    160118                }
    161119
     120                if ( ! WP_Icon_Collections_Registry::get_instance()->is_registered( $collection ) ) {
     121                        _doing_it_wrong(
     122                                __METHOD__,
     123                                sprintf(
     124                                        /* translators: %s: Icon collection slug. */
     125                                        __( 'Icon collection "%s" is not registered.' ),
     126                                        $collection
     127                                ),
     128                                '7.1.0'
     129                        );
     130                        return false;
     131                }
     132
    162133                if ( ! isset( $icon_properties['label'] ) || ! is_string( $icon_properties['label'] ) ) {
    163134                        _doing_it_wrong(
     
    202173                }
    203174
     175                $qualified_name = $collection . '/' . $unqualified_name;
     176
     177                if ( $this->is_registered( $qualified_name ) ) {
     178                        _doing_it_wrong(
     179                                __METHOD__,
     180                                __( 'Icon is already registered.' ),
     181                                '7.1.0'
     182                        );
     183                        return false;
     184                }
     185
    204186                $icon = array_merge(
    205187                        $icon_properties,
    206                         array( 'name' => $icon_name )
     188                        array(
     189                                'name'       => $qualified_name,
     190                                'collection' => $collection,
     191                        )
    207192                );
    208193
    209                 $this->registered_icons[ $icon_name ] = $icon;
    210 
     194                $this->registered_icons[ $qualified_name ] = $icon;
     195
     196                return true;
     197        }
     198
     199        /**
     200         * Unregisters an icon.
     201         *
     202         * @since 7.1.0
     203         *
     204         * @param string $icon_name Namespaced icon name in the form "collection/icon-name"
     205         *                          (e.g. "core/arrow-left").
     206         * @return bool True if the icon was unregistered successfully, false otherwise.
     207         */
     208        public function unregister( $icon_name ) {
     209                if ( ! $this->is_registered( $icon_name ) ) {
     210                        _doing_it_wrong(
     211                                __METHOD__,
     212                                sprintf(
     213                                        /* translators: %s: Icon name. */
     214                                        __( 'Icon "%s" is not registered.' ),
     215                                        $icon_name
     216                                ),
     217                                '7.1.0'
     218                        );
     219                        return false;
     220                }
     221
     222                unset( $this->registered_icons[ $icon_name ] );
    211223                return true;
    212224        }
     
    262274        protected function get_content( $icon_name ) {
    263275                if ( ! isset( $this->registered_icons[ $icon_name ]['content'] ) ) {
    264                         $content = file_get_contents(
    265                                 $this->registered_icons[ $icon_name ]['file_path']
    266                         );
    267                         $content = $this->sanitize_icon_content( $content );
     276                        $file_path  = $this->registered_icons[ $icon_name ]['file_path'] ?? '';
     277                        $is_stringy = is_string( $file_path ) || ( is_object( $file_path ) && method_exists( $file_path, '__toString' ) );
     278                        $icon_path  = $is_stringy ? realpath( (string) $file_path ) : false;
     279
     280                        if (
     281                                ! is_string( $icon_path ) ||
     282                                ! str_ends_with( $icon_path, '.svg' ) ||
     283                                ! is_file( $icon_path ) ||
     284                                ! is_readable( $icon_path )
     285                        ) {
     286                                wp_trigger_error(
     287                                        __METHOD__,
     288                                        __( 'Icon file is missing or unreadable.' )
     289                                );
     290                                return null;
     291                        }
     292
     293                        $content = $this->sanitize_icon_content( file_get_contents( $icon_path ) );
    268294
    269295                        if ( empty( $content ) ) {
     
    303329         *
    304330         * @since 7.0.0
     331         * @since 7.1.0 Search also matches icon labels.
    305332         *
    306333         * @param string $search Optional. Search term by which to filter the icons.
  • trunk/src/wp-includes/default-filters.php

    r62668 r62748  
    819819add_action( 'init', '_wp_register_default_font_collections' );
    820820
     821// Icons.
     822add_action( 'init', '_wp_register_default_icon_collections', 0 );
     823add_action( 'init', '_wp_register_default_icons' );
     824
    821825// Add ignoredHookedBlocks metadata attribute to the template and template part post types.
    822826add_filter( 'rest_pre_insert_wp_template', 'inject_ignored_hooked_blocks_metadata_attributes' );
  • trunk/src/wp-includes/icons.php

    r62736 r62748  
    11<?php
    22/**
    3  * Icons API: Icon-rendering helper functions.
     3 * Icons API: Icon registration and rendering helper functions.
    44 *
    55 * @package WordPress
     
    77 * @since 7.1.0
    88 */
     9
     10/**
     11 * Registers a new icon collection.
     12 *
     13 * @since 7.1.0
     14 *
     15 * @param string $slug Icon collection slug.
     16 * @param array  $args {
     17 *     Arguments for registering an icon collection.
     18 *
     19 *     @type string $label       Required. A human-readable label for the icon collection.
     20 *     @type string $description Optional. A human-readable description for the icon collection.
     21 * }
     22 * @return bool True if the icon collection was registered successfully, else false.
     23 */
     24function wp_register_icon_collection( $slug, $args ) {
     25        return WP_Icon_Collections_Registry::get_instance()->register( $slug, $args );
     26}
     27
     28/**
     29 * Unregisters an icon collection.
     30 *
     31 * @since 7.1.0
     32 *
     33 * @param string $slug Icon collection slug.
     34 * @return bool True if the icon collection was unregistered successfully, else false.
     35 */
     36function wp_unregister_icon_collection( $slug ) {
     37        return WP_Icon_Collections_Registry::get_instance()->unregister( $slug );
     38}
     39
     40/**
     41 * Registers a new icon.
     42 *
     43 * @since 7.1.0
     44 *
     45 * @param string $icon_name Namespaced icon name in the form "collection/icon-name"
     46 *                          (e.g. "my-plugin/arrow-left"). The "core" collection is
     47 *                          reserved for WordPress core icons; third-party code should
     48 *                          register icons under its own collection rather than the
     49 *                          "core" collection.
     50 * @param array  $args {
     51 *     List of properties for the icon.
     52 *
     53 *     @type string $label     Required. A human-readable label for the icon.
     54 *     @type string $content   Optional. SVG markup for the icon.
     55 *                             If not provided, the content will be retrieved from the `file_path` if set.
     56 *                             If both `content` and `file_path` are not set, the icon will not be registered.
     57 *     @type string $file_path Optional. The full path to the file containing the icon content.
     58 * }
     59 * @return bool True if the icon was registered successfully, else false.
     60 */
     61function wp_register_icon( $icon_name, $args ) {
     62        return WP_Icons_Registry::get_instance()->register( $icon_name, $args );
     63}
     64
     65/**
     66 * Unregisters an icon.
     67 *
     68 * @since 7.1.0
     69 *
     70 * @param string $icon_name Namespaced icon name in the form "collection/icon-name"
     71 *                          (e.g. "core/arrow-left").
     72 * @return bool True if the icon was unregistered successfully, else false.
     73 */
     74function wp_unregister_icon( $icon_name ) {
     75        return WP_Icons_Registry::get_instance()->unregister( $icon_name );
     76}
     77
     78/**
     79 * Registers the default icon collections.
     80 *
     81 * @since 7.1.0
     82 * @access private
     83 */
     84function _wp_register_default_icon_collections() {
     85        wp_register_icon_collection(
     86                'core',
     87                array(
     88                        'label'       => __( 'WordPress' ),
     89                        'description' => __( 'Default icon collection.' ),
     90                )
     91        );
     92}
     93
     94/**
     95 * Registers the default core icons from the manifest.
     96 *
     97 * @since 7.1.0
     98 * @access private
     99 */
     100function _wp_register_default_icons() {
     101        $icons_directory = ABSPATH . WPINC . '/images/icon-library/';
     102        $manifest_path   = ABSPATH . WPINC . '/assets/icon-library-manifest.php';
     103
     104        if ( ! is_readable( $manifest_path ) ) {
     105                wp_trigger_error(
     106                        __FUNCTION__,
     107                        __( 'Core icon collection manifest is missing or unreadable.' )
     108                );
     109                return;
     110        }
     111
     112        $collection = include $manifest_path;
     113
     114        if ( empty( $collection ) ) {
     115                wp_trigger_error(
     116                        __FUNCTION__,
     117                        __( 'Core icon collection manifest is empty or invalid.' )
     118                );
     119                return;
     120        }
     121
     122        foreach ( $collection as $icon_name => $icon_data ) {
     123                if (
     124                        empty( $icon_data['filePath'] )
     125                        || ! is_string( $icon_data['filePath'] )
     126                ) {
     127                        _doing_it_wrong(
     128                                __FUNCTION__,
     129                                __( 'Core icon collection manifest must provide a valid "filePath" for each icon.' ),
     130                                '7.0.0'
     131                        );
     132                        return;
     133                }
     134
     135                wp_register_icon(
     136                        'core/' . $icon_name,
     137                        array(
     138                                'label'     => $icon_data['label'],
     139                                'file_path' => $icons_directory . $icon_data['filePath'],
     140                        )
     141                );
     142        }
     143}
    9144
    10145/**
  • trunk/src/wp-includes/rest-api.php

    r62553 r62748  
    430430        $icons_controller->register_routes();
    431431
     432        // Icon Collections.
     433        $icon_collections_controller = new WP_REST_Icon_Collections_Controller();
     434        $icon_collections_controller->register_routes();
     435
    432436        // View Config.
    433437        $view_config_controller = new WP_REST_View_Config_Controller();
  • trunk/src/wp-includes/rest-api/endpoints/class-wp-rest-icons-controller.php

    r62435 r62748  
    11<?php
     2
    23/**
    34 * REST API: WP_REST_Icons_Controller class
     
    1011/**
    1112 * Controller which provides a REST endpoint for the editor to read registered
    12  * icons. For the time being, only core icons are available, which are defined
    13  * in a single manifest file (wp-includes/assets/icon-library-manifest.php).
    14  * Icons are comprised of their SVG source, a name and a translatable label.
     13 * icons. Icons are grouped into collections (the default one being `core`).
    1514 *
    1615 * @since 7.0.0
     
    3433         *
    3534         * @since 7.0.0
     35         * @since 7.1.0 Added the `/icons/<collection>` collection-scoped route.
    3636         */
    3737        public function register_routes() {
     
    5252                register_rest_route(
    5353                        $this->namespace,
    54                         '/' . $this->rest_base . '/(?P<name>[a-z][a-z0-9-]*/[a-z][a-z0-9-]*)',
     54                        '/' . $this->rest_base . '/(?P<collection>[a-z0-9](?:[a-z0-9_-]*[a-z0-9])?)',
     55                        array(
     56                                'args'   => array(
     57                                        'collection' => array(
     58                                                'description' => __( 'Icon collection slug.' ),
     59                                                'type'        => 'string',
     60                                        ),
     61                                ),
     62                                array(
     63                                        'methods'             => WP_REST_Server::READABLE,
     64                                        'callback'            => array( $this, 'get_items' ),
     65                                        'permission_callback' => array( $this, 'get_items_permissions_check' ),
     66                                        'args'                => $this->get_collection_params(),
     67                                ),
     68                                'schema' => array( $this, 'get_public_item_schema' ),
     69                        )
     70                );
     71
     72                register_rest_route(
     73                        $this->namespace,
     74                        '/' . $this->rest_base . '/(?P<name>[a-z0-9](?:[a-z0-9_-]*[a-z0-9])?/[a-z0-9](?:[a-z0-9_-]*[a-z0-9])?)',
    5575                        array(
    5676                                'args'   => array(
     
    120140
    121141        /**
    122          * Retrieves all icons.
    123          *
    124          * @since 7.0.0
     142         * Retrieves all icons, optionally scoped to a collection.
     143         *
     144         * @since 7.0.0
     145         * @since 7.1.0 Supports filtering by collection.
    125146         *
    126147         * @param WP_REST_Request $request Full details about the request.
     
    128149         */
    129150        public function get_items( $request ) {
     151                $collection = $request->get_param( 'collection' );
     152
     153                if ( null !== $collection && ! WP_Icon_Collections_Registry::get_instance()->is_registered( $collection ) ) {
     154                        return new WP_Error(
     155                                'rest_icon_collection_not_found',
     156                                sprintf(
     157                                        /* translators: %s: Icon collection slug. */
     158                                        __( 'Icon collection not found: "%s".' ),
     159                                        $collection
     160                                ),
     161                                array( 'status' => 404 )
     162                        );
     163                }
     164
    130165                $response = array();
    131166                $search   = $request->get_param( 'search' );
    132167                $icons    = WP_Icons_Registry::get_instance()->get_registered_icons( $search );
     168
    133169                foreach ( $icons as $icon ) {
     170                        if ( null !== $collection && ( ! isset( $icon['collection'] ) || $icon['collection'] !== $collection ) ) {
     171                                continue;
     172                        }
    134173                        $prepared_icon = $this->prepare_item_for_response( $icon, $request );
    135174                        $response[]    = $this->prepare_response_for_collection( $prepared_icon );
     
    187226         *
    188227         * @since 7.0.0
     228         * @since 7.1.0 Added the `collection` field.
    189229         *
    190230         * @param array           $item    Raw icon as registered, before any changes.
     
    195235                $fields = $this->get_fields_for_response( $request );
    196236                $keys   = array(
    197                         'name'    => 'name',
    198                         'label'   => 'label',
    199                         'content' => 'content',
     237                        'name'       => 'name',
     238                        'label'      => 'label',
     239                        'content'    => 'content',
     240                        'collection' => 'collection',
    200241                );
    201242                $data   = array();
     
    216257         *
    217258         * @since 7.0.0
     259         * @since 7.1.0 Added the `collection` property.
    218260         *
    219261         * @return array Item schema data.
     
    229271                        'type'       => 'object',
    230272                        'properties' => array(
    231                                 'name'    => array(
     273                                'name'       => array(
    232274                                        'description' => __( 'The icon name.' ),
    233275                                        'type'        => 'string',
     
    235277                                        'context'     => array( 'view', 'edit', 'embed' ),
    236278                                ),
    237                                 'label'   => array(
     279                                'label'      => array(
    238280                                        'description' => __( 'The icon label.' ),
    239281                                        'type'        => 'string',
     
    241283                                        'context'     => array( 'view', 'edit', 'embed' ),
    242284                                ),
    243                                 'content' => array(
     285                                'content'    => array(
    244286                                        'description' => __( 'The icon content (SVG markup).' ),
    245287                                        'type'        => 'string',
     
    247289                                        'context'     => array( 'view', 'edit', 'embed' ),
    248290                                ),
     291                                'collection' => array(
     292                                        'description' => __( 'The slug of the collection this icon belongs to.' ),
     293                                        'type'        => 'string',
     294                                        'readonly'    => true,
     295                                        'context'     => array( 'view', 'edit', 'embed' ),
     296                                ),
    249297                        ),
    250298                );
     
    259307         *
    260308         * @since 7.0.0
     309         * @since 7.1.0 Added the `collection` parameter.
    261310         *
    262311         * @return array Collection parameters.
     
    265314                $query_params                       = parent::get_collection_params();
    266315                $query_params['context']['default'] = 'view';
     316                $query_params['collection']         = array(
     317                        'description' => __( 'Limit results to icons belonging to the given collection slug.' ),
     318                        'type'        => 'string',
     319                        'pattern'     => '^[a-z0-9]([a-z0-9_-]*[a-z0-9])?$',
     320                );
    267321                return $query_params;
    268322        }
  • trunk/src/wp-settings.php

    r62736 r62748  
    300300require ABSPATH . WPINC . '/class-wp-connector-registry.php';
    301301require ABSPATH . WPINC . '/connectors.php';
     302require ABSPATH . WPINC . '/class-wp-icon-collections-registry.php';
    302303require ABSPATH . WPINC . '/class-wp-icons-registry.php';
    303304require ABSPATH . WPINC . '/icons.php';
     
    362363require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-font-collections-controller.php';
    363364require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-icons-controller.php';
     365require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-icon-collections-controller.php';
    364366require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-view-config-controller.php';
    365367require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-abilities-v1-categories-controller.php';
  • trunk/tests/phpunit/includes/functions.php

    r61824 r62748  
    377377
    378378/**
     379 * After the init action has been run once, trying to re-register icon collections and icons
     380 * can cause errors. To avoid this, unhook the icon registration functions.
     381 *
     382 * @since 7.1.0
     383 */
     384function _unhook_icon_registration() {
     385        remove_action( 'init', '_wp_register_default_icon_collections', 0 );
     386        remove_action( 'init', '_wp_register_default_icons' );
     387}
     388tests_add_filter( 'init', '_unhook_icon_registration', 1000 );
     389
     390/**
    379391 * After the init action has been run once, trying to re-register connector settings can cause
    380392 * duplicate registrations. To avoid this, unhook the connector registration functions.
  • trunk/tests/phpunit/tests/icons/wpIconsRegistry.php

    r62540 r62748  
    11<?php
    22/**
    3  * Tests for WP_Icons_Registry::register().
     3 * Unit tests covering WP_Icons_Registry functionality.
    44 *
    55 * @package WordPress
    6  * @subpackage Icons
     6 * @since 7.1.0
    77 *
    88 * @group icons
    9  * @covers WP_Icons_Registry::register
    10  * @covers WP_Icons_Registry::is_registered
     9 *
     10 * @coversDefaultClass WP_Icons_Registry
    1111 */
    1212class Tests_Icons_WpIconsRegistry extends WP_UnitTestCase {
    1313
    1414        /**
    15          * Registry instance for testing.
    16          *
    1715         * @var WP_Icons_Registry
    1816         */
    19         private $registry;
     17        protected $registry;
     18
     19        /**
     20         * Path to a temporary icon file created during a test, removed in tear_down.
     21         *
     22         * @var string|null
     23         */
     24        private $temp_file = null;
    2025
    2126        public function set_up() {
    2227                parent::set_up();
    2328                $this->registry = WP_Icons_Registry::get_instance();
     29
     30                $collections = WP_Icon_Collections_Registry::get_instance();
     31                if ( ! $collections->is_registered( 'test-collection' ) ) {
     32                        $collections->register( 'test-collection', array( 'label' => 'Test Plugin' ) );
     33                }
    2434        }
    2535
    2636        public function tear_down() {
    27                 $instance_property = new ReflectionProperty( WP_Icons_Registry::class, 'instance' );
    28 
     37                $reflection        = new ReflectionClass( WP_Icons_Registry::class );
     38                $instance_property = $reflection->getProperty( 'instance' );
    2939                if ( PHP_VERSION_ID < 80100 ) {
    3040                        $instance_property->setAccessible( true );
    3141                }
    32 
    3342                $instance_property->setValue( null, null );
     43
     44                $collections = WP_Icon_Collections_Registry::get_instance();
     45                if ( $collections->is_registered( 'test-collection' ) ) {
     46                        $collections->unregister( 'test-collection' );
     47                }
     48                if ( $collections->is_registered( 'other-collection' ) ) {
     49                        $collections->unregister( 'other-collection' );
     50                }
     51
     52                if ( $this->temp_file && file_exists( $this->temp_file ) ) {
     53                        unlink( $this->temp_file );
     54                }
     55                $this->temp_file = null;
    3456
    3557                $this->registry = null;
     
    3860
    3961        /**
    40          * Invokes WP_Icons_Registry::register despite it being private
    41          *
    42          * @param string $icon_name       Icon name including namespace.
    43          * @param array  $icon_properties Icon properties (label, content, file_path).
    44          * @return bool True if the icon was registered successfully.
    45          */
    46         private function register( $icon_name, $icon_properties ) {
    47                 $method = new ReflectionMethod( $this->registry, 'register' );
    48 
    49                 if ( PHP_VERSION_ID < 80100 ) {
    50                         $method->setAccessible( true );
    51                 }
    52 
    53                 return $method->invoke( $this->registry, $icon_name, $icon_properties );
    54         }
    55 
    56         /**
    57          * Provides invalid icon names.
    58          *
    59          * @return array[]
    60          */
     62         * Builds a unique temporary icon file path with the given extension.
     63         *
     64         * @param string|null $contents  File contents, or null to leave the file uncreated.
     65         * @param string      $extension File extension, without the leading dot.
     66         * @return string Absolute path to the temporary file.
     67         */
     68        private function create_temp_icon_file( $contents, $extension = 'svg' ) {
     69                $dir             = get_temp_dir();
     70                $this->temp_file = trailingslashit( $dir ) . wp_unique_filename( $dir, uniqid() . '.' . $extension );
     71                if ( null !== $contents ) {
     72                        file_put_contents( $this->temp_file, $contents );
     73                }
     74                return $this->temp_file;
     75        }
     76
     77        /**
     78         * Provides valid namespaced icon names, including names that contain or
     79         * start with digits, as well as underscores.
     80         *
     81         * @return array<string, array{0: string}>
     82         */
     83        public function data_valid_icon_names() {
     84                return array(
     85                        'simple name'            => array( 'test-collection/my-icon' ),
     86                        'digit at the start'     => array( 'test-collection/1-icon' ),
     87                        'digit in the name'      => array( 'test-collection/my-1-icon' ),
     88                        'digit at the end'       => array( 'test-collection/icon1' ),
     89                        'underscore in the name' => array( 'test-collection/my_icon' ),
     90                        'underscore at the end'  => array( 'test-collection/my-icon_' ),
     91                        'hyphen at the end'      => array( 'test-collection/my-icon-' ),
     92                );
     93        }
     94
     95        /**
     96         * @ticket 64651
     97         *
     98         * @dataProvider data_valid_icon_names
     99         *
     100         * @covers ::register
     101         *
     102         * @param string $name Valid icon name candidate.
     103         */
     104        public function test_register_icon( $name ) {
     105                $result = $this->registry->register(
     106                        $name,
     107                        array(
     108                                'label'   => 'My Icon',
     109                                'content' => '<svg></svg>',
     110                        )
     111                );
     112
     113                $this->assertTrue( $result );
     114                $this->assertTrue( $this->registry->is_registered( $name ) );
     115        }
     116
    61117        public function data_invalid_icon_names() {
    62118                return array(
    63                         'non-string name'      => array( 1 ),
    64                         'no namespace'         => array( 'plus' ),
    65                         'uppercase characters' => array( 'Test/Plus' ),
    66                         'invalid characters'   => array( 'test/_doing_it_wrong' ),
    67                 );
    68         }
    69 
    70         /**
    71          * Should fail to re-register the same icon.
    72          *
    73          * @ticket 64847
     119                        'integer name'            => array( 1 ),
     120                        'null name'               => array( null ),
     121                        'boolean name'            => array( true ),
     122                        'array name'              => array( array() ),
     123                        'empty name'              => array( 'test-collection/' ),
     124                        'uppercase at the start'  => array( 'test-collection/Icon' ),
     125                        'uppercase in the name'   => array( 'test-collection/my-Icon' ),
     126                        'uppercase at the end'    => array( 'test-collection/my-iconX' ),
     127                        'underscore at the start' => array( 'test-collection/_my-icon' ),
     128                        'hyphen at the start'     => array( 'test-collection/-my-icon' ),
     129                );
     130        }
     131
     132        /**
     133         * @ticket 64651
     134         *
     135         * @covers ::register
    74136         *
    75137         * @expectedIncorrectUsage WP_Icons_Registry::register
    76138         */
    77139        public function test_register_icon_twice() {
    78                 $name     = 'test-plugin/duplicate';
    79140                $settings = array(
    80141                        'label'   => 'Icon',
     
    82143                );
    83144
    84                 $result = $this->register( $name, $settings );
     145                $this->assertTrue( $this->registry->register( 'test-collection/duplicate', $settings ) );
     146                $this->assertFalse( $this->registry->register( 'test-collection/duplicate', $settings ) );
     147        }
     148
     149        /**
     150         * @ticket 64651
     151         *
     152         * @dataProvider data_invalid_icon_names
     153         *
     154         * @covers ::register
     155         *
     156         * @expectedIncorrectUsage WP_Icons_Registry::register
     157         *
     158         * @param mixed $name Invalid icon name candidate.
     159         */
     160        public function test_register_invalid_name( $name ) {
     161                $result = $this->registry->register(
     162                        $name,
     163                        array(
     164                                'label'   => 'Icon',
     165                                'content' => '<svg></svg>',
     166                        )
     167                );
     168                $this->assertFalse( $result );
     169        }
     170
     171        /**
     172         * Should reject a non-namespaced name, since the collection is derived from
     173         * the namespaced icon name in the form "collection/icon-name".
     174         *
     175         * @ticket 64651
     176         *
     177         * @covers ::register
     178         *
     179         * @expectedIncorrectUsage WP_Icons_Registry::register
     180         */
     181        public function test_register_rejects_non_namespaced_name() {
     182                $result = $this->registry->register(
     183                        'non-namespaced-icon',
     184                        array(
     185                                'label'   => 'Icon',
     186                                'content' => '<svg></svg>',
     187                        )
     188                );
     189                $this->assertFalse( $result );
     190                $this->assertFalse( $this->registry->is_registered( 'core/non-namespaced-icon' ) );
     191                $this->assertFalse( $this->registry->is_registered( 'non-namespaced-icon' ) );
     192        }
     193
     194        /**
     195         * Should reject `collection` passed as an icon property, since the collection
     196         * is derived from the namespaced icon name instead.
     197         *
     198         * @ticket 64651
     199         *
     200         * @covers ::register
     201         *
     202         * @expectedIncorrectUsage WP_Icons_Registry::register
     203         */
     204        public function test_register_rejects_collection_property() {
     205                $result = $this->registry->register(
     206                        'test-collection/my-icon',
     207                        array(
     208                                'label'      => 'Icon',
     209                                'content'    => '<svg></svg>',
     210                                'collection' => 'test-collection',
     211                        )
     212                );
     213                $this->assertFalse( $result );
     214        }
     215
     216        /**
     217         * Should fail when the name references a collection that is not registered.
     218         *
     219         * @ticket 64651
     220         *
     221         * @covers ::register
     222         *
     223         * @expectedIncorrectUsage WP_Icons_Registry::register
     224         */
     225        public function test_register_rejects_unregistered_collection() {
     226                $result = $this->registry->register(
     227                        'unregistered-collection/my-icon',
     228                        array(
     229                                'label'   => 'Icon',
     230                                'content' => '<svg></svg>',
     231                        )
     232                );
     233                $this->assertFalse( $result );
     234        }
     235
     236        /**
     237         * Should register an icon that provides its content through `file_path`.
     238         *
     239         * @ticket 64847
     240         *
     241         * @covers ::register
     242         */
     243        public function test_register_icon_with_file_path() {
     244                $path = $this->create_temp_icon_file( '<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"></svg>' );
     245
     246                $result = $this->registry->register(
     247                        'test-collection/file-path-icon',
     248                        array(
     249                                'label'     => 'Icon',
     250                                'file_path' => $path,
     251                        )
     252                );
     253
    85254                $this->assertTrue( $result );
    86                 $result2 = $this->register( $name, $settings );
    87                 $this->assertFalse( $result2 );
    88         }
    89 
    90 
    91         /**
    92          * Should fail to register icon with invalid names.
     255                $this->assertTrue( $this->registry->is_registered( 'test-collection/file-path-icon' ) );
     256
     257                $icon = $this->registry->get_registered_icon( 'test-collection/file-path-icon' );
     258                $this->assertStringContainsString( '<svg', $icon['content'] );
     259        }
     260
     261        /**
     262         * Should fail to register an icon that provides both `content` and `file_path`.
    93263         *
    94264         * @ticket 64847
    95265         *
    96          * @dataProvider data_invalid_icon_names
    97          * @expectedIncorrectUsage WP_Icons_Registry::register
    98          *
    99          * @param mixed $icon_name Icon name to register.
    100          */
    101         public function test_register_invalid_name( $icon_name ) {
    102                 $settings = array(
    103                         'label'   => 'Icon',
    104                         'content' => '<svg></svg>',
    105                 );
    106 
    107                 $result = $this->register( $icon_name, $settings );
    108                 $this->assertFalse( $result );
    109         }
    110 
    111         /**
    112          * Should register an icon that provides its content through `file_path`.
     266         * @covers ::register
     267         *
     268         * @expectedIncorrectUsage WP_Icons_Registry::register
     269         */
     270        public function test_register_icon_with_content_and_file_path() {
     271                $result = $this->registry->register(
     272                        'test-collection/content-and-file-path',
     273                        array(
     274                                'label'     => 'Icon',
     275                                'content'   => '<svg></svg>',
     276                                'file_path' => '/path/to/icon.svg',
     277                        )
     278                );
     279                $this->assertFalse( $result );
     280                $this->assertFalse( $this->registry->is_registered( 'test-collection/content-and-file-path' ) );
     281        }
     282
     283        /**
     284         * Should fail to register an icon that provides neither `content` nor `file_path`.
    113285         *
    114286         * @ticket 64847
    115287         *
    116288         * @covers ::register
    117          */
    118         public function test_register_icon_with_file_path() {
    119                 $file_path = tempnam( get_temp_dir(), 'wp-icon-' );
    120                 file_put_contents( $file_path, '<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"></svg>' );
    121 
    122                 $name     = 'test-plugin/file-path-icon';
    123                 $settings = array(
    124                         'label'     => 'Icon',
    125                         'file_path' => $file_path,
    126                 );
    127 
    128                 $result = $this->register( $name, $settings );
    129                 $this->assertTrue( $result );
    130                 $this->assertTrue( $this->registry->is_registered( $name ) );
    131 
    132                 $registered_icons = $this->registry->get_registered_icons( $name );
    133                 $this->assertCount( 1, $registered_icons );
    134                 $this->assertStringContainsString( '<svg', $registered_icons[0]['content'] );
    135 
    136                 unlink( $file_path );
    137         }
    138 
    139         /**
    140          * Should fail to register an icon that provides both `content` and `file_path`.
    141          *
    142          * @ticket 64847
    143          *
    144          * @covers ::register
    145          *
    146          * @expectedIncorrectUsage WP_Icons_Registry::register
    147          */
    148         public function test_register_icon_with_content_and_file_path() {
    149                 $name     = 'test-plugin/content-and-file-path';
    150                 $settings = array(
    151                         'label'     => 'Icon',
    152                         'content'   => '<svg></svg>',
    153                         'file_path' => '/path/to/icon.svg',
    154                 );
    155 
    156                 $result = $this->register( $name, $settings );
    157                 $this->assertFalse( $result );
    158                 $this->assertFalse( $this->registry->is_registered( $name ) );
    159         }
    160 
    161         /**
    162          * Should fail to register an icon that provides neither `content` nor `file_path`.
    163          *
    164          * @ticket 64847
    165          *
    166          * @covers ::register
    167289         *
    168290         * @expectedIncorrectUsage WP_Icons_Registry::register
    169291         */
    170292        public function test_register_icon_without_content_or_file_path() {
    171                 $name     = 'test-plugin/no-content';
    172                 $settings = array(
    173                         'label' => 'Icon',
    174                 );
    175 
    176                 $result = $this->register( $name, $settings );
    177                 $this->assertFalse( $result );
    178                 $this->assertFalse( $this->registry->is_registered( $name ) );
     293                $result = $this->registry->register(
     294                        'test-collection/no-content',
     295                        array(
     296                                'label' => 'Icon',
     297                        )
     298                );
     299                $this->assertFalse( $result );
     300                $this->assertFalse( $this->registry->is_registered( 'test-collection/no-content' ) );
     301        }
     302
     303        /**
     304         * @ticket 64651
     305         *
     306         * @covers ::register
     307         */
     308        public function test_same_name_across_collections_does_not_collide() {
     309                $collections = WP_Icon_Collections_Registry::get_instance();
     310                $collections->register( 'other-collection', array( 'label' => 'Other' ) );
     311
     312                $this->assertTrue(
     313                        $this->registry->register(
     314                                'test-collection/shared',
     315                                array(
     316                                        'label'   => 'Shared A',
     317                                        'content' => '<svg></svg>',
     318                                )
     319                        )
     320                );
     321                $this->assertTrue(
     322                        $this->registry->register(
     323                                'other-collection/shared',
     324                                array(
     325                                        'label'   => 'Shared B',
     326                                        'content' => '<svg></svg>',
     327                                )
     328                        )
     329                );
     330
     331                $this->assertTrue( $this->registry->is_registered( 'test-collection/shared' ) );
     332                $this->assertTrue( $this->registry->is_registered( 'other-collection/shared' ) );
     333
     334                $icon_a = $this->registry->get_registered_icon( 'test-collection/shared' );
     335                $icon_b = $this->registry->get_registered_icon( 'other-collection/shared' );
     336                $this->assertSame( 'Shared A', $icon_a['label'] );
     337                $this->assertSame( 'Shared B', $icon_b['label'] );
     338        }
     339
     340        /**
     341         * @ticket 64651
     342         *
     343         * @covers ::unregister
     344         */
     345        public function test_unregister_icon() {
     346                $this->registry->register(
     347                        'test-collection/my-icon',
     348                        array(
     349                                'label'   => 'Icon',
     350                                'content' => '<svg></svg>',
     351                        )
     352                );
     353
     354                $this->assertTrue( $this->registry->is_registered( 'test-collection/my-icon' ) );
     355                $this->assertTrue( $this->registry->unregister( 'test-collection/my-icon' ) );
     356                $this->assertFalse( $this->registry->is_registered( 'test-collection/my-icon' ) );
     357        }
     358
     359        /**
     360         * @ticket 64651
     361         *
     362         * @covers ::unregister
     363         *
     364         * @expectedIncorrectUsage WP_Icons_Registry::unregister
     365         */
     366        public function test_unregister_unknown_icon() {
     367                $this->assertFalse( $this->registry->unregister( 'test-collection/ghost' ) );
     368        }
     369
     370        /**
     371         * @ticket 64651
     372         *
     373         * @covers ::get_content
     374         */
     375        public function test_get_content_reads_from_valid_file_path() {
     376                $path = $this->create_temp_icon_file( '<svg><path d="M0 0"/></svg>' );
     377
     378                $this->registry->register(
     379                        'test-collection/from-file',
     380                        array(
     381                                'label'     => 'From File',
     382                                'file_path' => $path,
     383                        )
     384                );
     385
     386                $icon = $this->registry->get_registered_icon( 'test-collection/from-file' );
     387                $this->assertStringContainsString( '<path', $icon['content'] );
     388        }
     389
     390        /**
     391         * Provides icon files that cannot yield valid content.
     392         *
     393         * @return array<string, array{0: string|null, 1: string}> Data sets of [ $contents, $extension ].
     394         */
     395        public function data_invalid_icon_files() {
     396                return array(
     397                        'missing file'        => array( null, 'svg' ),
     398                        'non-svg extension'   => array( '<svg><path d="M0 0"/></svg>', 'txt' ),
     399                        'invalid svg content' => array( '', 'svg' ),
     400                );
     401        }
     402
     403        /**
     404         * @ticket 64651
     405         *
     406         * @dataProvider data_invalid_icon_files
     407         *
     408         * @covers ::get_content
     409         *
     410         * @param string|null $contents  File contents, or null to leave the file uncreated.
     411         * @param string      $extension File extension, without the leading dot.
     412         */
     413        public function test_get_content_returns_null_for_invalid_file( $contents, $extension ) {
     414                $path = $this->create_temp_icon_file( $contents, $extension );
     415
     416                $this->registry->register(
     417                        'test-collection/invalid-file',
     418                        array(
     419                                'label'     => 'Invalid File',
     420                                'file_path' => $path,
     421                        )
     422                );
     423
     424                add_filter( 'wp_trigger_error_trigger_error', '__return_false' );
     425                $icon = $this->registry->get_registered_icon( 'test-collection/invalid-file' );
     426                remove_filter( 'wp_trigger_error_trigger_error', '__return_false' );
     427
     428                $this->assertNull( $icon['content'] );
    179429        }
    180430}
  • trunk/tests/phpunit/tests/icons/wpRestIconsController.php

    r62551 r62748  
    3232        }
    3333
     34        public function set_up() {
     35                parent::set_up();
     36
     37                /*
     38                 * Other suites reset the `WP_Icons_Registry` singleton, wiping the core icons that
     39                 * `init` only registers once. Re-register them when empty so order-dependent tests pass.
     40                 */
     41                if ( ! WP_Icon_Collections_Registry::get_instance()->is_registered( 'core' ) ) {
     42                        _wp_register_default_icon_collections();
     43                }
     44                if ( empty( WP_Icons_Registry::get_instance()->get_registered_icons() ) ) {
     45                        _wp_register_default_icons();
     46                }
     47        }
     48
    3449        /**
    3550         * @ticket 64651
     
    4055                $routes = rest_get_server()->get_routes();
    4156                $this->assertArrayHasKey( '/wp/v2/icons', $routes );
    42                 $this->assertArrayHasKey( '/wp/v2/icons/(?P<name>[a-z][a-z0-9-]*/[a-z][a-z0-9-]*)', $routes );
     57                $this->assertArrayHasKey( '/wp/v2/icons/(?P<collection>[a-z0-9](?:[a-z0-9_-]*[a-z0-9])?)', $routes );
     58                $this->assertArrayHasKey( '/wp/v2/icons/(?P<name>[a-z0-9](?:[a-z0-9_-]*[a-z0-9])?/[a-z0-9](?:[a-z0-9_-]*[a-z0-9])?)', $routes );
     59        }
     60
     61        /**
     62         * @ticket 64651
     63         *
     64         * @covers WP_REST_Icons_Controller::get_items
     65         */
     66        public function test_get_items_collection_scope() {
     67                wp_register_icon_collection( 'rest-test-collection', array( 'label' => 'REST Test' ) );
     68                wp_register_icon(
     69                        'rest-test-collection/bell',
     70                        array(
     71                                'label'   => 'Bell',
     72                                'content' => '<svg></svg>',
     73                        )
     74                );
     75
     76                wp_set_current_user( self::$editor_id );
     77
     78                $request  = new WP_REST_Request( 'GET', '/wp/v2/icons/rest-test-collection' );
     79                $response = rest_get_server()->dispatch( $request );
     80                $data     = $response->get_data();
     81
     82                $this->assertSame( 200, $response->get_status() );
     83                $this->assertIsArray( $data );
     84
     85                $names = array_column( $data, 'name' );
     86                $this->assertContains( 'rest-test-collection/bell', $names );
     87                foreach ( $data as $icon ) {
     88                        $this->assertSame( 'rest-test-collection', $icon['collection'] );
     89                }
     90
     91                wp_unregister_icon_collection( 'rest-test-collection' );
     92        }
     93
     94        /**
     95         * @ticket 64651
     96         *
     97         * @covers WP_REST_Icons_Controller::get_items
     98         */
     99        public function test_get_items_unknown_collection_returns_404() {
     100                wp_set_current_user( self::$editor_id );
     101
     102                $request  = new WP_REST_Request( 'GET', '/wp/v2/icons/unknown-collection' );
     103                $response = rest_get_server()->dispatch( $request );
     104
     105                $this->assertErrorResponse( 'rest_icon_collection_not_found', $response, 404 );
     106        }
     107
     108        /**
     109         * @ticket 64651
     110         *
     111         * @covers WP_REST_Icons_Controller::prepare_item_for_response
     112         */
     113        public function test_response_includes_collection_field() {
     114                wp_register_icon_collection( 'rest-test-collection', array( 'label' => 'REST Test' ) );
     115                wp_register_icon(
     116                        'rest-test-collection/bell',
     117                        array(
     118                                'label'   => 'Bell',
     119                                'content' => '<svg></svg>',
     120                        )
     121                );
     122
     123                wp_set_current_user( self::$editor_id );
     124
     125                $request  = new WP_REST_Request( 'GET', '/wp/v2/icons/rest-test-collection/bell' );
     126                $response = rest_get_server()->dispatch( $request );
     127                $data     = $response->get_data();
     128
     129                $this->assertSame( 200, $response->get_status() );
     130                $this->assertArrayHasKey( 'collection', $data );
     131                $this->assertSame( 'rest-test-collection', $data['collection'] );
     132                $this->assertSame( 'rest-test-collection/bell', $data['name'] );
     133
     134                wp_unregister_icon_collection( 'rest-test-collection' );
    43135        }
    44136
     
    123215         */
    124216        public function test_prepare_item() {
    125                 $this->markTestSkipped( 'No public icons are available in manifest.php yet' );
    126217                wp_set_current_user( self::$editor_id );
    127218
     
    156247         */
    157248        public function test_get_items_returns_icons_list() {
    158                 $this->markTestSkipped( 'No public icons are available in manifest.php yet' );
    159249                wp_set_current_user( self::$editor_id );
    160250
     
    224314         */
    225315        public function test_get_item_returns_specific_icon() {
    226                 $this->markTestSkipped( 'No public icons are available in manifest.php yet' );
    227316                wp_set_current_user( self::$editor_id );
    228317
     
    276365         */
    277366        public function test_get_items_search_filters_results() {
    278                 $this->markTestSkipped( 'No public icons are available in manifest.php yet' );
    279367                wp_set_current_user( self::$editor_id );
    280368
     
    394482         */
    395483        public function test_get_item_requires_permissions() {
    396                 $this->markTestSkipped( 'No public icons are available in manifest.php yet' );
    397484                // Get a valid icon name first with proper permissions
    398485                wp_set_current_user( self::$editor_id );
  • trunk/tests/phpunit/tests/rest-api/rest-schema-setup.php

    r62547 r62748  
    201201                        '/wp/v2/font-families/(?P<font_family_id>[\d]+)/font-faces/(?P<id>[\d]+)',
    202202                        '/wp/v2/font-families/(?P<id>[\d]+)',
     203                        '/wp/v2/icon-collections',
     204                        '/wp/v2/icon-collections/(?P<slug>[a-z0-9](?:[a-z0-9_-]*[a-z0-9])?)',
    203205                        '/wp/v2/icons',
    204                         '/wp/v2/icons/(?P<name>[a-z][a-z0-9-]*/[a-z][a-z0-9-]*)',
     206                        '/wp/v2/icons/(?P<collection>[a-z0-9](?:[a-z0-9_-]*[a-z0-9])?)',
     207                        '/wp/v2/icons/(?P<name>[a-z0-9](?:[a-z0-9_-]*[a-z0-9])?/[a-z0-9](?:[a-z0-9_-]*[a-z0-9])?)',
    205208                        '/wp/v2/view-config',
    206209                        '/wp-abilities/v1',
  • trunk/tests/qunit/fixtures/wp-api-generated.js

    r62659 r62748  
    1281112811                            "type": "string",
    1281212812                            "required": false
     12813                        },
     12814                        "collection": {
     12815                            "description": "Limit results to icons belonging to the given collection slug.",
     12816                            "type": "string",
     12817                            "pattern": "^[a-z0-9]([a-z0-9_-]*[a-z0-9])?$",
     12818                            "required": false
    1281312819                        }
    1281412820                    }
     
    1282312829            }
    1282412830        },
    12825         "/wp/v2/icons/(?P<name>[a-z][a-z0-9-]*/[a-z][a-z0-9-]*)": {
     12831        "/wp/v2/icons/(?P<collection>[a-z0-9](?:[a-z0-9_-]*[a-z0-9])?)": {
    1282612832            "namespace": "wp/v2",
    1282712833            "methods": [
     
    1283412840                    ],
    1283512841                    "args": {
     12842                        "collection": {
     12843                            "description": "Limit results to icons belonging to the given collection slug.",
     12844                            "type": "string",
     12845                            "pattern": "^[a-z0-9]([a-z0-9_-]*[a-z0-9])?$",
     12846                            "required": false
     12847                        },
     12848                        "context": {
     12849                            "description": "Scope under which the request is made; determines fields present in response.",
     12850                            "type": "string",
     12851                            "enum": [
     12852                                "view",
     12853                                "embed",
     12854                                "edit"
     12855                            ],
     12856                            "default": "view",
     12857                            "required": false
     12858                        },
     12859                        "page": {
     12860                            "description": "Current page of the collection.",
     12861                            "type": "integer",
     12862                            "default": 1,
     12863                            "minimum": 1,
     12864                            "required": false
     12865                        },
     12866                        "per_page": {
     12867                            "description": "Maximum number of items to be returned in result set.",
     12868                            "type": "integer",
     12869                            "default": 10,
     12870                            "minimum": 1,
     12871                            "maximum": 100,
     12872                            "required": false
     12873                        },
     12874                        "search": {
     12875                            "description": "Limit results to those matching a string.",
     12876                            "type": "string",
     12877                            "required": false
     12878                        }
     12879                    }
     12880                }
     12881            ]
     12882        },
     12883        "/wp/v2/icons/(?P<name>[a-z0-9](?:[a-z0-9_-]*[a-z0-9])?/[a-z0-9](?:[a-z0-9_-]*[a-z0-9])?)": {
     12884            "namespace": "wp/v2",
     12885            "methods": [
     12886                "GET"
     12887            ],
     12888            "endpoints": [
     12889                {
     12890                    "methods": [
     12891                        "GET"
     12892                    ],
     12893                    "args": {
    1283612894                        "name": {
    1283712895                            "description": "Icon name.",
     12896                            "type": "string",
     12897                            "required": false
     12898                        },
     12899                        "context": {
     12900                            "description": "Scope under which the request is made; determines fields present in response.",
     12901                            "type": "string",
     12902                            "enum": [
     12903                                "view",
     12904                                "embed",
     12905                                "edit"
     12906                            ],
     12907                            "default": "view",
     12908                            "required": false
     12909                        }
     12910                    }
     12911                }
     12912            ]
     12913        },
     12914        "/wp/v2/icon-collections": {
     12915            "namespace": "wp/v2",
     12916            "methods": [
     12917                "GET"
     12918            ],
     12919            "endpoints": [
     12920                {
     12921                    "methods": [
     12922                        "GET"
     12923                    ],
     12924                    "args": {
     12925                        "context": {
     12926                            "description": "Scope under which the request is made; determines fields present in response.",
     12927                            "type": "string",
     12928                            "enum": [
     12929                                "view",
     12930                                "embed",
     12931                                "edit"
     12932                            ],
     12933                            "default": "view",
     12934                            "required": false
     12935                        },
     12936                        "page": {
     12937                            "description": "Current page of the collection.",
     12938                            "type": "integer",
     12939                            "default": 1,
     12940                            "minimum": 1,
     12941                            "required": false
     12942                        },
     12943                        "per_page": {
     12944                            "description": "Maximum number of items to be returned in result set.",
     12945                            "type": "integer",
     12946                            "default": 10,
     12947                            "minimum": 1,
     12948                            "maximum": 100,
     12949                            "required": false
     12950                        },
     12951                        "search": {
     12952                            "description": "Limit results to those matching a string.",
     12953                            "type": "string",
     12954                            "required": false
     12955                        }
     12956                    }
     12957                }
     12958            ],
     12959            "_links": {
     12960                "self": [
     12961                    {
     12962                        "href": "http://example.org/index.php?rest_route=/wp/v2/icon-collections"
     12963                    }
     12964                ]
     12965            }
     12966        },
     12967        "/wp/v2/icon-collections/(?P<slug>[a-z0-9](?:[a-z0-9_-]*[a-z0-9])?)": {
     12968            "namespace": "wp/v2",
     12969            "methods": [
     12970                "GET"
     12971            ],
     12972            "endpoints": [
     12973                {
     12974                    "methods": [
     12975                        "GET"
     12976                    ],
     12977                    "args": {
     12978                        "slug": {
     12979                            "description": "Icon collection slug.",
    1283812980                            "type": "string",
    1283912981                            "required": false
Note: See TracChangeset for help on using the changeset viewer.