diff --git a/src/wp-admin/edit-form-blocks.php b/src/wp-admin/edit-form-blocks.php index f43c835168c1d..e324a4e1b7bd5 100644 --- a/src/wp-admin/edit-form-blocks.php +++ b/src/wp-admin/edit-form-blocks.php @@ -203,7 +203,7 @@ static function( $classes ) { 'unlockNonce' => wp_create_nonce( 'update-post_' . $post->ID ), 'ajaxUrl' => admin_url( 'admin-ajax.php' ), ), - 'supportsLayout' => WP_Theme_JSON_Resolver::theme_has_support(), + 'supportsLayout' => wp_theme_has_theme_json(), 'supportsTemplateMode' => current_theme_supports( 'block-templates' ), // Whether or not to load the 'postcustom' meta box is stored as a user meta diff --git a/src/wp-admin/site-editor.php b/src/wp-admin/site-editor.php index a8b43b52af768..deb556a695768 100644 --- a/src/wp-admin/site-editor.php +++ b/src/wp-admin/site-editor.php @@ -74,7 +74,7 @@ static function( $classes ) { 'styles' => get_block_editor_theme_styles(), 'defaultTemplateTypes' => $indexed_template_types, 'defaultTemplatePartAreas' => get_allowed_block_template_part_areas(), - 'supportsLayout' => WP_Theme_JSON_Resolver::theme_has_support(), + 'supportsLayout' => wp_theme_has_theme_json(), 'supportsTemplatePartsMode' => ! wp_is_block_theme() && current_theme_supports( 'block-template-parts' ), '__unstableHomeTemplate' => $home_template, ); diff --git a/src/wp-includes/block-editor.php b/src/wp-includes/block-editor.php index 7af9f2a90062a..4227c674c02ff 100644 --- a/src/wp-includes/block-editor.php +++ b/src/wp-includes/block-editor.php @@ -417,7 +417,7 @@ function get_block_editor_settings( array $custom_settings, $block_editor_contex } } - if ( WP_Theme_JSON_Resolver::theme_has_support() ) { + if ( wp_theme_has_theme_json() ) { $block_classes = array( 'css' => 'styles', '__unstableType' => 'theme', diff --git a/src/wp-includes/block-patterns.php b/src/wp-includes/block-patterns.php index 074ed33c84b44..63398513d48dc 100644 --- a/src/wp-includes/block-patterns.php +++ b/src/wp-includes/block-patterns.php @@ -139,7 +139,7 @@ function _register_remote_theme_patterns() { return; } - if ( ! WP_Theme_JSON_Resolver::theme_has_support() ) { + if ( ! wp_theme_has_theme_json() ) { return; } diff --git a/src/wp-includes/block-supports/layout.php b/src/wp-includes/block-supports/layout.php index 7a882a9bab386..93c7364a323b6 100644 --- a/src/wp-includes/block-supports/layout.php +++ b/src/wp-includes/block-supports/layout.php @@ -464,7 +464,7 @@ function wp_restore_group_inner_container( $block_content, $block ) { ); if ( - WP_Theme_JSON_Resolver::theme_has_support() || + wp_theme_has_theme_json() || 1 === preg_match( $group_with_inner_container_regex, $block_content ) || ( isset( $block['attrs']['layout']['type'] ) && 'flex' === $block['attrs']['layout']['type'] ) ) { @@ -527,7 +527,7 @@ function wp_restore_image_outer_container( $block_content, $block ) { )/iUx"; if ( - WP_Theme_JSON_Resolver::theme_has_support() || + wp_theme_has_theme_json() || 0 === preg_match( $image_with_align, $block_content, $matches ) ) { return $block_content; diff --git a/src/wp-includes/block-template-utils.php b/src/wp-includes/block-template-utils.php index 826237aa18946..66c48db7f1305 100644 --- a/src/wp-includes/block-template-utils.php +++ b/src/wp-includes/block-template-utils.php @@ -347,7 +347,7 @@ function _get_block_templates_files( $template_type ) { * @return array Template item. */ function _add_block_template_info( $template_item ) { - if ( ! WP_Theme_JSON_Resolver::theme_has_support() ) { + if ( ! wp_theme_has_theme_json() ) { return $template_item; } @@ -370,7 +370,7 @@ function _add_block_template_info( $template_item ) { * @return array Template info. */ function _add_block_template_part_area_info( $template_info ) { - if ( WP_Theme_JSON_Resolver::theme_has_support() ) { + if ( wp_theme_has_theme_json() ) { $theme_data = WP_Theme_JSON_Resolver::get_theme_data( array(), array( 'with_supports' => false ) )->get_template_parts(); } diff --git a/src/wp-includes/class-wp-theme-json-resolver.php b/src/wp-includes/class-wp-theme-json-resolver.php index e4afc3c1489d8..3abc3142e95f7 100644 --- a/src/wp-includes/class-wp-theme-json-resolver.php +++ b/src/wp-includes/class-wp-theme-json-resolver.php @@ -57,14 +57,6 @@ class WP_Theme_JSON_Resolver { */ protected static $theme = null; - /** - * Whether or not the theme supports theme.json. - * - * @since 5.8.0 - * @var bool - */ - protected static $theme_has_support = null; - /** * Container for data coming from the user. * @@ -295,7 +287,7 @@ public static function get_theme_data( $deprecated = array(), $options = array() * and merge the static::$theme upon that. */ $theme_support_data = WP_Theme_JSON::get_from_editor_settings( get_default_block_editor_settings() ); - if ( ! static::theme_has_support() ) { + if ( ! wp_theme_has_theme_json() ) { if ( ! isset( $theme_support_data['settings']['color'] ) ) { $theme_support_data['settings']['color'] = array(); } @@ -421,11 +413,11 @@ public static function get_user_data_from_wp_global_styles( $theme, $create_post /* * Bail early if the theme does not support a theme.json. * - * Since WP_Theme_JSON_Resolver::theme_has_support() only supports the active + * Since wp_theme_has_theme_json() only supports the active * theme, the extra condition for whether $theme is the active theme is * present here. */ - if ( $theme->get_stylesheet() === get_stylesheet() && ! static::theme_has_support() ) { + if ( $theme->get_stylesheet() === get_stylesheet() && ! wp_theme_has_theme_json() ) { return array(); } @@ -602,18 +594,14 @@ public static function get_user_global_styles_post_id() { * * @since 5.8.0 * @since 5.9.0 Added a check in the parent theme. + * @deprecated 6.2.0 Use wp_theme_has_theme_json() instead. * * @return bool */ public static function theme_has_support() { - if ( ! isset( static::$theme_has_support ) ) { - static::$theme_has_support = ( - static::get_file_path_from_theme( 'theme.json' ) !== '' || - static::get_file_path_from_theme( 'theme.json', true ) !== '' - ); - } + _deprecated_function( __METHOD__, '6.2.0', 'wp_theme_has_theme_json()' ); - return static::$theme_has_support; + return wp_theme_has_theme_json(); } /** @@ -656,7 +644,6 @@ public static function clean_cached_data() { static::$theme = null; static::$user = null; static::$user_custom_post_type_id = null; - static::$theme_has_support = null; static::$i18n_schema = null; } diff --git a/src/wp-includes/default-filters.php b/src/wp-includes/default-filters.php index 5c623e371b195..4e6340700cc15 100644 --- a/src/wp-includes/default-filters.php +++ b/src/wp-includes/default-filters.php @@ -346,8 +346,8 @@ add_action( 'init', '_register_core_block_patterns_and_categories' ); add_action( 'init', 'check_theme_switched', 99 ); add_action( 'init', array( 'WP_Block_Supports', 'init' ), 22 ); -add_action( 'switch_theme', array( 'WP_Theme_JSON_Resolver', 'clean_cached_data' ) ); -add_action( 'start_previewing_theme', array( 'WP_Theme_JSON_Resolver', 'clean_cached_data' ) ); +add_action( 'switch_theme', 'wp_clean_theme_json_cache' ); +add_action( 'start_previewing_theme', 'wp_clean_theme_json_cache' ); add_action( 'after_switch_theme', '_wp_menus_changed' ); add_action( 'after_switch_theme', '_wp_sidebars_changed' ); add_action( 'wp_print_styles', 'print_emoji_styles' ); diff --git a/src/wp-includes/global-styles-and-settings.php b/src/wp-includes/global-styles-and-settings.php index b6c8c91b464c0..d93792668752e 100644 --- a/src/wp-includes/global-styles-and-settings.php +++ b/src/wp-includes/global-styles-and-settings.php @@ -102,7 +102,7 @@ function wp_get_global_stylesheet( $types = array() ) { $tree = WP_Theme_JSON_Resolver::get_merged_data(); - $supports_theme_json = WP_Theme_JSON_Resolver::theme_has_support(); + $supports_theme_json = wp_theme_has_theme_json(); if ( empty( $types ) && ! $supports_theme_json ) { $types = array( 'variables', 'presets', 'base-layout-styles' ); } elseif ( empty( $types ) ) { @@ -184,7 +184,7 @@ function wp_get_global_styles_svg_filters() { } } - $supports_theme_json = WP_Theme_JSON_Resolver::theme_has_support(); + $supports_theme_json = wp_theme_has_theme_json(); $origins = array( 'default', 'theme', 'custom' ); if ( ! $supports_theme_json ) { @@ -255,3 +255,72 @@ function ( $item ) { } } } + +/** + * Checks whether a theme or its parent has a theme.json file. + * + * @since 6.2.0 + * + * @return bool Returns true if theme or its parent has a theme.json file, false otherwise. + */ +function wp_theme_has_theme_json() { + /* + * By using the 'theme_json' group, this data is marked to be non-persistent across requests. + * @see `wp_cache_add_non_persistent_groups()`. + * + * The rationale for this is to make sure derived data from theme.json + * is always fresh from the potential modifications done via hooks + * that can use dynamic data (modify the stylesheet depending on some option, + * settings depending on user permissions, etc.). + * For some of the existing hooks to modify theme.json behavior: + * @see https://make.wordpress.org/core/2022/10/10/filters-for-theme-json-data/ + * + * A different alternative considered was to invalidate the cache upon certain + * events such as options add/update/delete, user meta, etc. + * It was judged not enough, hence this approach. + * @see https://github.com/WordPress/gutenberg/pull/45372 + */ + $cache_group = 'theme_json'; + $cache_key = 'wp_theme_has_theme_json'; + $theme_has_support = wp_cache_get( $cache_key, $cache_group ); + + /* + * $theme_has_support is stored as an int in the cache. + * + * The reason not to store it as a boolean is to avoid working + * with the $found parameter which apparently had some issues in some implementations + * @see https://developer.wordpress.org/reference/functions/wp_cache_get/ + * + * Ignore cache when `WP_DEBUG` is enabled, so it doesn't interfere with the theme + * developer's workflow. + * + * @todo Replace `WP_DEBUG` once an "in development mode" check is available in Core. + */ + if ( ! WP_DEBUG && is_int( $theme_has_support ) ) { + return (bool) $theme_has_support; + } + + // Does the theme have its own theme.json? + $theme_has_support = is_readable( get_stylesheet_directory() . '/theme.json' ); + + // Look up the parent if the child does not have a theme.json. + if ( ! $theme_has_support ) { + $theme_has_support = is_readable( get_template_directory() . '/theme.json' ); + } + + $theme_has_support = $theme_has_support ? 1 : 0; + + wp_cache_set( $cache_key, $theme_has_support, $cache_group ); + + return (bool) $theme_has_support; +} + +/** + * Cleans the caches under the theme_json group. + * + * @since 6.2.0 + */ +function wp_clean_theme_json_cache() { + wp_cache_delete( 'wp_theme_has_theme_json', 'theme_json' ); + WP_Theme_JSON_Resolver::clean_cached_data(); +} diff --git a/src/wp-includes/load.php b/src/wp-includes/load.php index 4c212086df34e..c2450e582f958 100644 --- a/src/wp-includes/load.php +++ b/src/wp-includes/load.php @@ -753,7 +753,7 @@ function wp_start_object_cache() { ) ); - wp_cache_add_non_persistent_groups( array( 'counts', 'plugins' ) ); + wp_cache_add_non_persistent_groups( array( 'counts', 'plugins', 'theme_json' ) ); } $first_init = false; diff --git a/src/wp-includes/ms-blogs.php b/src/wp-includes/ms-blogs.php index 0ea6f85627dcb..7ff830e930863 100644 --- a/src/wp-includes/ms-blogs.php +++ b/src/wp-includes/ms-blogs.php @@ -575,7 +575,7 @@ function switch_to_blog( $new_blog_id, $deprecated = null ) { ); } - wp_cache_add_non_persistent_groups( array( 'counts', 'plugins' ) ); + wp_cache_add_non_persistent_groups( array( 'counts', 'plugins', 'theme_json' ) ); } } @@ -666,7 +666,7 @@ function restore_current_blog() { ); } - wp_cache_add_non_persistent_groups( array( 'counts', 'plugins' ) ); + wp_cache_add_non_persistent_groups( array( 'counts', 'plugins', 'theme_json' ) ); } } diff --git a/src/wp-includes/script-loader.php b/src/wp-includes/script-loader.php index 1444727f9dae7..de52578936989 100644 --- a/src/wp-includes/script-loader.php +++ b/src/wp-includes/script-loader.php @@ -1620,7 +1620,7 @@ function wp_default_styles( $styles ) { ); // Only load the default layout and margin styles for themes without theme.json file. - if ( ! WP_Theme_JSON_Resolver::theme_has_support() ) { + if ( ! wp_theme_has_theme_json() ) { $wp_edit_blocks_dependencies[] = 'wp-editor-classic-layout-styles'; } @@ -3667,7 +3667,7 @@ function _wp_theme_json_webfonts_handler() { * @since 6.1.0 */ function wp_enqueue_classic_theme_styles() { - if ( ! WP_Theme_JSON_Resolver::theme_has_support() ) { + if ( ! wp_theme_has_theme_json() ) { $suffix = wp_scripts_get_suffix(); wp_register_style( 'classic-theme-styles', '/' . WPINC . "/css/classic-themes$suffix.css", array(), true ); wp_enqueue_style( 'classic-theme-styles' ); @@ -3685,7 +3685,7 @@ function wp_enqueue_classic_theme_styles() { * @return array A filtered array of editor settings. */ function wp_add_editor_classic_theme_styles( $editor_settings ) { - if ( WP_Theme_JSON_Resolver::theme_has_support() ) { + if ( wp_theme_has_theme_json() ) { return $editor_settings; } diff --git a/src/wp-includes/theme-templates.php b/src/wp-includes/theme-templates.php index 7ef010eea5ffd..ae71852a370e0 100644 --- a/src/wp-includes/theme-templates.php +++ b/src/wp-includes/theme-templates.php @@ -209,7 +209,7 @@ function the_block_template_skip_link() { * @since 5.8.0 */ function wp_enable_block_templates() { - if ( wp_is_block_theme() || WP_Theme_JSON_Resolver::theme_has_support() ) { + if ( wp_is_block_theme() || wp_theme_has_theme_json() ) { add_theme_support( 'block-templates' ); } } diff --git a/tests/phpunit/data/themedir1/block-theme-child-no-theme-json/style.css b/tests/phpunit/data/themedir1/block-theme-child-no-theme-json/style.css new file mode 100644 index 0000000000000..344441288258e --- /dev/null +++ b/tests/phpunit/data/themedir1/block-theme-child-no-theme-json/style.css @@ -0,0 +1,8 @@ +/* +Theme Name: Block Theme Child with no theme.json +Theme URI: https://wordpress.org/ +Description: For testing purposes only. +Template: block-theme +Version: 1.0.0 +Text Domain: block-theme-child-no-theme-json +*/ diff --git a/tests/phpunit/data/themedir1/default-child-no-theme-json/style.css b/tests/phpunit/data/themedir1/default-child-no-theme-json/style.css new file mode 100644 index 0000000000000..8f971990d9fec --- /dev/null +++ b/tests/phpunit/data/themedir1/default-child-no-theme-json/style.css @@ -0,0 +1,8 @@ +/* +Theme Name: Default Child Theme with no theme.json +Theme URI: https://wordpress.org/ +Description: For testing purposes only. +Template: default +Version: 1.0.0 +Text Domain: default-child-no-theme-json +*/ diff --git a/tests/phpunit/includes/abstract-testcase.php b/tests/phpunit/includes/abstract-testcase.php index bae787a42dee3..fa048cdfa703a 100644 --- a/tests/phpunit/includes/abstract-testcase.php +++ b/tests/phpunit/includes/abstract-testcase.php @@ -401,7 +401,7 @@ public static function flush_cache() { ) ); - wp_cache_add_non_persistent_groups( array( 'counts', 'plugins' ) ); + wp_cache_add_non_persistent_groups( array( 'counts', 'plugins', 'theme_json' ) ); } /** diff --git a/tests/phpunit/tests/theme/themeDir.php b/tests/phpunit/tests/theme/themeDir.php index 29fbbd392f05a..270895c4426bd 100644 --- a/tests/phpunit/tests/theme/themeDir.php +++ b/tests/phpunit/tests/theme/themeDir.php @@ -163,6 +163,7 @@ public function test_theme_list() { $theme_names = array_keys( $themes ); $expected = array( 'WordPress Default', + 'Default Child Theme with no theme.json', 'Sandbox', 'Stylesheet Only', 'My Theme', @@ -177,6 +178,7 @@ public function test_theme_list() { 'REST Theme', 'Block Theme', 'Block Theme Child Theme', + 'Block Theme Child with no theme.json', 'Block Theme Child Theme With Fluid Typography', 'Block Theme [0.4.0]', 'Block Theme [1.0.0] in subdirectory', diff --git a/tests/phpunit/tests/theme/wpThemeHasThemeJson.php b/tests/phpunit/tests/theme/wpThemeHasThemeJson.php new file mode 100644 index 0000000000000..636886c03bbcc --- /dev/null +++ b/tests/phpunit/tests/theme/wpThemeHasThemeJson.php @@ -0,0 +1,72 @@ +assertSame( $expected, wp_theme_has_theme_json() ); + } + + /** + * Data provider. + * + * @return array[] + */ + public function data_theme_has_theme_json_reports_correctly() { + return array( + 'a theme with theme.json' => array( + 'theme' => 'block-theme', + 'expected' => true, + ), + 'a theme without theme.json' => array( + 'theme' => 'default', + 'expected' => false, + ), + 'a child theme with theme.json' => array( + 'theme' => 'block-theme-child', + 'expected' => true, + ), + 'a child theme without theme.json and parent theme with theme.json' => array( + 'theme' => 'block-theme-child-no-theme-json', + 'expected' => true, + ), + 'a child theme without theme.json and parent theme without theme.json' => array( + 'theme' => 'default-child-no-theme-json', + 'expected' => false, + ), + ); + } + + /** + * @ticket 52991 + */ + public function test_switching_themes_recalculates_support() { + // The "default" theme doesn't have theme.json support. + switch_theme( 'default' ); + $default = wp_theme_has_theme_json(); + + // Switch to a theme that does have support. + switch_theme( 'block-theme' ); + $block_theme = wp_theme_has_theme_json(); + + $this->assertFalse( $default, 'The "default" theme should not report theme.json support.' ); + $this->assertTrue( $block_theme, 'The block theme should report theme.json support.' ); + } +} diff --git a/tests/phpunit/tests/theme/wpThemeJsonResolver.php b/tests/phpunit/tests/theme/wpThemeJsonResolver.php index 541699c686172..7db984e9418bd 100644 --- a/tests/phpunit/tests/theme/wpThemeJsonResolver.php +++ b/tests/phpunit/tests/theme/wpThemeJsonResolver.php @@ -110,7 +110,7 @@ public function tear_down() { unset( $GLOBALS['wp_themes'] ); // Reset data between tests. - WP_Theme_JSON_Resolver::clean_cached_data(); + wp_clean_theme_json_cache(); parent::tear_down(); } @@ -376,7 +376,7 @@ public function data_has_same_registered_blocks_when_all_blocks_are_cached() { * @ticket 56467 */ public function test_get_core_data( $should_fire_filter, $core_is_cached, $blocks_are_cached ) { - WP_Theme_JSON_Resolver::clean_cached_data(); + wp_clean_theme_json_cache(); // If should cache core, then fire the method to cache it before running the tests. if ( $core_is_cached ) { @@ -431,22 +431,6 @@ public function data_get_core_data() { ); } - /** - * @ticket 52991 - */ - public function test_switching_themes_recalculates_data() { - // The "default" theme doesn't have theme.json support. - switch_theme( 'default' ); - $default = WP_Theme_JSON_Resolver::theme_has_support(); - - // Switch to a theme that does have support. - switch_theme( 'block-theme' ); - $has_theme_json_support = WP_Theme_JSON_Resolver::theme_has_support(); - - $this->assertFalse( $default ); - $this->assertTrue( $has_theme_json_support ); - } - /** * @ticket 54336 * @ticket 56467 @@ -482,7 +466,7 @@ public function test_add_theme_supports_are_loaded_for_themes_without_theme_json remove_theme_support( 'editor-color-palette' ); remove_theme_support( 'appearance-tools' ); - $this->assertFalse( WP_Theme_JSON_Resolver::theme_has_support() ); + $this->assertFalse( wp_theme_has_theme_json() ); $this->assertTrue( $settings['typography']['lineHeight'] ); $this->assertSame( $color_palette, $settings['color']['palette']['theme'] ); $this->assertTrue( $settings['border']['color'], 'Support for appearance-tools was not added.' ); @@ -643,7 +627,7 @@ function( $query ) use ( &$global_styles_query_count ) { ); for ( $i = 0; $i < 3; $i++ ) { WP_Theme_JSON_Resolver::get_user_data_from_wp_global_styles( $theme ); - WP_Theme_JSON_Resolver::clean_cached_data(); + wp_clean_theme_json_cache(); } $this->assertSame( 0, $global_styles_query_count, 'Unexpected SQL queries detected for the wp_global_style post type prior to creation.' ); @@ -656,7 +640,7 @@ function( $query ) use ( &$global_styles_query_count ) { $global_styles_query_count = 0; for ( $i = 0; $i < 3; $i++ ) { $new_user_cpt = WP_Theme_JSON_Resolver::get_user_data_from_wp_global_styles( $theme ); - WP_Theme_JSON_Resolver::clean_cached_data(); + wp_clean_theme_json_cache(); $this->assertSameSets( $user_cpt, $new_user_cpt, "User CPTs do not match on run {$i}." ); } $this->assertSame( 1, $global_styles_query_count, 'Unexpected SQL queries detected for the wp_global_style post type after creation.' ); @@ -673,7 +657,7 @@ public function test_get_user_data_from_wp_global_styles_does_not_use_uncached_q $query_count = get_num_queries(); for ( $i = 0; $i < 3; $i++ ) { WP_Theme_JSON_Resolver::get_user_data_from_wp_global_styles( $theme ); - WP_Theme_JSON_Resolver::clean_cached_data(); + wp_clean_theme_json_cache(); } $query_count = get_num_queries() - $query_count; $this->assertSame( 0, $query_count, 'Unexpected SQL queries detected for the wp_global_style post type prior to creation.' ); diff --git a/tests/phpunit/tests/webfonts/wpThemeJsonWebfontsHandler.php b/tests/phpunit/tests/webfonts/wpThemeJsonWebfontsHandler.php index 5c609923d7b39..ae7deacc01977 100644 --- a/tests/phpunit/tests/webfonts/wpThemeJsonWebfontsHandler.php +++ b/tests/phpunit/tests/webfonts/wpThemeJsonWebfontsHandler.php @@ -129,7 +129,7 @@ public function data_font_face_not_generated() { private function setup_theme_and_test( $theme_name ) { switch_theme( $theme_name ); do_action( 'after_setup_theme' ); - WP_Theme_JSON_Resolver::clean_cached_data(); + wp_clean_theme_json_cache(); do_action( 'wp_loaded' ); do_action( 'wp_enqueue_scripts' ); }