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

wp_theme_has_theme_json: add public method to know whether the theme supports theme.json #3556

Closed
wants to merge 15 commits into from
Closed
2 changes: 1 addition & 1 deletion src/wp-admin/edit-form-blocks.php
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ static function( $classes ) {
'unlockNonce' => wp_create_nonce( 'update-post_' . $post->ID ),
'ajaxUrl' => admin_url( 'admin-ajax.php' ),
),
'supportsLayout' => WP_Theme_JSON_Resolver::theme_has_support(),
'supportsLayout' => wp_theme_has_theme_json(),
'supportsTemplateMode' => current_theme_supports( 'block-templates' ),

// Whether or not to load the 'postcustom' meta box is stored as a user meta
Expand Down
2 changes: 1 addition & 1 deletion src/wp-admin/site-editor.php
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ static function( $classes ) {
'styles' => get_block_editor_theme_styles(),
'defaultTemplateTypes' => $indexed_template_types,
'defaultTemplatePartAreas' => get_allowed_block_template_part_areas(),
'supportsLayout' => WP_Theme_JSON_Resolver::theme_has_support(),
'supportsLayout' => wp_theme_has_theme_json(),
'supportsTemplatePartsMode' => ! wp_is_block_theme() && current_theme_supports( 'block-template-parts' ),
'__unstableHomeTemplate' => $home_template,
);
Expand Down
2 changes: 1 addition & 1 deletion src/wp-includes/block-editor.php
Original file line number Diff line number Diff line change
Expand Up @@ -417,7 +417,7 @@ function get_block_editor_settings( array $custom_settings, $block_editor_contex
}
}

