-
Notifications
You must be signed in to change notification settings - Fork 4.2k
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
Persistently cache theme.json
files via WP Cache API
#56095
base: trunk
Are you sure you want to change the base?
Changes from all commits
8f586b5
1d17bda
d299fa4
42f8bc2
4d4542c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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'; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @joemcgill Related to https://core.trac.wordpress.org/ticket/59719#comment:18, what is your thought on this?
I think we have 3 options here:
|
||
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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @krupal-panchal I don't think it's needed for a single condition like this? I'm all for readability, but I personally find parentheses like this only helpful if there's more than one conditions in a ternary operator. Also worth noting this line is already in WordPress core in exactly the same format, so I'm not sure we should change it here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Okay, not an issue. LGTM also. |
||
|
||
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,15 +167,24 @@ 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 ); | ||
|
||
/** | ||
* Filters the default data provided by WordPress for global styles & settings. | ||
* | ||
* @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 ); | ||
} | ||
|
||
/** | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We might need to keep them if someone is extending the class and expects these to exist.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think that matters here as the class is marked private. It must not be overwritten for this purpose, and if anyone does so it's at their own risk (similar to e.g. using "experimental" APIs in Gutenberg).