Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions src/wp-includes/class-wp-icons-registry.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,14 @@ class WP_Icons_Registry {
*/
protected $registered_icons = array();

/**
* Whether the default icons bundled with core have been registered yet.
*
* @since 7.1.0
* @var bool
*/
protected $default_icons_loaded = false;

/**
* Container for the main instance of the class.
*
Expand Down Expand Up @@ -336,6 +344,8 @@ public function get_registered_icon( $icon_name ) {
* @return array[] Array of arrays containing the registered icon properties.
*/
public function get_registered_icons( $search = '' ) {
$this->load_default_icons();

$icons = array();

foreach ( $this->registered_icons as $icon ) {
Expand All @@ -362,9 +372,35 @@ public function get_registered_icons( $search = '' ) {
* @return bool True if the icon is registered, false otherwise.
*/
public function is_registered( $icon_name ) {
$this->load_default_icons();

return isset( $this->registered_icons[ $icon_name ] );
}

/**
* Registers the default icons bundled with core, once, on first use.
*
* The core icon manifest declares 88 icons, each with a translated label.
* Registering them eagerly costs every request — including front-end
* requests that never render an icon — so registration is deferred until
* something actually reads the registry.
*
* @since 7.1.0
*/
protected function load_default_icons() {
if ( $this->default_icons_loaded ) {
return;
}

/*
* Set before registering: _wp_register_default_icons() calls back into
* this class, and this flag is what stops that from recursing.
*/
$this->default_icons_loaded = true;

_wp_register_default_icons();
}

/**
* Utility method to retrieve the main instance of the class.
*
Expand Down
5 changes: 4 additions & 1 deletion src/wp-includes/default-filters.php
Original file line number Diff line number Diff line change
Expand Up @@ -816,7 +816,10 @@

// Icons.
add_action( 'init', '_wp_register_default_icon_collections', 0 );
add_action( 'init', '_wp_register_default_icons' );
/*
* The default icons themselves are registered lazily, on first read of the
* icons registry. See WP_Icons_Registry::load_default_icons().
*/

// Add ignoredHookedBlocks metadata attribute to the template and template part post types.
add_filter( 'rest_pre_insert_wp_template', 'inject_ignored_hooked_blocks_metadata_attributes' );
Expand Down
9 changes: 9 additions & 0 deletions src/wp-includes/icons.php
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,15 @@ function _wp_register_default_icon_collections() {
* @access private
*/
function _wp_register_default_icons() {
/*
* Icons can be requested before `init` now that the default icons are
* registered on first read, so make sure the collection they belong to
* exists rather than relying on the `init` callback having run.
*/
if ( ! WP_Icon_Collections_Registry::get_instance()->is_registered( 'core' ) ) {
_wp_register_default_icon_collections();
}

$icons_directory = ABSPATH . WPINC . '/images/icon-library/';
$manifest_path = ABSPATH . WPINC . '/assets/icon-library-manifest.php';

Expand Down
104 changes: 104 additions & 0 deletions tests/phpunit/tests/icons/wpIconsRegistry.php
Original file line number Diff line number Diff line change
Expand Up @@ -453,4 +453,108 @@ public function test_get_content_returns_null_for_invalid_file( $contents, $exte

$this->assertNull( $icon['content'] );
}

/**
* Resets the registry singleton so a test starts with the default icons
* unloaded.
*
* Needed because tear_down() unregisters a test collection, and collection
* unregistration reads the icon registry in order to drop that collection's
* icons — which triggers the lazy load into the replacement instance.
*/
private function reset_registry() {
$reflection = new ReflectionClass( WP_Icons_Registry::class );
$property = $reflection->getProperty( 'instance' );
if ( PHP_VERSION_ID < 80100 ) {
$property->setAccessible( true );
}
$property->setValue( null, null );

$this->registry = WP_Icons_Registry::get_instance();
}

/**
* Reads the registered icons property directly, so that the assertion itself
* does not trigger the lazy registration it is checking for.
*
* @return array Registered icons.
*/
private function get_registered_icons_property() {
$reflection = new ReflectionClass( WP_Icons_Registry::class );
$property = $reflection->getProperty( 'registered_icons' );
if ( PHP_VERSION_ID < 80100 ) {
$property->setAccessible( true );
}

return $property->getValue( $this->registry );
}

/**
* @ticket 00000
*
* @covers ::load_default_icons
*/
public function test_default_icons_are_not_registered_until_the_registry_is_read() {
$this->reset_registry();

$this->assertSame(
array(),
$this->get_registered_icons_property(),
'The default icons should not be registered before the registry is read.'
);
}

/**
* @ticket 00000
*
* @covers ::load_default_icons
*/
public function test_default_icons_are_registered_on_first_read() {
$this->reset_registry();

$icon = $this->registry->get_registered_icon( 'core/arrow-left' );

$this->assertIsArray( $icon, 'A core icon should resolve without explicit registration.' );
$this->assertSame( 'core/arrow-left', $icon['name'] );
$this->assertStringStartsWith( '<svg', $icon['content'] );
$this->assertNotEmpty(
$this->get_registered_icons_property(),
'Reading the registry should have registered the default icons.'
);
}

/**
* @ticket 00000
*
* @covers ::is_registered
*/
public function test_is_registered_triggers_default_icon_registration() {
$this->reset_registry();

$this->assertTrue( $this->registry->is_registered( 'core/arrow-left' ) );
}

/**
* The default icons belong to the `core` collection, which is normally
* registered on `init`. A lazy load can happen before that, so the collection
* must be ensured at load time rather than assumed to exist.
*
* @ticket 00000
*
* @covers ::load_default_icons
*/
public function test_default_icons_load_even_if_core_collection_is_missing() {
$collections = WP_Icon_Collections_Registry::get_instance();
if ( $collections->is_registered( 'core' ) ) {
$collections->unregister( 'core' );
}
$this->assertFalse( $collections->is_registered( 'core' ) );

$this->reset_registry();

$icons = $this->registry->get_registered_icons();

$this->assertNotEmpty( $icons, 'Default icons should register even when the collection was not set up yet.' );
$this->assertTrue( $collections->is_registered( 'core' ), 'The core collection should be registered on demand.' );
}
}
Loading