Icons: Add APIs for collection and icon registration#77260
Conversation
There was a problem hiding this comment.
Pull request overview
This PR adds a collection-based registration layer to the Icons API, enabling plugins/themes to register their own SVG icon collections and icons, and extends the REST API to query icons by collection.
Changes:
- Introduces an icon collections registry and public wrapper functions for registering/unregistering icon collections and icons.
- Refactors
WP_Icons_Registry_Gutenbergto require acollectionfor icon registration and to qualify stored icon names as{collection}/{icon}. - Extends the icons REST controller with a collection-scoped listing route and updates the Icon block to request all icons when opening the inserter.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 10 comments.
Show a summary per file
| File | Description |
|---|---|
| phpunit/experimental/class-wp-icons-registry-gutenberg-test.php | Updates/extends registry tests for collection-aware registration behavior. |
| packages/block-library/src/icon/edit.js | Changes icon list fetching parameters when inserter is open. |
| lib/load.php | Loads new 7.1 compat files for collections + icons API wrappers. |
| lib/compat/wordpress-7.1/icons.php | Adds public wrapper functions and default collection/icon registration hooks. |
| lib/compat/wordpress-7.1/class-wp-icon-collections-registry.php | New singleton registry for icon collections with basic CRUD. |
| lib/class-wp-rest-icons-controller-gutenberg.php | Adds collection-scoped icons route and includes collection in REST schema/response. |
| lib/class-wp-icons-registry-gutenberg.php | Refactors registration to be collection-based and adds unregister(). |
Comments suppressed due to low confidence (1)
phpunit/experimental/class-wp-icons-registry-gutenberg-test.php:57
- The helper comment says it invokes
register"despite it being private", butWP_Icons_Registry_Gutenberg::register()is now public. Either update the comment (and consider calling the method directly instead of using reflection) to keep the test intent clear.
/**
* Invokes WP_Icons_Registry_Gutenberg::register despite it being private
*
* @param string $icon_name Icon name (without namespace prefix).
* @param array $icon_properties Icon properties (label, content, filePath, collection).
* @return bool True if the icon was registered successfully.
*/
private function register( $icon_name, $icon_properties ) {
$method = new ReflectionMethod( $this->registry, 'register' );
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| * Registers a new icon. | ||
| * | ||
| * @param string $icon_name Icon name including namespace. | ||
| * @param array $args { | ||
| * List of properties for the icon. | ||
| * | ||
| * @type string $label Required. A human-readable label for the icon. | ||
| * @type string $collection Required. The slug of a registered icon collection that this icon belongs to. | ||
| * @type string $content Optional. SVG markup for the icon. | ||
| * If not provided, the content will be retrieved from the `filePath` if set. | ||
| * If both `content` and `filePath` are not set, the icon will not be registered. | ||
| * @type string $filePath Optional. The full path to the file containing the icon content. | ||
| * } | ||
| * @return bool True if the icon was registered successfully, else false. | ||
| */ | ||
| function wp_register_icon( $icon_name, $args ) { | ||
| return WP_Icons_Registry::get_instance()->register( $icon_name, $args ); | ||
| } |
There was a problem hiding this comment.
The wp_register_icon() docblock says $icon_name includes a namespace, but the exposed API and the registry implementation now expect an unqualified icon slug (with the collection provided in $args['collection']). Update the param docs to match the actual accepted format to avoid consumers passing collection/icon and getting _doing_it_wrong failures.
There was a problem hiding this comment.
Why not keep registration the same and just require collection from $args? The wp_unregister_icon can remain the same as it is now.
There was a problem hiding this comment.
Fixed in 69a5551
Furthermore, based on #77260 (comment), I have made the parameter optional.
There was a problem hiding this comment.
Furthermore, based on #77260 (comment), I have made the parameter optional.
So it would default to core if collection is omitted?
There was a problem hiding this comment.
Yes. I don't have a strong opinion on whether the collection should be a required parameter. What do you think?
There was a problem hiding this comment.
As long as intent and results are documented, I also don't have a strong opinion here.
What happens if I re-register the star icon with the default collection? Will the core icon be replaced, or do I get a "doing it wrong" warning?
There was a problem hiding this comment.
We get a "doing it wrong" warning.
register_block_type outputs "doing it wrong", but register_block_pattern has its pattern replaced. I'm a little unsure about which pattern to follow for the icon registration.
|
Size Change: +9 B (0%) Total Size: 7.82 MB 📦 View Changed
ℹ️ View Unchanged
|
| * Arguments for registering an icon collection. | ||
| * | ||
| * @type string $label Required. A human-readable label for the icon collection. | ||
| * @type string $description Optional. A human-readable description for the icon collection. |
There was a problem hiding this comment.
I haven't decided yet whether to visually display the collection description, but it probably won't cause any problems if it's included.
Expose public APIs for registering third-party SVG icons by grouping them
into collections. Every icon is associated with a single collection
(defaulting to `core`), and icons are uniquely identified by
`{collection-slug}/{icon-slug}`. Unregistering a collection cascades to
all icons within it, and the same icon slug may coexist across different
collections.
New `WP_Icon_Collections_Registry` singleton stores collections.
`WP_Icons_Registry::register()` becomes public, requires a `collection`
property, and gains a matching `unregister()` method. Wrapper functions
`wp_register_icon_collection()`, `wp_unregister_icon_collection()`,
`wp_register_icon()`, and `wp_unregister_icon()` are introduced, and the
default `core` collection plus bundled icons are registered on `init`.
The REST controller gains a `/wp/v2/icons/<namespace>` route for
collection-scoped listings and exposes a `collection` field in responses.
Ports the equivalent functionality from the Gutenberg plugin
(WordPress/gutenberg#77260) to Core, along with covering unit tests.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message. To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
|
Flaky tests detected in 306ac80. 🔍 Workflow run URL: https://github.com/WordPress/gutenberg/actions/runs/28235261307
|
Introduce a singleton registry class that lets plugins register icon collections with a label, description, and categories. This provides the foundation for a `wp_register_icon_collection()` wrapper and for grouping icons in the editor UI. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Expose wp_register_icon_collection() / wp_unregister_icon_collection() as the public API for plugins, and register a default 'wordpress' collection on init so the registry is populated out of the box. Wire the new files into lib/load.php so they run under the WP 7.1 compat layer. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Ensures every icon belongs to a registered collection so the collections registry can be relied on as the source of truth. Default icon collection registration runs at init priority 0 so collections exist before the Gutenberg registry override replays registered icons. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…hooks Moves core icon registration out of the registry constructor into a `gutenberg_register_icons` action and registers default collections via `gutenberg_register_icon_collections`. Both hooks remove the matching core actions (`_wp_register_default_icons` / `_wp_register_default_icon_collections`) when present, so the Gutenberg plugin owns registration end-to-end and stays in sync with future core registration hooks without double-registering. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Exposes a public registration API on top of the icons registry by widening `register` visibility and adding a matching `unregister` method on `WP_Icons_Registry_Gutenberg`. This lets plugins register icons without reaching into reflection and lets the Gutenberg registration paths call the public API directly. `gutenberg_register_icons` runs at the default priority so the registry override at priority 1 has already replaced the core singleton, allowing the wrapper to resolve the Gutenberg instance through `WP_Icons_Registry::get_instance()`. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Removes the 'categories' property from the collection registration API and its validation. Category support adds a second axis of grouping on top of collections and is best introduced as a follow-up once the base collection/icon registration API has settled, rather than landing both at once. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Allow clients to request icons limited to a specific registered collection via /wp/v2/icons?collection=<slug>. Without this, fetching icons for a given collection would require downloading all registered icons and filtering on the client, which scales poorly once large third-party collections are registered. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
# Conflicts: # lib/class-wp-icons-registry-gutenberg.php # lib/compat/wordpress-7.0/class-wp-icons-registry.php # phpunit/experimental/class-wp-icons-registry-gutenberg-test.php
The trunk merge pulled in file_path registration tests that used an unregistered 'test-plugin' collection and a non-.svg temp file. After the icons API refactor, register() rejects unregistered collections and get_content() requires a .svg extension, so these tests failed. Use the registered 'test-collection' and the create_temp_icon_file helper. Co-Authored-By: Claude <noreply@anthropic.com>
|
What is missing / necessary to move this one forward? |
|
I believe this PR has been approved and is ready for release, but I have one concern. If custom icons are registered through this new API, they will all become selectable in the Icon block. What if consumers want to centrally manage their own icon sets through this API but do not want to expose them in the icon block? If we ship this PR, I believe we need to consider this point simultaneously. I am thinking that we may need to implement some kind of parameters, for example, as follows. // Only icons with "show_in_rest" set to true will be available in the Icon block.
wp_register_icon( 'my-icons/star', array(
'label' => 'Star',
'content' => '<svg/></svg>',
'show_in_rest' _> true,
) );
// Icons other than these are not exposed to the REST API, but can be retrieved using `wp_get_icon()`.
wp_register_icon( 'my-icons/cog', array(
'label' => 'Cog',
'content' => '<svg/></svg>',
) );I believe this option would be beneficial for us as well. The |
|
If we want to hide an icon from the Icon block, then We could add a very specific |
I feel like these specific declarations could be done in an iteration. I don't see why they would block the first version of icon registration API. On the contrary - if we open the icon registration API we would get feedback from developers about what additional registration flags they're missing. |
|
Thanks for the feedback! So, shall I smoke test this PR again and if there are no issues, we can ship it? |
|
Yes, I'm personally fine with iterating separately with these flags. @mcsf WDYT? |
Fine with me too! I'd like us to have a good look at flags well before 7.1, but — yes — it's fine to iterate. :) |
The trunk merge left two require statements for the WordPress 7.1 icons.php compat file: one inside the WP_REST_Controller block and one at the top level. Because gutenberg_register_default_icon_collections() is not guarded by function_exists(), loading the file twice triggered a fatal "Cannot redeclare" error. Keep the unconditional top-level require (matching trunk) and drop the duplicate inside the REST block. Co-Authored-By: Claude <noreply@anthropic.com>
I'm considering what name would be best, but icons aren't just used in the Icon block. The icon picker might be introduced in the future to change icons in the Navigation block, Details block, and so on. With that in mind, Some of my ideas:
|
|
It's tricky...
|
There should be the |
* Icons: Add WP_Icon_Collections_Registry for icon collection registration
Introduce a singleton registry class that lets plugins register icon
collections with a label, description, and categories. This provides the
foundation for a `wp_register_icon_collection()` wrapper and for grouping
icons in the editor UI.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Icons: Add wrapper functions and default icon collection registration
Expose wp_register_icon_collection() / wp_unregister_icon_collection()
as the public API for plugins, and register a default 'wordpress'
collection on init so the registry is populated out of the box. Wire
the new files into lib/load.php so they run under the WP 7.1 compat
layer.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Icons: Require collection when registering icons in Gutenberg registry
Ensures every icon belongs to a registered collection so the collections
registry can be relied on as the source of truth. Default icon collection
registration runs at init priority 0 so collections exist before the
Gutenberg registry override replays registered icons.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Icons: Register default icons and collections via Gutenberg-specific hooks
Moves core icon registration out of the registry constructor into a
`gutenberg_register_icons` action and registers default collections via
`gutenberg_register_icon_collections`. Both hooks remove the matching
core actions (`_wp_register_default_icons` /
`_wp_register_default_icon_collections`) when present, so the Gutenberg
plugin owns registration end-to-end and stays in sync with future core
registration hooks without double-registering.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Icons: Add wp_register_icon / wp_unregister_icon public API
Exposes a public registration API on top of the icons registry by
widening `register` visibility and adding a matching `unregister` method
on `WP_Icons_Registry_Gutenberg`. This lets plugins register icons
without reaching into reflection and lets the Gutenberg registration
paths call the public API directly. `gutenberg_register_icons` runs at
the default priority so the registry override at priority 1 has already
replaced the core singleton, allowing the wrapper to resolve the
Gutenberg instance through `WP_Icons_Registry::get_instance()`.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Icons: Drop category support from icon collections
Removes the 'categories' property from the collection registration API
and its validation. Category support adds a second axis of grouping on
top of collections and is best introduced as a follow-up once the base
collection/icon registration API has settled, rather than landing both
at once.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Icons: Filter REST icons endpoint by collection slug
Allow clients to request icons limited to a specific registered collection
via /wp/v2/icons?collection=<slug>. Without this, fetching icons for a given
collection would require downloading all registered icons and filtering on
the client, which scales poorly once large third-party collections are
registered.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Icons: Add collection-scoped REST route and unify default slug to "core"
Exposes `/wp/v2/icons/<namespace>` alongside the existing list and
single-item routes, mirroring the hierarchical URL style used by
block-types. The same `get_items` handler serves both the global list
and the collection-scoped listing via the URL-captured `namespace`
parameter, which is also formally declared in `get_collection_params`.
The default icon collection slug is renamed from `wordpress` to `core`
so that it matches the namespace prefix (`core/`) used by bundled
icons, removing the confusing split between namespace and collection
identifiers.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Icons: Decouple icon name from collection slug at the public API layer
Callers of `wp_register_icon` now pass an unqualified icon name (e.g.
`arrow-left`) together with a `collection` slug, instead of encoding
both into a single namespaced string (`core/arrow-left`). The registry
continues to key storage by `<collection>/<name>` internally so that
the existing single-item REST route, cross-collection name-collision
protection, and `is_registered`/`get_registered_icon` lookups keep
working without broader changes.
The REST response is reshaped to match: icons now expose separate
`collection` and `name` fields instead of a single namespaced `name`,
which lines up with the hierarchical `/icons/<collection>` route added
earlier and avoids clients having to parse the slash-delimited form.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Icons: Keep namespaced name in REST response and delegate to parent
The previous commit reshaped the icon response to return an unqualified
`name` plus a separate `collection` field, but that diverges from the
namespaced identifier clients already use to address single items via
`/icons/<collection>/<name>`. Restore the namespaced `name` so the
response value can be used as-is for lookups, and keep `collection` as
an additional field for convenience. Reimplement the override as a
thin wrapper around the parent method to avoid duplicating the base
field-filtering logic.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Icons: Update registry tests for unqualified-name registration
Rework the existing tests to match the current registration contract:
callers pass an unqualified icon name together with a `collection`
slug, and the registry stores items under a `<collection>/<name>` key.
Set up a test collection in `set_up`/`tear_down` so the registration
path has a valid collection to target, and refresh the invalid-name
fixtures to reflect that slashes are now rejected at input rather than
required.
Add coverage for the collection requirement itself (missing /
non-string / unregistered collection) and for cross-collection name
reuse, since the split between name and collection is the core
behavior that differs from the previous namespaced-name design.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Icons: Rename gutenberg_register_icons to gutenberg_register_default_icons
Makes the bootstrap function's intent explicit: it specifically seeds
the default `core` collection from the bundled manifest, mirroring the
naming of `gutenberg_register_icon_collections` which seeds the
default collection itself. The prior generic name was easy to mistake
for a public registration helper.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Icons: Rename gutenberg_register_icon_collections for naming parity
Matches the earlier rename of the icon bootstrap function to
`gutenberg_register_default_icons`, so both helpers that seed the
bundled defaults share a `_default_` marker and read as clearly
internal rather than part of the public registration API.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Icon block: Request full icon list without pagination
The inserter needs every registered icon to populate its picker, but
the default `getEntityRecords` request paginates to the first page
only, which silently truncates the list once more than a page's worth
of icons are registered (e.g. once plugins contribute their own
collections). Passing `per_page: -1` forces the resolver's chunked
fetch path so the picker always reflects the full registry.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Icons: Align variable assignment formatting with WPCS
Matches the WordPress coding standard's variable-alignment rule, so
phpcbf no longer rewrites these lines on contributors' pre-commit
runs.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Icons: Cascade icon removal when unregistering a collection
Unregistering a collection previously left its icons in the icons
registry, which produced orphaned entries still reachable through
`/wp/v2/icons` and `/wp/v2/icons/<collection>/<name>` even though the
collection-scoped route returned 404. Cascade the removal so the two
registries stay consistent, and add a regression test covering both
the cascade and the fact that icons in unrelated collections are left
alone.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Icons: Take collection as a separate argument in register/unregister
Lifts the collection slug out of the `$args` array and the qualified
`<collection>/<name>` string into its own required parameter on both
`wp_register_icon`/`wp_unregister_icon` and the underlying registry
methods. The symmetric `( $icon_name, $collection, ... )` shape makes
the dependency between an icon and its collection explicit at the
call site, avoids callers having to manually concatenate the
qualified name just to remove an icon, and keeps the public API in
line with the internal storage convention where the two values are
always tracked separately.
The collection-cascade in `WP_Icon_Collections_Registry::unregister`
is updated accordingly to pass the unqualified name and slug to the
new signature.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Icons: Use __() for the default collection label
`_x()` takes a context string as its second argument, but the call was
passing the text domain, so the string was being registered with an
unintended context and never picking up the translation. Switch to
`__()` so the label is translatable via the `gutenberg` text domain
the same way as the surrounding strings.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Icons: Fix word order in manifest validation error message
The existing message read "valid a \"filePath\"" due to a transposed
article. Correct it to "a valid \"filePath\"" so the error is readable
and translatable in a natural form.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Icons: Use matching version in collection unregister _doing_it_wrong
The `_doing_it_wrong` call in `WP_Icon_Collections_Registry::unregister`
referenced a future `21.4.0` marker, but the rest of this class (and
the surrounding icons API it ships with) consistently reports `7.1.0`
as the introduction version. Align the version argument so all
`_doing_it_wrong` notices from this file point at the same release.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Icons: Guard collection registration against non-array properties
Without this check, passing anything other than an array as the second
argument (e.g. null, a string, or a forgotten argument from a partial
refactor) reached `array_keys` / `array_fill_keys` and produced a
PHP type error or warning instead of a clean `_doing_it_wrong`
notice. Fail early with the same pattern used for the other
validation branches so misuse is surfaced as a developer warning and
the registration simply returns false.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Icons: Validate the namespace query param shape at the REST boundary
The URL-segment route already restricts the capture to the collection
slug pattern, but the same parameter as a query string had only a
`string` type check. Adding the matching `pattern` lets
`rest_validate_request_arg` reject malformed input with a 400 before
the handler runs, instead of funnelling everything through the
404-on-unregistered-collection path.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Icons: Exercise each invalid-name case individually in the data provider
`test_register_invalid_name` was annotated with `@dataProvider` but
ignored the argument and looped over the provider manually, so every
run received an array (e.g. `[ 'Plus' ]`) as the name and only the
non-string branch of the validator was actually hit. Accept `$name`
from the provider and drop the manual loop so each case — slash,
uppercase, leading underscore, non-string — is covered as a separate
assertion in the failure output.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Icons: Target the Gutenberg icons registry directly in cascade removal
The cascade fetched the icons registry via the base class singleton,
which stays in sync with the Gutenberg subclass only after
`gutenberg_override_wp_icons_registry()` has run. Anything that
resets the subclass singleton on its own (notably the PHPUnit
`tear_down` between tests) leaves the two pointers diverged, and the
cascade then iterates a stale instance and silently no-ops -- which
is exactly what made `test_unregister_collection_cascades_to_icons`
fail intermittently.
The collections registry is shipped only with Gutenberg and only
Gutenberg-registered icons carry a `collection` field for the cascade
to match on, so resolving to `WP_Icons_Registry_Gutenberg` directly
is both accurate and removes the singleton-sync dependency.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Icons: Add backport changelog entry for 7.1
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Icons: Add unregister tests and relocate cascade test
Cover unregister() success and unknown-icon paths in the icons registry
test, and move the collection-cascade test out to the collections suite
where it belongs.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Icons: Add unit tests for WP_Icon_Collections_Registry
Mirror the coverage in core's tests/phpunit/tests/icons/wpIconCollectionsRegistry.php
so the Gutenberg copy of the collections registry is exercised the same
way, including the cascade-to-icons behavior via the Gutenberg icons
registry.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Icon block: Drop unused per_page query arg
The icons REST controller does not paginate, so passing { per_page: -1 } has no effect and only adds a redundant query parameter.
* Icon collections registry: Use a plain list for allowed property keys
Replace the array_fill_keys/array_key_exists pair with a plain list
checked via in_array, since only two keys are permitted and the
indirection adds no value at this scale.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Fix incorrect WP version
Co-authored-by: Miguel Fonseca <150562+mcsf@users.noreply.github.com>
* Icons: Keep collection inside $args on wp_register_icon
Reverts the public registration wrapper to the original
`wp_register_icon( $icon_name, $args )` shape and lets `collection`
travel as a required key inside `$args`, alongside `label`, `content`,
and `filePath`. The unqualified-name benefit of the previous change
only applied to `wp_unregister_icon`, where callers no longer have to
concatenate `<collection>/<name>` themselves; on the registration
side the qualified-name issue never existed, so splitting `collection`
out as a positional parameter just for symmetry was unnecessary
churn for callers.
`wp_unregister_icon( $icon_name, $collection )` and the underlying
registry methods are left as is.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Icons: Default collection to "core" when omitted on register
Treat the `collection` property of `WP_Icons_Registry::register()` as
optional and fall back to the built-in `core` collection when callers
do not specify one. The validation that previously rejected a missing
collection now only fires when a non-string value is passed; an
unregistered collection slug still triggers `_doing_it_wrong`.
Plugin authors registering a small number of icons no longer need to
know about the collection concept at all — they can register icons
directly into `core` by omitting the field. Authors that want their
own namespace can still pass `collection` explicitly after registering
a collection through `wp_register_icon_collection()`.
The internal docblock and the `wp_register_icon` wrapper docblock are
updated to mark `collection` as optional with the new default. The
`test_register_requires_collection` test is replaced with
`test_register_defaults_collection_to_core`, which asserts that an
omitted collection lands the icon under `core/<name>`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Icon collections registry: Use strict comparison in in_array
Adds the `true` strict-mode argument to the allowed-keys check so
that property names are compared by both type and value, satisfying
WPCS WordPress.PHP.StrictInArray and matching the rest of the file's
strict-typed validation.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Icons: Clarify default collection description as "Core"
Aligns the wording with the slug ("core") so the description reflects
the collection identity rather than its default-registration status.
* Icons: Use snake_case `file_path` key in PHP icon manifest and registry
The generated `manifest.php` and the PHP icon registry exposed the icon
path under a camelCase `filePath` key, which is inconsistent with PHP/WP
array-key conventions. Rename it to `file_path` across the manifest PHP
generator, both registry classes, and the test docblock. The
`manifest.json` source intentionally keeps `filePath` (JS convention).
Because the Gutenberg override inherits `get_content()` from the base
class, also override `get_content()` in `WP_Icons_Registry_Gutenberg` so
content is read from the `file_path` property even when the base class
comes from WordPress core (which may still use `filePath`), preventing a
key mismatch that would silently break icon content retrieval.
Co-Authored-By: Claude <noreply@anthropic.com>
* Add backport changelog
* Icons: Keep camelCase filePath in manifest, convert to file_path only in registry
The previous commit renamed the icon path key to snake_case file_path
across both the generated manifest.php and its generator. The manifest
mirrors the JS manifest.json, which uses camelCase filePath, so the
generated PHP manifest should keep filePath for consistency with its
source. Revert the generator and manifest.php back to filePath.
The PHP registry still exposes the path as file_path (PHP/WP array-key
convention), so register_collection now reads the camelCase filePath
from the manifest and converts it to file_path when registering. This
confines the filePath-to-file_path conversion to the registry boundary.
Co-Authored-By: Claude <noreply@anthropic.com>
* Icons: Rename icon registration property from filePath to file_path
Align the icon registration API property with WordPress snake_case
conventions following the base branch change. The manifest JSON key
(`$icon_data['filePath']`) stays camelCase since it mirrors the JS
manifest format.
Co-Authored-By: Claude <noreply@anthropic.com>
* Icons: Register icons by namespaced "collection/name" string
Replace the separate icon name and collection arguments with a single
namespaced name in the form "collection/icon-name" (e.g. "core/arrow-left"),
mirroring how block types are named. The collection is now derived from the
name string instead of a `collection` property, and `wp_unregister_icon`
takes the same namespaced name so the register/unregister pair stays
symmetric. Names without a prefix still default to the "core" collection.
Co-Authored-By: Claude <noreply@anthropic.com>
* Icons: Validate icon file path before reading content
Custom icons can be registered with a `file_path`, but the path was never
checked before `file_get_contents()`, so a missing, unreadable, non-SVG, or
non-string path emitted a raw PHP warning and a misleading "invalid SVG
markup" error. Guard `get_content()` with the same validation core uses in
`WP_Block_Patterns_Registry`: resolve via `realpath()`, then require an
`.svg` extension, a regular file, and readability, returning null with a
clear message otherwise.
Override `get_content()` in `WP_Icons_Registry_Gutenberg` as well so the
validation applies when the base `WP_Icons_Registry` is provided by core
instead of the compat shim. Add tests covering valid and invalid file paths.
Co-Authored-By: Claude <noreply@anthropic.com>
* Icons: Preserve original priority when removing core init actions
has_action() returns the registered priority (or false), which is 0
for the priority-0 hooks. The previous truthy check skipped removal at
priority 0, and remove_action() assumed the default priority 10. Capture
the returned priority, compare with !== false, and pass it to
remove_action() so the core actions are removed regardless of priority.
Co-Authored-By: Claude <noreply@anthropic.com>
* Icons: Allow digits in icon collection slugs
Mirror register_block_type's name validation by accepting lowercase
alphanumeric characters and hyphens. The previous rule rejected digits,
which blocked legitimate slugs such as "i18n" or icon variations that
include numbers. Update the slug tests accordingly: digit-containing
slugs are now valid, and underscores remain rejected.
Co-Authored-By: Claude <noreply@anthropic.com>
* Icons: Add unit tests for the `file_path` icon property
The registry rename to snake_case `file_path` only updated test docblocks
without exercising the property. Cover registering via `file_path`, reading
content from the referenced file, and rejecting icons that supply both or
neither of `content`/`file_path`.
Co-Authored-By: Claude <noreply@anthropic.com>
* Icons: Require namespaced "collection/name" for icon registration
Previously, registering an icon without a collection prefix silently
defaulted to the "core" collection, letting third-party code register
icons under the reserved core namespace by omitting the prefix. Require
an explicit "collection/icon-name" form and reject non-namespaced names.
Co-Authored-By: Claude <noreply@anthropic.com>
* Icons: Drop redundant comments in icon name validation
Co-Authored-By: Claude <noreply@anthropic.com>
* Tests: align merged icon file_path tests with collection requirement
The trunk merge pulled in file_path registration tests that used an
unregistered 'test-plugin' collection and a non-.svg temp file. After
the icons API refactor, register() rejects unregistered collections and
get_content() requires a .svg extension, so these tests failed. Use the
registered 'test-collection' and the create_temp_icon_file helper.
Co-Authored-By: Claude <noreply@anthropic.com>
* Fix duplicate icons.php require causing fatal redeclare error
The trunk merge left two require statements for the WordPress 7.1
icons.php compat file: one inside the WP_REST_Controller block and one
at the top level. Because gutenberg_register_default_icon_collections()
is not guarded by function_exists(), loading the file twice triggered a
fatal "Cannot redeclare" error. Keep the unconditional top-level require
(matching trunk) and drop the duplicate inside the REST block.
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: t-hamano <wildworks@git.wordpress.org>
Co-authored-by: mcsf <mcsf@git.wordpress.org>
Co-authored-by: Mamaduka <mamaduka@git.wordpress.org>
Co-authored-by: tyxla <tyxla@git.wordpress.org>
Co-authored-by: jsnajdr <jsnajdr@git.wordpress.org>
file_pathkey in icon registry #79100Note: I understand that this PR is large. However, I believe this is the minimum implementation required to correctly expose the API for registering icons.
What?
This PR exposes basic APIs for registering SVG icons.
The approach proposed by this PR is as follows. Please share your thoughts on this approach:
core(WordPress).{collection-slug}/{icon-slug}, as before.In the future, we might also support "categories," similar to font collections. That is, something like this:
Font Awesome > Symbol > Arrow LeftClasses
I added and extended classes to allow custom icon registration.
WP_Icon_Collections_RegistryNew. Singleton that stores icon collections. It supports basic methods such as registering, unregistering, and retrieving items from a collection.
WP_Icons_Registry_Gutenberggutenberg_register_default_iconsinstead.WP_REST_Icons_Controller_GutenbergAdd an endpoint to retrieve only the icons belonging to a specific collection.
/wp/v2/icons: Unchanged. Lists all registered icons./wp/v2/icons/<namespace>: New. Lists icons belonging to the given collection slug/wp/v2/icons/<namespace>/<name>: Unchanged. Single-item lookup.PHP functions
These are new wrapper functions for managing collections and icons.
wp_register_icon_collection( $slug, $args )wp_unregister_icon_collection( $slug )wp_register_icon( $icon_name, $args )wp_unregister_icon( $icon_name )Testing Instructions
Verify that the main APIs are functioning correctly. Below are code examples.
Register icons:
Confirm registered icons:
Unregister icons and collections
Screenshot
As you can see, the icon picker modal does not currently support filtering by collection. This will be addressed in a follow-up update.
Use of AI Tools
Parts of this PR (code refactoring, commit messages, PR description) were drafted with assistance from Claude Code. All generated changes were reviewed and adjusted manually before committing.