diff --git a/src/wp-includes/class-wp-theme-json-resolver.php b/src/wp-includes/class-wp-theme-json-resolver.php index cf14486f6770d..c166283af30f1 100644 --- a/src/wp-includes/class-wp-theme-json-resolver.php +++ b/src/wp-includes/class-wp-theme-json-resolver.php @@ -246,8 +246,13 @@ 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' ) ) { - $theme_json_data = static::read_json_file( static::get_file_path_from_theme( 'theme.json' ) ); - $theme_json_data = static::translate( $theme_json_data, wp_get_theme()->get( 'TextDomain' ) ); + $theme_json_file = static::get_file_path_from_theme( 'theme.json' ); + if ( '' !== $theme_json_file ) { + $theme_json_data = static::read_json_file( $theme_json_file ); + $theme_json_data = static::translate( $theme_json_data, wp_get_theme()->get( 'TextDomain' ) ); + } else { + $theme_json_data = array(); + } /** * Filters the data provided by the theme for global styles and settings. @@ -259,20 +264,23 @@ public static function get_theme_data( $deprecated = array(), $options = array() $theme_json = apply_filters( 'wp_theme_json_data_theme', new WP_Theme_JSON_Data( $theme_json_data, 'theme' ) ); $theme_json_data = $theme_json->get_data(); static::$theme = new WP_Theme_JSON( $theme_json_data ); - } - - if ( wp_get_theme()->parent() ) { - // Get parent theme.json. - $parent_theme_json_data = static::read_json_file( static::get_file_path_from_theme( 'theme.json', true ) ); - $parent_theme_json_data = static::translate( $parent_theme_json_data, wp_get_theme()->parent()->get( 'TextDomain' ) ); - $parent_theme = new WP_Theme_JSON( $parent_theme_json_data ); - /* - * Merge the child theme.json into the parent theme.json. - * The child theme takes precedence over the parent. - */ - $parent_theme->merge( static::$theme ); - static::$theme = $parent_theme; + if ( wp_get_theme()->parent() ) { + // Get parent theme.json. + $parent_theme_json_file = static::get_file_path_from_theme( 'theme.json', true ); + if ( '' !== $parent_theme_json_file ) { + $parent_theme_json_data = static::read_json_file( $parent_theme_json_file ); + $parent_theme_json_data = static::translate( $parent_theme_json_data, wp_get_theme()->parent()->get( 'TextDomain' ) ); + $parent_theme = new WP_Theme_JSON( $parent_theme_json_data ); + + /* + * Merge the child theme.json into the parent theme.json. + * The child theme takes precedence over the parent. + */ + $parent_theme->merge( static::$theme ); + static::$theme = $parent_theme; + } + } } if ( ! $options['with_supports'] ) { @@ -403,6 +411,18 @@ public static function get_user_data_from_wp_global_styles( $theme, $create_post if ( ! $theme instanceof WP_Theme ) { $theme = wp_get_theme(); } + + /* + * Bail early if the theme does not support a theme.json. + * + * Since WP_Theme_JSON_Resolver::theme_has_support() 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() ) { + return array(); + } + $user_cpt = array(); $post_type_filter = 'wp_global_styles'; $stylesheet = $theme->get_stylesheet(); @@ -582,8 +602,8 @@ public static function get_user_global_styles_post_id() { public static function theme_has_support() { if ( ! isset( static::$theme_has_support ) ) { static::$theme_has_support = ( - is_readable( static::get_file_path_from_theme( 'theme.json' ) ) || - is_readable( static::get_file_path_from_theme( 'theme.json', true ) ) + static::get_file_path_from_theme( 'theme.json' ) !== '' || + static::get_file_path_from_theme( 'theme.json', true ) !== '' ); } diff --git a/src/wp-includes/class-wp-theme-json.php b/src/wp-includes/class-wp-theme-json.php index d0fcdeb52a3c6..df5bd20d84917 100644 --- a/src/wp-includes/class-wp-theme-json.php +++ b/src/wp-includes/class-wp-theme-json.php @@ -2939,7 +2939,8 @@ public function get_data() { public function set_spacing_sizes() { $spacing_scale = _wp_array_get( $this->theme_json, array( 'settings', 'spacing', 'spacingScale' ), array() ); - if ( ! is_numeric( $spacing_scale['steps'] ) + if ( ! isset( $spacing_scale['steps'] ) + || ! is_numeric( $spacing_scale['steps'] ) || ! isset( $spacing_scale['mediumStep'] ) || ! isset( $spacing_scale['unit'] ) || ! isset( $spacing_scale['operator'] ) diff --git a/tests/phpunit/tests/theme/wpThemeJsonResolver.php b/tests/phpunit/tests/theme/wpThemeJsonResolver.php index 524fade2d8f85..1cbc519255cf4 100644 --- a/tests/phpunit/tests/theme/wpThemeJsonResolver.php +++ b/tests/phpunit/tests/theme/wpThemeJsonResolver.php @@ -33,13 +33,6 @@ class Tests_Theme_wpThemeJsonResolver extends WP_UnitTestCase { */ private $orig_theme_dir; - /** - * Queries. - * - * @var array - */ - private $queries = array(); - /** * WP_Theme_JSON_Resolver::$blocks_cache property. * @@ -105,7 +98,7 @@ public function set_up() { add_filter( 'theme_root', array( $this, 'filter_set_theme_root' ) ); add_filter( 'stylesheet_root', array( $this, 'filter_set_theme_root' ) ); add_filter( 'template_root', array( $this, 'filter_set_theme_root' ) ); - $this->queries = array(); + // Clear caches. wp_clean_themes_cache(); unset( $GLOBALS['wp_themes'] ); @@ -129,13 +122,6 @@ public function filter_set_locale_to_polish() { return 'pl_PL'; } - function filter_db_query( $query ) { - if ( preg_match( '#post_type = \'wp_global_styles\'#', $query ) ) { - $this->queries[] = $query; - } - return $query; - } - /** * @ticket 52991 * @ticket 54336 @@ -634,17 +620,26 @@ function test_merges_child_theme_json_into_parent_theme_json() { * @covers WP_Theme_JSON_Resolver::get_user_data_from_wp_global_styles */ function test_get_user_data_from_wp_global_styles_does_not_use_uncached_queries() { + // Switch to a theme that does have support. + switch_theme( 'block-theme' ); wp_set_current_user( self::$administrator_id ); $theme = wp_get_theme(); WP_Theme_JSON_Resolver::get_user_data_from_wp_global_styles( $theme ); - add_filter( 'query', array( $this, 'filter_db_query' ) ); - $query_count = count( $this->queries ); + $global_styles_query_count = 0; + add_filter( + 'query', + function( $query ) use ( &$global_styles_query_count ) { + if ( preg_match( '#post_type = \'wp_global_styles\'#', $query ) ) { + $global_styles_query_count++; + } + return $query; + } + ); 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(); } - $query_count = count( $this->queries ) - $query_count; - $this->assertSame( 0, $query_count, 'Unexpected SQL queries detected for the wp_global_style post type prior to creation.' ); + $this->assertSame( 0, $global_styles_query_count, 'Unexpected SQL queries detected for the wp_global_style post type prior to creation.' ); $user_cpt = WP_Theme_JSON_Resolver::get_user_data_from_wp_global_styles( $theme ); $this->assertEmpty( $user_cpt, 'User CPT is expected to be empty.' ); @@ -652,40 +647,64 @@ function test_get_user_data_from_wp_global_styles_does_not_use_uncached_queries( $user_cpt = WP_Theme_JSON_Resolver::get_user_data_from_wp_global_styles( $theme, true ); $this->assertNotEmpty( $user_cpt, 'User CPT is expected not to be empty.' ); - $query_count = count( $this->queries ); + $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(); $this->assertSameSets( $user_cpt, $new_user_cpt, "User CPTs do not match on run {$i}." ); } - $query_count = count( $this->queries ) - $query_count; - $this->assertSame( 1, $query_count, 'Unexpected SQL queries detected for the wp_global_style post type after creation.' ); + $this->assertSame( 1, $global_styles_query_count, 'Unexpected SQL queries detected for the wp_global_style post type after creation.' ); } /** * @covers WP_Theme_JSON_Resolver::get_user_data_from_wp_global_styles */ function test_get_user_data_from_wp_global_styles_does_not_use_uncached_queries_for_logged_out_users() { + // Switch to a theme that does have support. + switch_theme( 'block-theme' ); $theme = wp_get_theme(); WP_Theme_JSON_Resolver::get_user_data_from_wp_global_styles( $theme ); - add_filter( 'query', array( $this, 'filter_db_query' ) ); - $query_count = count( $this->queries ); + $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(); } - $query_count = count( $this->queries ) - $query_count; + $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.' ); $user_cpt = WP_Theme_JSON_Resolver::get_user_data_from_wp_global_styles( $theme ); $this->assertEmpty( $user_cpt, 'User CPT is expected to be empty.' ); } + /** + * @ticket 56945 + * @covers WP_Theme_JSON_Resolver::get_user_data_from_wp_global_styles + */ + function test_get_user_data_from_wp_global_styles_does_not_run_for_theme_without_support() { + // The 'default' theme does not support theme.json. + switch_theme( 'default' ); + wp_set_current_user( self::$administrator_id ); + $theme = wp_get_theme(); + + $start_queries = get_num_queries(); + + // When theme.json is not supported, the method should not run a query and always return an empty result. + $user_cpt = WP_Theme_JSON_Resolver::get_user_data_from_wp_global_styles( $theme ); + $this->assertEmpty( $user_cpt, 'User CPT is expected to be empty.' ); + $this->assertSame( 0, get_num_queries() - $start_queries, 'Unexpected SQL query detected for theme without theme.json support.' ); + + $user_cpt = WP_Theme_JSON_Resolver::get_user_data_from_wp_global_styles( $theme, true ); + $this->assertEmpty( $user_cpt, 'User CPT is expected to be empty.' ); + $this->assertSame( 0, get_num_queries() - $start_queries, 'Unexpected SQL query detected for theme without theme.json support.' ); + } + /** * @ticket 55392 * @covers WP_Theme_JSON_Resolver::get_user_data_from_wp_global_styles */ function test_get_user_data_from_wp_global_styles_does_exist() { + // Switch to a theme that does have support. + switch_theme( 'block-theme' ); $theme = wp_get_theme(); $post1 = WP_Theme_JSON_Resolver::get_user_data_from_wp_global_styles( $theme, true ); $this->assertIsArray( $post1 ); @@ -701,6 +720,8 @@ function test_get_user_data_from_wp_global_styles_does_exist() { * @covers WP_Theme_JSON_Resolver::get_user_data_from_wp_global_styles */ function test_get_user_data_from_wp_global_styles_create_post() { + // Switch to a theme that does have support. + switch_theme( 'block-theme' ); $theme = wp_get_theme( 'testing' ); $post1 = WP_Theme_JSON_Resolver::get_user_data_from_wp_global_styles( $theme ); $this->assertIsArray( $post1 ); @@ -718,6 +739,8 @@ function test_get_user_data_from_wp_global_styles_create_post() { * @covers WP_Theme_JSON_Resolver::get_user_data_from_wp_global_styles */ function test_get_user_data_from_wp_global_styles_filter_state() { + // Switch to a theme that does have support. + switch_theme( 'block-theme' ); $theme = wp_get_theme( 'foo' ); $post1 = WP_Theme_JSON_Resolver::get_user_data_from_wp_global_styles( $theme, true, array( 'publish' ) ); $this->assertIsArray( $post1 ); @@ -749,4 +772,29 @@ function test_get_theme_data_theme_supports_overrides_theme_json() { $line_height = $current_settings['typography']['lineHeight']; $this->assertTrue( $line_height, 'lineHeight setting after add_theme_support() should be true.' ); } + + /** + * @ticket 56945 + * @covers WP_Theme_JSON_Resolver::get_theme_data + */ + function test_get_theme_data_does_not_parse_theme_json_if_not_present() { + // The 'default' theme does not support theme.json. + switch_theme( 'default' ); + + $theme_json_resolver = new WP_Theme_JSON_Resolver(); + + // Force-unset $i18n_schema property to "unload" translation schema. + $property = new ReflectionProperty( $theme_json_resolver, 'i18n_schema' ); + $property->setAccessible( true ); + $property->setValue( null ); + + // A completely empty theme.json data set still has the 'version' key when parsed. + $empty_theme_json = array( 'version' => WP_Theme_JSON::LATEST_SCHEMA ); + + // Call using 'with_supports' set to false, so that the method only considers theme.json. + $theme_data = $theme_json_resolver->get_theme_data( array(), array( 'with_supports' => false ) ); + $this->assertInstanceOf( 'WP_Theme_JSON', $theme_data, 'Theme data should be an instance of WP_Theme_JSON.' ); + $this->assertSame( $empty_theme_json, $theme_data->get_raw_data(), 'Theme data should be empty without theme support.' ); + $this->assertNull( $property->getValue(), 'Theme i18n schema should not have been loaded without theme support.' ); + } }