diff --git a/lib/class-wp-theme-json-resolver-gutenberg.php b/lib/class-wp-theme-json-resolver-gutenberg.php index 189d411db2257..611db4e2891a5 100644 --- a/lib/class-wp-theme-json-resolver-gutenberg.php +++ b/lib/class-wp-theme-json-resolver-gutenberg.php @@ -77,43 +77,35 @@ class WP_Theme_JSON_Resolver_Gutenberg { */ protected static $user_custom_post_type_id = null; - /** - * Container to keep loaded i18n schema for `theme.json`. - * - * @since 5.8.0 As `$theme_json_i18n`. - * @since 5.9.0 Renamed from `$theme_json_i18n` to `$i18n_schema`. - * @var array - */ - protected static $i18n_schema = null; - - /** - * `theme.json` file cache. - * - * @since 6.1.0 - * @var array - */ - protected static $theme_json_file_cache = array(); - /** * Processes a file that adheres to the theme.json schema * and returns an array with its contents, or a void array if none found. * * @since 5.8.0 * @since 6.1.0 Added caching. + * @since 6.5.0 Added persistent caching using object cache, and the `$cache_key` parameter. * * @param string $file_path Path to file. Empty if no file. + * @param string $cache_key Optional. Key to cache the result under. Omitting the parameter results in no caching + * being used. Default empty string. * @return array Contents that adhere to the theme.json schema. */ - protected static function read_json_file( $file_path ) { + protected static function read_json_file( $file_path, $cache_key = '' ) { if ( $file_path ) { - if ( array_key_exists( $file_path, static::$theme_json_file_cache ) ) { - return static::$theme_json_file_cache[ $file_path ]; + $cache_group = 'theme_json_files'; + if ( $cache_key ) { + $decoded_file = wp_cache_get( $cache_key, $cache_group ); + if ( false !== $decoded_file ) { + return $decoded_file; + } } $decoded_file = wp_json_file_decode( $file_path, array( 'associative' => true ) ); if ( is_array( $decoded_file ) ) { - static::$theme_json_file_cache[ $file_path ] = $decoded_file; - return static::$theme_json_file_cache[ $file_path ]; + if ( $cache_key ) { + wp_cache_set( $cache_key, $decoded_file, $cache_group ); + } + return $decoded_file; } } @@ -145,12 +137,22 @@ public static function get_fields_to_translate() { * @return array Returns the modified $theme_json_structure. */ protected static function translate( $theme_json, $domain = 'default' ) { - if ( null === static::$i18n_schema ) { - $i18n_schema = wp_json_file_decode( __DIR__ . '/theme-i18n.json' ); - static::$i18n_schema = null === $i18n_schema ? array() : $i18n_schema; + // Include an unmodified $wp_version. + require ABSPATH . WPINC . '/version.php'; + + $cache_group = 'theme_json_files'; + $cache_key = "i18n_schema_{$wp_version}"; // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable + + $i18n_schema = wp_cache_get( $cache_key, $cache_group ); + + if ( false === $i18n_schema ) { + $i18n_schema = wp_json_file_decode( __DIR__ . '/theme-i18n.json' ); + $i18n_schema = null === $i18n_schema ? array() : $i18n_schema; + + wp_cache_set( $cache_key, $i18n_schema, $cache_group ); } - return translate_settings_using_i18n_schema( static::$i18n_schema, $theme_json, $domain ); + return translate_settings_using_i18n_schema( $i18n_schema, $theme_json, $domain ); } /** @@ -165,7 +167,16 @@ public static function get_core_data() { return static::$core; } - $config = static::read_json_file( __DIR__ . '/theme.json' ); + // Only use cache when not currently developing for core. + $cache_key = ''; + if ( ! wp_is_development_mode( 'core' ) ) { + // Include an unmodified $wp_version. + require ABSPATH . WPINC . '/version.php'; + + $cache_key = "core_{$wp_version}"; // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable + } + + $config = static::read_json_file( __DIR__ . '/theme.json', $cache_key ); $config = static::translate( $config ); /** @@ -173,7 +184,7 @@ public static function get_core_data() { * * @since 6.1.0 * - * @param WP_Theme_JSON_Data Class to access and update the underlying data. + * @param WP_Theme_JSON_Data $theme_json Class to access and update the underlying data. */ $theme_json = apply_filters( 'wp_theme_json_data_default', new WP_Theme_JSON_Data_Gutenberg( $config, 'default' ) ); $config = $theme_json->get_data(); @@ -241,14 +252,10 @@ 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' ) ) { - $wp_theme = wp_get_theme(); - $theme_json_file = $wp_theme->get_file_path( 'theme.json' ); - if ( is_readable( $theme_json_file ) ) { - $theme_json_data = static::read_json_file( $theme_json_file ); - $theme_json_data = static::translate( $theme_json_data, $wp_theme->get( 'TextDomain' ) ); - } else { - $theme_json_data = array(); - } + $wp_theme = wp_get_theme(); + + // Read main theme.json (which may also come from the parent theme). + $raw_theme_json_data = static::read_theme_json_data_for_theme( $wp_theme ); /** * Filters the data provided by the theme for global styles and settings. @@ -257,17 +264,15 @@ public static function get_theme_data( $deprecated = array(), $options = array() * * @param WP_Theme_JSON_Data Class to access and update the underlying data. */ - $theme_json = apply_filters( 'wp_theme_json_data_theme', new WP_Theme_JSON_Data_Gutenberg( $theme_json_data, 'theme' ) ); + $theme_json = apply_filters( 'wp_theme_json_data_theme', new WP_Theme_JSON_Data_Gutenberg( $raw_theme_json_data, 'theme' ) ); $theme_json_data = $theme_json->get_data(); static::$theme = new WP_Theme_JSON_Gutenberg( $theme_json_data ); if ( $wp_theme->parent() ) { - // Get parent theme.json. - $parent_theme_json_file = $wp_theme->parent()->get_file_path( 'theme.json' ); - if ( $theme_json_file !== $parent_theme_json_file && is_readable( $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_theme->parent()->get( 'TextDomain' ) ); - $parent_theme = new WP_Theme_JSON_Gutenberg( $parent_theme_json_data ); + // Read parent theme.json, and only merge it if successful and different from main theme.json data. + $raw_parent_theme_json_data = static::read_theme_json_data_for_theme( $wp_theme->parent() ); + if ( $raw_parent_theme_json_data && $raw_theme_json_data !== $raw_parent_theme_json_data ) { + $parent_theme = new WP_Theme_JSON_Gutenberg( $raw_parent_theme_json_data ); /* * Merge the child theme.json into the parent theme.json. @@ -402,6 +407,45 @@ public static function get_block_data() { return static::$blocks; } + /** + * Returns theme.json data for the given theme. + * + * If the theme has a parent theme, the data may also come from the parent theme's theme.json. + * + * Data will be cached for the theme that the theme.json file belongs to. + * + * @since 6.5.0 + * + * @param WP_Theme $wp_theme Theme instance. + * @return array Raw array of data read from theme.json, or empty array if not readable. + */ + protected static function read_theme_json_data_for_theme( $wp_theme ) { + $theme_json_file = $wp_theme->get_file_path( 'theme.json' ); + + if ( ! is_readable( $theme_json_file ) ) { + return array(); + } + + // If the file found is actually from the parent theme, cache it for the parent theme instead. + if ( $wp_theme->parent() ) { + $parent_theme_json_file = $wp_theme->parent()->get_file_path( 'theme.json' ); + if ( $theme_json_file === $parent_theme_json_file ) { + $wp_theme = $wp_theme->parent(); + } + } + + // Only use cache when not currently developing the theme. + $cache_key = ''; + if ( ! wp_is_development_mode( 'theme' ) ) { + $cache_key = "theme_{$wp_theme->stylesheet}_{$wp_theme->version}"; + } + + $theme_json_data = static::read_json_file( $theme_json_file, $cache_key ); + $theme_json_data = static::translate( $theme_json_data, $wp_theme->get( 'TextDomain' ) ); + + return $theme_json_data; + } + /** * When given an array, this will remove any keys with the name `//`. * @@ -683,6 +727,8 @@ protected static function get_file_path_from_theme( $file_name, $template = fals * and `$i18n_schema` variables to reset. * @since 6.1.0 Added the `$blocks` and `$blocks_cache` variables * to reset. + * @since 6.5.0 Modified resetting of i18n schema to use cache + * instead of variable. */ public static function clean_cached_data() { static::$core = null; @@ -696,7 +742,13 @@ public static function clean_cached_data() { static::$theme = null; static::$user = null; static::$user_custom_post_type_id = null; - static::$i18n_schema = null; + + // Include an unmodified $wp_version. + require ABSPATH . WPINC . '/version.php'; + + $cache_group = 'theme_json_files'; + $cache_key = "i18n_schema_{$wp_version}"; // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable + wp_cache_delete( $cache_key, $cache_group ); } /**