diff --git a/src/wp-includes/class-wp-theme-json-resolver.php b/src/wp-includes/class-wp-theme-json-resolver.php index c8129e1ebc864..11d3944bb69c9 100644 --- a/src/wp-includes/class-wp-theme-json-resolver.php +++ b/src/wp-includes/class-wp-theme-json-resolver.php @@ -168,7 +168,7 @@ protected static function translate( $theme_json, $domain = 'default' ) { * @return WP_Theme_JSON Entity that holds core data. */ public static function get_core_data() { - if ( null !== static::$core && static::has_same_registered_blocks( 'core' ) ) { + if ( static::has_same_registered_blocks( 'core' ) && null !== static::$core ) { return static::$core; } @@ -262,7 +262,7 @@ public static function get_theme_data( $deprecated = array(), $options = array() $options = wp_parse_args( $options, array( 'with_supports' => true ) ); - if ( null === static::$theme || ! static::has_same_registered_blocks( 'theme' ) ) { + if ( ! static::has_same_registered_blocks( 'theme' ) || null === static::$theme ) { $wp_theme = wp_get_theme(); $theme_json_file = $wp_theme->get_file_path( 'theme.json' ); if ( is_readable( $theme_json_file ) ) { @@ -407,7 +407,7 @@ public static function get_block_data() { $registry = WP_Block_Type_Registry::get_instance(); $blocks = $registry->get_all_registered(); - if ( null !== static::$blocks && static::has_same_registered_blocks( 'blocks' ) ) { + if ( static::has_same_registered_blocks( 'blocks' ) && null !== static::$blocks ) { return static::$blocks; } @@ -555,7 +555,7 @@ public static function get_user_data_from_wp_global_styles( $theme, $create_post * @return WP_Theme_JSON Entity that holds styles for user data. */ public static function get_user_data() { - if ( null !== static::$user && static::has_same_registered_blocks( 'user' ) ) { + if ( static::has_same_registered_blocks( 'user' ) && null !== static::$user ) { return static::$user; } diff --git a/tests/phpunit/tests/theme/wpThemeJsonResolver.php b/tests/phpunit/tests/theme/wpThemeJsonResolver.php index 02af5b51c00d0..03d2c1838d0d6 100644 --- a/tests/phpunit/tests/theme/wpThemeJsonResolver.php +++ b/tests/phpunit/tests/theme/wpThemeJsonResolver.php @@ -126,6 +126,10 @@ public function tear_down() { unset( $GLOBALS['wp_themes'] ); remove_filter( 'theme_file_uri', array( $this, 'filter_theme_file_uri' ) ); + if ( WP_Block_Type_Registry::get_instance()->is_registered( 'test/seeded' ) ) { + unregister_block_type( 'test/seeded' ); + } + // Reset data between tests. wp_clean_theme_json_cache(); parent::tear_down(); @@ -433,12 +437,10 @@ public function test_get_core_data( $should_fire_filter, $core_is_cached, $block WP_Theme_JSON_Resolver::get_core_data(); } - // If should cache registered blocks, then set them up before running the tests. - if ( $blocks_are_cached ) { - $blocks_cache = static::$property_blocks_cache->getValue(); - $blocks_cache['core'] = $this->get_registered_block_names(); - static::$property_blocks_cache->setValue( null, $blocks_cache ); - } + // Set the blocks cache to the registered blocks, or empty it: caching core also fills it. + $blocks_cache = static::$property_blocks_cache->getValue(); + $blocks_cache['core'] = $blocks_are_cached ? $this->get_registered_block_names() : array(); + static::$property_blocks_cache->setValue( null, $blocks_cache ); $expected_filter_count = did_filter( 'wp_theme_json_data_default' ); $actual = WP_Theme_JSON_Resolver::get_core_data(); @@ -474,13 +476,63 @@ public static function data_get_core_data() { 'blocks_are_cached' => false, ), 'When both caches are not empty' => array( - 'should_fire_filter' => true, + 'should_fire_filter' => false, 'core_is_cached' => true, - 'blocks_are_cached' => false, + 'blocks_are_cached' => true, ), ); } + /** + * The first call to an origin getter should record the registered blocks, + * so the second call reuses the data instead of rebuilding it. + * + * @ticket 62249 + * + * @dataProvider data_origin_getters + * + * @covers WP_Theme_JSON_Resolver::get_core_data + * @covers WP_Theme_JSON_Resolver::get_block_data + * @covers WP_Theme_JSON_Resolver::get_theme_data + * @covers WP_Theme_JSON_Resolver::get_user_data + * + * @param string $getter Resolver method name. + * @param list $args Arguments for the method. + * @param string $filter Filter the method applies when it builds the data. + */ + public function test_origin_data_is_built_once_while_registered_blocks_are_unchanged( string $getter, array $args, string $filter ): void { + wp_clean_theme_json_cache(); + $callback = array( WP_Theme_JSON_Resolver::class, $getter ); + $filter_count = did_filter( $filter ); + + $first = call_user_func_array( $callback, $args ); + $this->assertSame( $filter_count + 1, did_filter( $filter ), 'The first call should build the data' ); + + $second = call_user_func_array( $callback, $args ); + $this->assertSame( $filter_count + 1, did_filter( $filter ), 'The second call should not rebuild the data' ); + $this->assertSame( $first, $second, 'The second call should return the same object' ); + + register_block_type( 'test/seeded' ); + + $third = call_user_func_array( $callback, $args ); + $this->assertSame( $filter_count + 2, did_filter( $filter ), 'Registering a block type should rebuild the data' ); + $this->assertNotSame( $first, $third, 'The rebuilt data should be a new object' ); + } + + /** + * Data provider. + * + * @return array, 2: string}> + */ + public static function data_origin_getters(): array { + return array( + 'core' => array( 'get_core_data', array(), 'wp_theme_json_data_default' ), + 'blocks' => array( 'get_block_data', array(), 'wp_theme_json_data_blocks' ), + 'theme' => array( 'get_theme_data', array( array(), array( 'with_supports' => false ) ), 'wp_theme_json_data_theme' ), + 'user' => array( 'get_user_data', array(), 'wp_theme_json_data_user' ), + ); + } + /** * @ticket 54336 * @ticket 60118