Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use persistent caching around _only_ the theme.json files #5267

Draft
wants to merge 10 commits into
base: trunk
Choose a base branch
from
138 changes: 95 additions & 43 deletions src/wp-includes/class-wp-theme-json-resolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -74,43 +74,35 @@ class WP_Theme_JSON_Resolver {
*/
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.
*
* @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;
}
}

Expand Down Expand Up @@ -142,12 +134,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}";

$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 );
}

/**
Expand All @@ -162,7 +164,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}";
}

$config = static::read_json_file( __DIR__ . '/theme.json', $cache_key );
$config = static::translate( $config );

/**
Expand Down Expand Up @@ -238,14 +249,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 );
Comment on lines -241 to +255
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that this change here actually includes a bug fix for something I just noticed while working on it:

  • WP_Theme::get_file_path() may return the parent theme's theme.json file.
  • But then below it is translated using the child theme's text domain, which will lead to translations not to work correctly given the text domain is probably different for the parent and child theme.

The way I have updated the code now this is no longer a problem.


/**
* Filters the data provided by the theme for global styles and settings.
Expand All @@ -254,17 +261,15 @@ public static function get_theme_data( $deprecated = array(), $options = array()
*
* @param WP_Theme_JSON_Data $theme_json Class to access and update the underlying data.
*/
$theme_json = apply_filters( 'wp_theme_json_data_theme', new WP_Theme_JSON_Data( $theme_json_data, 'theme' ) );
$theme_json = apply_filters( 'wp_theme_json_data_theme', new WP_Theme_JSON_Data( $raw_theme_json_data, 'theme' ) );
$theme_json_data = $theme_json->get_data();
static::$theme = new WP_Theme_JSON( $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( $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( $raw_parent_theme_json_data );

/*
* Merge the child theme.json into the parent theme.json.
Expand Down Expand Up @@ -380,6 +385,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 `//`.
*
Expand Down Expand Up @@ -664,6 +708,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;
Expand All @@ -677,7 +723,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}";
wp_cache_delete( $cache_key, $cache_group );
}

/**
Expand Down
12 changes: 6 additions & 6 deletions tests/phpunit/tests/theme/wpThemeJsonResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -756,19 +756,19 @@ public function test_get_theme_data_does_not_parse_theme_json_if_not_present() {

$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, 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.' );

// Include an unmodified $wp_version.
require ABSPATH . WPINC . '/version.php';
$cache_group = 'theme_json_files';
$cache_key = "i18n_schema_{$wp_version}";
$this->assertFalse( wp_cache_get( $cache_key, $cache_group ), 'Theme i18n schema should not have been loaded without theme support.' );
}

/**
Expand Down
Loading