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

Global Styles: Update gutenberg_get_global_stylesheet to use WP_Object_Cache #45679

Merged
merged 27 commits into from
Nov 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
fad27ea
Global Styles: Update `gutenberg_get_global_stylesheet` to use `WP_Ob…
mmtr Nov 9, 2022
0a23b43
Add remaining hooks
mmtr Nov 9, 2022
714819b
Simplify test setup
mmtr Nov 9, 2022
4f36f72
Rename cache key and functions
mmtr Nov 9, 2022
0f80d71
Merge remote-tracking branch 'origin/trunk' into update/global-styles…
mmtr Nov 16, 2022
a845e85
Add filter to shortcircuit the cache
mmtr Nov 16, 2022
6014519
Clean cache within `wp_theme_clean_theme_json_cached_data`
mmtr Nov 16, 2022
5acfd40
Fix test
mmtr Nov 16, 2022
4eaeb71
Add missing params in docstring
mmtr Nov 16, 2022
e8a32cb
Create separate function to clean the cache
mmtr Nov 16, 2022
6931aa8
Remove SCRIPT_DEBUG check since it is not usually part of the theme d…
mmtr Nov 16, 2022
a2b9d6f
Clear cache from WP_Theme_JSON_Resolver separately
mmtr Nov 16, 2022
2acbafd
Rename filter
mmtr Nov 16, 2022
30361b5
Fix linting issues
mmtr Nov 16, 2022
b38b5b8
Merge remote-tracking branch 'origin/trunk' into update/global-styles…
mmtr Nov 17, 2022
0a371d4
Use WP version in since annotation
mmtr Nov 17, 2022
18cd94f
Check parent theme (props @felixarntz)
mmtr Nov 17, 2022
8e2ffb8
Invalidate WP_Theme_JSON_Resolver_Gutenberg cache after toggling a pl…
mmtr Nov 17, 2022
18f1443
Invalidate WP_Theme_JSON_Resolver_Gutenberg cache after an upgrade
mmtr Nov 17, 2022
4f1a1b8
Remove @since 6.2.0 annotations as we don't know which WP version wil…
mmtr Nov 17, 2022
795045a
Merge remote-tracking branch 'origin/trunk' into update/global-styles…
mmtr Nov 17, 2022
c9df6b4
Merge remote-tracking branch 'origin/trunk' into update/global-styles…
mmtr Nov 17, 2022
88c1e33
Update phpunit/wp-get-global-stylesheet-test.php
mmtr Nov 17, 2022
9dcdde7
Add missing params to actions
mmtr Nov 18, 2022
9321aca
Merge remote-tracking branch 'origin/trunk' into update/global-styles…
mmtr Nov 21, 2022
8f642a4
Merge remote-tracking branch 'origin/trunk' into update/global-styles…
mmtr Nov 22, 2022
2b16326
Remove filter
mmtr Nov 22, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 0 additions & 85 deletions lib/compat/wordpress-6.1/get-global-styles-and-settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,88 +54,3 @@ function ( $item ) {
}
}
}

/**
* Returns the stylesheet resulting of merging core, theme, and user data.
*
* @param array $types Types of styles to load. Optional.
* It accepts 'variables', 'styles', 'presets' as values.
* If empty, it'll load all for themes with theme.json support
* and only [ 'variables', 'presets' ] for themes without theme.json support.
*
* @return string Stylesheet.
*/
function gutenberg_get_global_stylesheet( $types = array() ) {
// Return cached value if it can be used and exists.
// It's cached by theme to make sure that theme switching clears the cache.
$can_use_cached = (
Copy link
Member

@oandregal oandregal Nov 17, 2022

Choose a reason for hiding this comment

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

This logic is changing:

  • ✔️ adds a filter.
  • ✔️ keeps empty( $types ).
  • ✔️ keeps the WP_DEBUG check.
  • ✔️ removes the SCRIPT_DEBUG. WP_DEBUG should be enough.
  • ✔️ removes is_admin. I don't know why we started adding is_admin in a few places (in this function, in the SVG filters cache, and webfonts). From the cache management point of view, the way it works now is that admin users won't use the cache, no matter the other conditions. I don't see a reason why, other than perhaps circumventing the issues introduced by the transient (styles were not immediately applied in the front-end). I think it's safe to remove.
  • 🟡 removes REST_REQUEST. I don't know why this logic was present and why we cannot cache REST requests. After some searching, I found the PR that first introduced this logic. The SVG and webfonts PRs came later, so they probably followed what they saw. I asked there for more context.

Copy link
Member

Choose a reason for hiding this comment

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

Got a response from Ari: he says the rest request check was probably for the editor (it was many moons ago, so it's hard for anyone to remember exactly). Going to investigate this a bit.

Copy link
Member

Choose a reason for hiding this comment

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

I only know see places in which we use a REST Request with this data:

Copy link
Member

Choose a reason for hiding this comment

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

@geriux and @fluiddot would you be able to test if this PR, as it is, works for you as well? If so, we can safely remove it.

Sure! I will test it and report back 👍

Copy link
Member

Choose a reason for hiding this comment

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

I've just tested these changes and the mobile endpoint works correctly ✅ Thanks for the ping!

( empty( $types ) ) &&
( ! defined( 'WP_DEBUG' ) || ! WP_DEBUG ) &&
( ! defined( 'SCRIPT_DEBUG' ) || ! SCRIPT_DEBUG ) &&
( ! defined( 'REST_REQUEST' ) || ! REST_REQUEST ) &&
! is_admin()
);
$transient_name = 'gutenberg_global_styles_' . get_stylesheet();
if ( $can_use_cached ) {
$cached = get_transient( $transient_name );
if ( $cached ) {
return $cached;
}
}
$tree = WP_Theme_JSON_Resolver_Gutenberg::get_merged_data();
$supports_theme_json = wp_theme_has_theme_json();
if ( empty( $types ) && ! $supports_theme_json ) {
$types = array( 'variables', 'presets', 'base-layout-styles' );
} elseif ( empty( $types ) ) {
$types = array( 'variables', 'styles', 'presets' );
}

/*
* If variables are part of the stylesheet,
* we add them.
*
* This is so themes without a theme.json still work as before 5.9:
* they can override the default presets.
* See https://core.trac.wordpress.org/ticket/54782
*/
$styles_variables = '';
if ( in_array( 'variables', $types, true ) ) {
/*
* We only use the default, theme, and custom origins.
* This is because styles for blocks origin are added
* at a later phase (render cycle) so we only render the ones in use.
* @see wp_add_global_styles_for_blocks
*/
$origins = array( 'default', 'theme', 'custom' );
$styles_variables = $tree->get_stylesheet( array( 'variables' ), $origins );
$types = array_diff( $types, array( 'variables' ) );
}

/*
* For the remaining types (presets, styles), we do consider origins:
*
* - themes without theme.json: only the classes for the presets defined by core
* - themes with theme.json: the presets and styles classes, both from core and the theme
*/
$styles_rest = '';
if ( ! empty( $types ) ) {
/*
* We only use the default, theme, and custom origins.
* This is because styles for blocks origin are added
* at a later phase (render cycle) so we only render the ones in use.
* @see wp_add_global_styles_for_blocks
*/
$origins = array( 'default', 'theme', 'custom' );
if ( ! $supports_theme_json ) {
$origins = array( 'default' );
}
$styles_rest = $tree->get_stylesheet( $types, $origins );
}
$stylesheet = $styles_variables . $styles_rest;
if ( $can_use_cached ) {
// Cache for a minute.
// This cache doesn't need to be any longer, we only want to avoid spikes on high-traffic sites.
set_transient( $transient_name, $stylesheet, MINUTE_IN_SECONDS );
}
return $stylesheet;
}
25 changes: 25 additions & 0 deletions lib/compat/wordpress-6.2/class-wp-theme-json-resolver-6-2.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,29 @@ public static function theme_has_support() {
return wp_theme_has_theme_json();
}

/**
* Private method to clean the cached data after an upgrade.
*
* It is hooked into the `upgrader_process_complete` action.
*
* @see default-filters.php
*
* @param WP_Upgrader $upgrader WP_Upgrader instance.
* @param array $options Array of bulk item update data.
*/
public static function _clean_cached_data_upon_upgrading( $upgrader, $options ) {
Copy link
Member

Choose a reason for hiding this comment

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

Let stop adding static methods to this class. They are not in part of the WordPress coding standards and are harder to test.

Copy link
Member

Choose a reason for hiding this comment

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

I've done a quick search for this pattern and there are multiple cases where this pattern is used:

I won't list them all, but these should serve to highlight that there are different sensibilities around this. Given this code is architected this way, and it is a very common pattern, I don't think we should do it differently.

if ( 'update' !== $options['action'] ) {
return;
}

if (
'core' === $options['type'] ||
'plugin' === $options['type'] ||
// Clean cache only if the active theme was updated.
( 'theme' === $options['type'] && ( isset( $options['themes'][ get_stylesheet() ] ) || isset( $options['themes'][ get_template() ] ) ) )
) {
static::clean_cached_data();
}
}

}
10 changes: 10 additions & 0 deletions lib/compat/wordpress-6.2/default-filters.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,13 @@
add_action( 'switch_theme', 'wp_theme_has_theme_json_clean_cache' );
add_action( 'start_previewing_theme', 'wp_theme_has_theme_json_clean_cache' );
add_action( 'upgrader_process_complete', '_wp_theme_has_theme_json_clean_cache_upon_upgrading_active_theme', 10, 2 );
add_action( 'save_post_wp_global_styles', array( 'WP_Theme_JSON_Resolver_Gutenberg', 'clean_cached_data' ) );
add_action( 'activated_plugin', array( 'WP_Theme_JSON_Resolver_Gutenberg', 'clean_cached_data' ) );
add_action( 'deactivated_plugin', array( 'WP_Theme_JSON_Resolver_Gutenberg', 'clean_cached_data' ) );
add_action( 'upgrader_process_complete', array( 'WP_Theme_JSON_Resolver_Gutenberg', '_clean_cached_data_upon_upgrading', 10, 2 ) );
add_action( 'save_post_wp_global_styles', 'gutenberg_get_global_stylesheet_clean_cache' );
add_action( 'switch_theme', 'gutenberg_get_global_stylesheet_clean_cache' );
add_action( 'start_previewing_theme', 'gutenberg_get_global_stylesheet_clean_cache' );
add_action( 'activated_plugin', 'gutenberg_get_global_stylesheet_clean_cache' );
add_action( 'deactivated_plugin', 'gutenberg_get_global_stylesheet_clean_cache' );
add_action( 'upgrader_process_complete', '_gutenberg_get_global_stylesheet_clean_cache_upon_upgrading', 10, 2 );
110 changes: 110 additions & 0 deletions lib/compat/wordpress-6.2/get-global-styles-and-settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -79,3 +79,113 @@ function _wp_theme_has_theme_json_clean_cache_upon_upgrading_active_theme( $upgr
}
}
}

/**
* Returns the stylesheet resulting of merging core, theme, and user data.
*
* @param array $types Types of styles to load. Optional.
* It accepts 'variables', 'styles', 'presets' as values.
* If empty, it'll load all for themes with theme.json support
* and only [ 'variables', 'presets' ] for themes without theme.json support.
*
* @return string Stylesheet.
*/
function gutenberg_get_global_stylesheet( $types = array() ) {
// Ignore cache when `WP_DEBUG` is enabled, so it doesn't interfere with the theme developers workflow.
$can_use_cached = empty( $types ) && ( ! defined( 'WP_DEBUG' ) || ! WP_DEBUG );
$cache_key = 'gutenberg_get_global_stylesheet';
mmtr marked this conversation as resolved.
Show resolved Hide resolved
$cache_group = 'theme_json';
if ( $can_use_cached ) {
$cached = wp_cache_get( $cache_key, $cache_group );
if ( $cached ) {
return $cached;
}
}
$tree = WP_Theme_JSON_Resolver_Gutenberg::get_merged_data();
$supports_theme_json = wp_theme_has_theme_json();
if ( empty( $types ) && ! $supports_theme_json ) {
$types = array( 'variables', 'presets', 'base-layout-styles' );
} elseif ( empty( $types ) ) {
$types = array( 'variables', 'styles', 'presets' );
}

/*
* If variables are part of the stylesheet,
* we add them.
*
* This is so themes without a theme.json still work as before 5.9:
* they can override the default presets.
* See https://core.trac.wordpress.org/ticket/54782
*/
$styles_variables = '';
if ( in_array( 'variables', $types, true ) ) {
/*
* We only use the default, theme, and custom origins.
* This is because styles for blocks origin are added
* at a later phase (render cycle) so we only render the ones in use.
* @see wp_add_global_styles_for_blocks
*/
$origins = array( 'default', 'theme', 'custom' );
$styles_variables = $tree->get_stylesheet( array( 'variables' ), $origins );
$types = array_diff( $types, array( 'variables' ) );
}

/*
* For the remaining types (presets, styles), we do consider origins:
*
* - themes without theme.json: only the classes for the presets defined by core
* - themes with theme.json: the presets and styles classes, both from core and the theme
*/
$styles_rest = '';
if ( ! empty( $types ) ) {
/*
* We only use the default, theme, and custom origins.
* This is because styles for blocks origin are added
* at a later phase (render cycle) so we only render the ones in use.
* @see wp_add_global_styles_for_blocks
*/
$origins = array( 'default', 'theme', 'custom' );
if ( ! $supports_theme_json ) {
$origins = array( 'default' );
}
$styles_rest = $tree->get_stylesheet( $types, $origins );
}
$stylesheet = $styles_variables . $styles_rest;
if ( $can_use_cached ) {
wp_cache_set( $cache_key, $stylesheet, $cache_group );
}
return $stylesheet;
}

/**
* Clean the cache used by the `gutenberg_get_global_stylesheet` function.
*/
function gutenberg_get_global_stylesheet_clean_cache() {
wp_cache_delete( 'gutenberg_get_global_stylesheet', 'theme_json' );
}

/**
* Private function to clean the cache used by the `gutenberg_get_global_stylesheet` function after an upgrade.
*
* It is hooked into the `upgrader_process_complete` action.
*
* @see default-filters.php
*
* @param WP_Upgrader $upgrader WP_Upgrader instance.
* @param array $options Array of bulk item update data.
*/
function _gutenberg_get_global_stylesheet_clean_cache_upon_upgrading( $upgrader, $options ) {
if ( 'update' !== $options['action'] ) {
return;
}

if (
'core' === $options['type'] ||
'plugin' === $options['type'] ||
// Clean cache only if the active theme was updated.
( 'theme' === $options['type'] && ( isset( $options['themes'][ get_stylesheet() ] ) || isset( $options['themes'][ get_template() ] ) ) )
) {
gutenberg_get_global_stylesheet_clean_cache();
}
}

98 changes: 98 additions & 0 deletions phpunit/wp-get-global-stylesheet-test.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
<?php
/**
* Tests wp_get_global_stylesheet().
*
* @package Gutenberg
*/

class WP_Get_Global_Stylesheet_Test extends WP_UnitTestCase {

/**
* Administrator ID.
*
* @var int
*/
protected static $administrator_id;

/**
* Theme root directory.
*
* @var string
*/
private $theme_root;

/**
* Original theme directory.
*
* @var string
*/
private $orig_theme_dir;

public static function set_up_before_class() {
parent::set_up_before_class();

self::$administrator_id = self::factory()->user->create(
array(
'role' => 'administrator',
'user_email' => 'administrator@example.com',
)
);
}

public function set_up() {
parent::set_up();

$this->orig_theme_dir = $GLOBALS['wp_theme_directories'];
$this->theme_root = realpath( DIR_TESTDATA . '/themedir1' );

// /themes is necessary as theme.php functions assume /themes is the root if there is only one root.
$GLOBALS['wp_theme_directories'] = array( WP_CONTENT_DIR . '/themes', $this->theme_root );

// Set up the new root.
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' ) );

// Clear caches.
wp_clean_themes_cache();
unset( $GLOBALS['wp_themes'] );
}

public function tear_down() {
$GLOBALS['wp_theme_directories'] = $this->orig_theme_dir;

// Clear up the filters to modify the theme root.
remove_filter( 'theme_root', array( $this, 'filter_set_theme_root' ) );
remove_filter( 'stylesheet_root', array( $this, 'filter_set_theme_root' ) );
remove_filter( 'template_root', array( $this, 'filter_set_theme_root' ) );

wp_clean_themes_cache();
unset( $GLOBALS['wp_themes'] );

parent::tear_down();
}

public function filter_set_theme_root() {
return $this->theme_root;
}

public function test_global_styles_user_cpt_change_invalidates_cached_stylesheet() {
add_filter( 'wp_get_global_stylesheet_can_use_cache', '__return_true' );
switch_theme( 'block-theme' );
wp_set_current_user( self::$administrator_id );
Copy link
Member

Choose a reason for hiding this comment

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

Documenting something Miguel fixed for posterity: we need to make the petition as an admin, otherwise WP_Theme_JSON_Resolver_Gutenberg::get_user_data_from_wp_global_styles returns an empty post.


$styles = gutenberg_get_global_stylesheet();
$this->assertStringNotContainsString( 'background-color: hotpink;', $styles );

$user_cpt = WP_Theme_JSON_Resolver_Gutenberg::get_user_data_from_wp_global_styles( wp_get_theme(), true );
$config = json_decode( $user_cpt['post_content'], true );
$config['styles']['color']['background'] = 'hotpink';
$user_cpt['post_content'] = wp_json_encode( $config );

wp_update_post( $user_cpt, true, false );

$styles = gutenberg_get_global_stylesheet();
mmtr marked this conversation as resolved.
Show resolved Hide resolved
$this->assertStringContainsString( 'background-color: hotpink;', $styles );
remove_filter( 'wp_get_global_stylesheet_can_use_cache', '__return_true' );
}
}