if ( WP_Theme_JSON_Resolver::theme_has_support() ) {
if ( wp_theme_has_theme_json() ) {
$block_classes = array(
'css' => 'styles',
'__unstableType' => 'theme',
Expand Down
2 changes: 1 addition & 1 deletion src/wp-includes/block-patterns.php
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ function _register_remote_theme_patterns() {
return;
}

if ( ! WP_Theme_JSON_Resolver::theme_has_support() ) {
if ( ! wp_theme_has_theme_json() ) {
return;
}

Expand Down
4 changes: 2 additions & 2 deletions src/wp-includes/block-supports/layout.php
Original file line number Diff line number Diff line change
Expand Up @@ -464,7 +464,7 @@ function wp_restore_group_inner_container( $block_content, $block ) {
);

if (
WP_Theme_JSON_Resolver::theme_has_support() ||
wp_theme_has_theme_json() ||
1 === preg_match( $group_with_inner_container_regex, $block_content ) ||
( isset( $block['attrs']['layout']['type'] ) && 'flex' === $block['attrs']['layout']['type'] )
) {
Expand Down Expand Up @@ -527,7 +527,7 @@ function wp_restore_image_outer_container( $block_content, $block ) {
)/iUx";

if (
WP_Theme_JSON_Resolver::theme_has_support() ||
wp_theme_has_theme_json() ||
0 === preg_match( $image_with_align, $block_content, $matches )
) {
return $block_content;
Expand Down
4 changes: 2 additions & 2 deletions src/wp-includes/block-template-utils.php
Original file line number Diff line number Diff line change
Expand Up @@ -347,7 +347,7 @@ function _get_block_templates_files( $template_type ) {
* @return array Template item.
*/
function _add_block_template_info( $template_item ) {
if ( ! WP_Theme_JSON_Resolver::theme_has_support() ) {
if ( ! wp_theme_has_theme_json() ) {
return $template_item;
}

Expand All @@ -370,7 +370,7 @@ function _add_block_template_info( $template_item ) {
* @return array Template info.
*/
function _add_block_template_part_area_info( $template_info ) {
if ( WP_Theme_JSON_Resolver::theme_has_support() ) {
if ( wp_theme_has_theme_json() ) {
$theme_data = WP_Theme_JSON_Resolver::get_theme_data( array(), array( 'with_supports' => false ) )->get_template_parts();
}

Expand Down
25 changes: 6 additions & 19 deletions src/wp-includes/class-wp-theme-json-resolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,14 +57,6 @@ class WP_Theme_JSON_Resolver {
*/
protected static $theme = null;

/**
* Whether or not the theme supports theme.json.
*
* @since 5.8.0
* @var bool
*/
protected static $theme_has_support = null;

/**
* Container for data coming from the user.
*
Expand Down Expand Up @@ -295,7 +287,7 @@ public static function get_theme_data( $deprecated = array(), $options = array()
* and merge the static::$theme upon that.
*/
$theme_support_data = WP_Theme_JSON::get_from_editor_settings( get_default_block_editor_settings() );
if ( ! static::theme_has_support() ) {
if ( ! wp_theme_has_theme_json() ) {
if ( ! isset( $theme_support_data['settings']['color'] ) ) {
$theme_support_data['settings']['color'] = array();
}
Expand Down Expand Up @@ -416,11 +408,11 @@ public static function get_user_data_from_wp_global_styles( $theme, $create_post
/*
* Bail early if the theme does not support a theme.json.
*
* Since WP_Theme_JSON_Resolver::theme_has_support() only supports the active
* Since wp_theme_has_theme_json() 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() ) {
if ( $theme->get_stylesheet() === get_stylesheet() && ! wp_theme_has_theme_json() ) {
return array();
}

Expand Down Expand Up @@ -597,18 +589,14 @@ public static function get_user_global_styles_post_id() {
*
* @since 5.8.0
* @since 5.9.0 Added a check in the parent theme.
* @deprecated 6.2.0 Use wp_theme_has_theme_json() instead.
*
* @return bool
*/
public static function theme_has_support() {
if ( ! isset( static::$theme_has_support ) ) {
static::$theme_has_support = (
static::get_file_path_from_theme( 'theme.json' ) !== '' ||
static::get_file_path_from_theme( 'theme.json', true ) !== ''
);
}
_deprecated_function( __METHOD__, '6.2.0', 'wp_theme_has_theme_json()' );

return static::$theme_has_support;
return wp_theme_has_theme_json();
}

/**
Expand Down Expand Up @@ -651,7 +639,6 @@ public static function clean_cached_data() {
static::$theme = null;
static::$user = null;
static::$user_custom_post_type_id = null;
static::$theme_has_support = null;
static::$i18n_schema = null;
}

Expand Down
4 changes: 2 additions & 2 deletions src/wp-includes/default-filters.php
Original file line number Diff line number Diff line change
Expand Up @@ -346,8 +346,8 @@
add_action( 'init', '_register_core_block_patterns_and_categories' );
add_action( 'init', 'check_theme_switched', 99 );
add_action( 'init', array( 'WP_Block_Supports', 'init' ), 22 );
add_action( 'switch_theme', array( 'WP_Theme_JSON_Resolver', 'clean_cached_data' ) );
add_action( 'start_previewing_theme', array( 'WP_Theme_JSON_Resolver', 'clean_cached_data' ) );
add_action( 'switch_theme', 'wp_clean_theme_json_caches' );
add_action( 'start_previewing_theme', 'wp_clean_theme_json_caches' );
felixarntz marked this conversation as resolved.
Show resolved Hide resolved
add_action( 'after_switch_theme', '_wp_menus_changed' );
add_action( 'after_switch_theme', '_wp_sidebars_changed' );
add_action( 'wp_print_styles', 'print_emoji_styles' );
Expand Down
73 changes: 71 additions & 2 deletions src/wp-includes/global-styles-and-settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ function wp_get_global_stylesheet( $types = array() ) {

$tree = WP_Theme_JSON_Resolver::get_merged_data();

$supports_theme_json = WP_Theme_JSON_Resolver::theme_has_support();
$supports_theme_json = wp_theme_has_theme_json();
if ( empty( $types ) && ! $supports_theme_json ) {
$types = array( 'variables', 'presets', 'base-layout-styles' );
} elseif ( empty( $types ) ) {
Expand Down Expand Up @@ -184,7 +184,7 @@ function wp_get_global_styles_svg_filters() {
}
}

$supports_theme_json = WP_Theme_JSON_Resolver::theme_has_support();
$supports_theme_json = wp_theme_has_theme_json();

$origins = array( 'default', 'theme', 'custom' );
if ( ! $supports_theme_json ) {
Expand Down Expand Up @@ -255,3 +255,72 @@ function ( $item ) {
}
}
}

/**
* Checks whether a theme or its parent has a theme.json file.
*
* @since 6.2.0
*
* @return bool Returns true if theme or its parent has a theme.json file, false otherwise.
*/
function wp_theme_has_theme_json() {
Copy link
Member

Choose a reason for hiding this comment

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

@oandregal See my feedback in WordPress/gutenberg#45955 (review).

I think this function is good like this, however we should also add a method fulfilling the same purpose to WP_Theme. For that method, performance is not as much of a concern, so I think it's perfectly fine not having caching in there (as other methods on WP_Theme don't have such caching either, plus the common use-case we're optimizing for is the current theme, which this function is for).

LMKWYT. We can also do that in a follow up PR, but would like to hear your thoughts.

Copy link
Member Author

Choose a reason for hiding this comment

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

I have that PR in my queue to re-review, but I've been AFK for a week for personal reasons and have a long backlog :) My TLDR is: I understand having the ability to check for any theme, not just the active one, is something we need. I'm not sure adding that parameter to this function is the way to go. Need to look at that.

In any case, that conversation should not block this PR, as it's a potential follow-up we can do if it comes to implement it that way.

Copy link
Member

Choose a reason for hiding this comment

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

As noted in the PR WordPress/gutenberg#45955. We need to know on EVERY request if the current AND parent theme support theme json. For that reason, this function is not fit for the requirements.

Copy link
Member

Choose a reason for hiding this comment

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

Should this logic live in a method in the WP_Theme class? See is_block_theme as a example.

/**
* Returns whether this theme is a block-based theme or not.
*
* @since 5.9.0
*
* @return bool
*/
public function is_block_theme() {
$paths_to_index_block_template = array(
$this->get_file_path( '/block-templates/index.html' ),
$this->get_file_path( '/templates/index.html' ),
);
foreach ( $paths_to_index_block_template as $path_to_index_block_template ) {
if ( is_file( $path_to_index_block_template ) && is_readable( $path_to_index_block_template ) ) {
return true;
}
}
return false;
}

/**
* Returns whether the active theme is a block-based theme or not.
*
* @since 5.9.0
*
* @return boolean Whether the active theme is a block-based theme or not.
*/
function wp_is_block_theme() {
return wp_get_theme()->is_block_theme();
}

Copy link
Member Author

@oandregal oandregal Dec 19, 2022

Choose a reason for hiding this comment

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

As noted in the PR WordPress/gutenberg#45955. We need to know on EVERY request if the current AND parent theme support theme json. For that reason, this function is not fit for the requirements.

Different people have different views on this. @felixarntz mentioned it does not necessarily requires modifying this function. I also shared the implementation can be different. There's agreement on querying themes other than the active theme is needed, though the implementation is not clear yet. Even if that PR was the way to go: it's a future modification that does not affect the API. We should aim to ship work in small pieces to make progress.

Should this logic live in a method in the WP_Theme class? See is_block_theme as a example.

Note that there is a wp_is_block_theme as well, which is the main API for consumers, like this functions aims to be.

Whether we also have a WP_Theme->has_theme_json is something to talk about. This was brought up in the conversations we had in Gutenberg already. There was no clarity about value, so the decision was to ship the pieces that are ready and keep iterating on the parts that needed more thought.

Copy link
Member

Choose a reason for hiding this comment

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

We didn't really discuss this as part of the gutenberg PR, as that is not a gutenberg change, it is a core change. Now we are in core, we need to talk about it again.

Copy link
Contributor

Choose a reason for hiding this comment

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

I disagree @spacedmonkey. Smaller iterations of work can be committed into Core.

What do I mean exactly? For this particular PR's scope of work, it can be iteratively committed as separate initiatives such as:

  • Initiative 1: Add functionality to provide a global way to identify if a theme supports theme.json is useful for Core.
  • Initiative 2: Improve its performance, if needed.

Rather than blocking a backport, let the first get committed with a follow-up to improve its performance.

Copy link
Contributor

Choose a reason for hiding this comment

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

Hmm that's interesting thought #3556 (comment) @felixarntz. I agree.

It's common for Core to have a global convenience function that wraps around a public method.

I agree:

  • WP_Theme::has_theme_json() makes sense.
  • and can be added in a separate PR and commit, ie for smaller scopes of work.

Copy link
Member

Choose a reason for hiding this comment

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

WP_Theme::has_theme_json() makes sense.

See #3604 where I did this

Copy link
Member

Choose a reason for hiding this comment

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

@spacedmonkey The implementation from #3604 uses the WP_Theme method in wp_theme_has_theme_json() function though, which is different from what @hellofromtonya and I were referring to further above though.

Normally I would say it makes sense and we don't want to duplicate code, but what I am outlining in #3556 (comment) is a common core pattern already:

  • For example, get_stylesheet() does not call WP_Theme::get_stylesheet().
  • Instead, it implements the behavior in a simpler and less expensive way (without relying on WP_Theme).
  • That pattern I think we should follow here as well.

We can create the WP_Theme method in a separate PR though, it doesn't have to be the same one.

/*
* By using the 'theme_json' group, this data is marked to be non-persistent across requests.
* @see `wp_cache_add_non_persistent_groups()`.
*
* The rationale for this is to make sure derived data from theme.json
* is always fresh from the potential modifications done via hooks
* that can use dynamic data (modify the stylesheet depending on some option,
* settings depending on user permissions, etc.).
* For some of the existing hooks to modify theme.json behavior:
* @see https://make.wordpress.org/core/2022/10/10/filters-for-theme-json-data/
*
* A different alternative considered was to invalidate the cache upon certain
* events such as options add/update/delete, user meta, etc.
* It was judged not enough, hence this approach.
* @see https://github.com/WordPress/gutenberg/pull/45372
*/
$cache_group = 'theme_json';
$cache_key = 'wp_theme_has_theme_json';
$theme_has_support = wp_cache_get( $cache_key, $cache_group );

/*
* $theme_has_support is stored as an int in the cache.
*
* The reason not to store it as a boolean is to avoid working
* with the $found parameter which apparently had some issues in some implementations
* @see https://developer.wordpress.org/reference/functions/wp_cache_get/
*
* Ignore cache when `WP_DEBUG` is enabled, so it doesn't interfere with the theme
* developer's workflow.
*
* @todo Replace `WP_DEBUG` once an "in development mode" check is available in Core.
*/
if ( ! WP_DEBUG && is_int( $theme_has_support ) ) {
felixarntz marked this conversation as resolved.
Show resolved Hide resolved
return (bool) $theme_has_support;
}

// Does the theme have its own theme.json?
$theme_has_support = is_readable( get_stylesheet_directory() . '/theme.json' );

// Look up the parent if the child does not have a theme.json.
if ( ! $theme_has_support ) {
$theme_has_support = is_readable( get_template_directory() . '/theme.json' );
}
Comment on lines +303 to +309
Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Member Author

Choose a reason for hiding this comment

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

That could be a future change, indeed. We need to measure the performance impact as well. If/when we do that, the corresponding backport should be filled. How is that a blocker for this PR?

Copy link
Member

Choose a reason for hiding this comment

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

I am saying, we are have months to go until the 6.2 release, there is no need to merge this change until ALL the PRs on the gutenberg are resolved. I am in no hurry to merge this, let's get it right instead of hurshing changes into core.

Copy link
Contributor

@azaozz azaozz Dec 19, 2022

Choose a reason for hiding this comment

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

we are have months to go until the 6.2 release, there is no need to merge this change until...
...
I am in no hurry to merge this...

I'm actually trying to do exactly the opposite, the "merge early, merge often" strategy. :)

We are merging to WP trunk (6.2-alpha) so no point in delaying it. On the opposite, if there are any bugs the chances to discover them increase if they are also in core trunk, not just released as plugin. Then if something is fixed/patched "upstream" in the plugin, it can be added to trunk too, no pressure.

Merging early and having more chances to discover bugs seems a lot better than merging in the last minute, and eventually having to fix/patch bugs during RC :)

In that terms hoping to see all changes from the current Gutenberg plugin (14.7.3) in core trunk before the end of the year.

Copy link
Member

Choose a reason for hiding this comment

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

I didn't say this could not be merged. Just that is should not be backported to 6.1.2.


$theme_has_support = $theme_has_support ? 1 : 0;
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
$theme_has_support = $theme_has_support ? 1 : 0;
$theme_has_support = (int) $theme_has_support;

Might as well cast to int here right?

Copy link
Member Author

Choose a reason for hiding this comment

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

I guess it works, though I strongly prefer the backport PR to be exactly the same as the upstream Gutenberg code, so I rather do whatever is in Gutenberg.


wp_cache_set( $cache_key, $theme_has_support, $cache_group );

return (bool) $theme_has_support;
}

/**
* Cleans the caches under the theme_json group.
*
* @since 6.2.0
*/
function wp_clean_theme_json_caches() {
wp_cache_delete( 'wp_theme_has_theme_json', 'theme_json' );
Copy link
Member Author

Choose a reason for hiding this comment

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

If #3712 lands first, this line needs to be added to the function.

Copy link
Contributor

Choose a reason for hiding this comment

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

Same question here #3556 (comment)

WP_Theme_JSON_Resolver::clean_cached_data();
}
2 changes: 1 addition & 1 deletion src/wp-includes/load.php
Original file line number Diff line number Diff line change
Expand Up @@ -753,7 +753,7 @@ function wp_start_object_cache() {
)
);

wp_cache_add_non_persistent_groups( array( 'counts', 'plugins' ) );
wp_cache_add_non_persistent_groups( array( 'counts', 'plugins', 'theme_json' ) );
Copy link
Member Author

Choose a reason for hiding this comment

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

If #3712 lands first, this would have already been ported over there (same for the other places where the group was added).

Copy link
Contributor

Choose a reason for hiding this comment

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

Same question here #3556 (comment).

Copy link
Member Author

Choose a reason for hiding this comment

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

responded at #3556 (comment)

Copy link
Member

Choose a reason for hiding this comment

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

See above, IMO this is good to keep.

}

$first_init = false;
Expand Down
4 changes: 2 additions & 2 deletions src/wp-includes/ms-blogs.php
Original file line number Diff line number Diff line change
Expand Up @@ -575,7 +575,7 @@ function switch_to_blog( $new_blog_id, $deprecated = null ) {
);
}

wp_cache_add_non_persistent_groups( array( 'counts', 'plugins' ) );
wp_cache_add_non_persistent_groups( array( 'counts', 'plugins', 'theme_json' ) );
}
}

Expand Down Expand Up @@ -666,7 +666,7 @@ function restore_current_blog() {
);
}

wp_cache_add_non_persistent_groups( array( 'counts', 'plugins' ) );
wp_cache_add_non_persistent_groups( array( 'counts', 'plugins', 'theme_json' ) );
}
}

Expand Down
6 changes: 3 additions & 3 deletions src/wp-includes/script-loader.php
Original file line number Diff line number Diff line change
Expand Up @@ -1618,7 +1618,7 @@ function wp_default_styles( $styles ) {
);

// Only load the default layout and margin styles for themes without theme.json file.
if ( ! WP_Theme_JSON_Resolver::theme_has_support() ) {
if ( ! wp_theme_has_theme_json() ) {
$wp_edit_blocks_dependencies[] = 'wp-editor-classic-layout-styles';
}

Expand Down Expand Up @@ -3665,7 +3665,7 @@ function _wp_theme_json_webfonts_handler() {
* @since 6.1.0
*/
function wp_enqueue_classic_theme_styles() {
if ( ! WP_Theme_JSON_Resolver::theme_has_support() ) {
if ( ! wp_theme_has_theme_json() ) {
$suffix = wp_scripts_get_suffix();
wp_register_style( 'classic-theme-styles', '/' . WPINC . "/css/classic-themes$suffix.css", array(), true );
wp_enqueue_style( 'classic-theme-styles' );
Expand All @@ -3683,7 +3683,7 @@ function wp_enqueue_classic_theme_styles() {
* @return array A filtered array of editor settings.
*/
function wp_add_editor_classic_theme_styles( $editor_settings ) {
if ( WP_Theme_JSON_Resolver::theme_has_support() ) {
if ( wp_theme_has_theme_json() ) {
return $editor_settings;
}

Expand Down
2 changes: 1 addition & 1 deletion src/wp-includes/theme-templates.php
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ function the_block_template_skip_link() {
* @since 5.8.0
*/
function wp_enable_block_templates() {
if ( wp_is_block_theme() || WP_Theme_JSON_Resolver::theme_has_support() ) {
if ( wp_is_block_theme() || wp_theme_has_theme_json() ) {
add_theme_support( 'block-templates' );
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/*
Theme Name: Block Theme Child with no theme.json
Theme URI: https://wordpress.org/
Description: For testing purposes only.
Template: block-theme
Version: 1.0.0
Text Domain: block-theme-child-no-theme-json
*/
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/*
Theme Name: Default Child Theme with no theme.json
Theme URI: https://wordpress.org/
Description: For testing purposes only.
Template: default
Version: 1.0.0
Text Domain: default-child-no-theme-json
*/
2 changes: 1 addition & 1 deletion tests/phpunit/includes/abstract-testcase.php
Original file line number Diff line number Diff line change
Expand Up @@ -401,7 +401,7 @@ public static function flush_cache() {
)
);

wp_cache_add_non_persistent_groups( array( 'counts', 'plugins' ) );
wp_cache_add_non_persistent_groups( array( 'counts', 'plugins', 'theme_json' ) );
}

/**
Expand Down
2 changes: 2 additions & 0 deletions tests/phpunit/tests/theme/themeDir.php
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ public function test_theme_list() {
$theme_names = array_keys( $themes );
$expected = array(
'WordPress Default',
'Default Child Theme with no theme.json',
'Sandbox',
'Stylesheet Only',
'My Theme',
Expand All @@ -177,6 +178,7 @@ public function test_theme_list() {
'REST Theme',
'Block Theme',
'Block Theme Child Theme',
'Block Theme Child with no theme.json',
'Block Theme Child Theme With Fluid Typography',
'Block Theme [0.4.0]',
'Block Theme [1.0.0] in subdirectory',
Expand Down
Loading