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

Site global styles #50102

Closed
wants to merge 24 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
27ee2a3
adding site global styles post type in the backend
matiasbenedetto Apr 26, 2023
a32f1ee
Consuming site global styles from the client
matiasbenedetto Apr 26, 2023
2102a52
undo unwanted changes made merging from trunk
matiasbenedetto Apr 26, 2023
0ad0aa4
removing not needed variable
matiasbenedetto Apr 26, 2023
58fa7d9
post name as variable
matiasbenedetto Apr 26, 2023
f72324d
adding site to useGlobalStyle hook
matiasbenedetto Apr 28, 2023
1849eac
adding site to useGlobalSetting hook
matiasbenedetto Apr 28, 2023
f1be9f4
adding site to global styles context
matiasbenedetto Apr 28, 2023
82c3ef2
reset site styles when reset global styles to default is run
matiasbenedetto Apr 28, 2023
8158e3f
Make PHP linter happy
oandregal May 2, 2023
ed6b8fd
Enable site as a valid origin for data
oandregal May 2, 2023
c461e25
Add test for get_site_data_from_wp_global_styles
oandregal May 2, 2023
fc139bf
reverting unwanted change
matiasbenedetto May 2, 2023
ce3bb54
Adding PHPDoc for get_site_global_styles_post_id and get_site_data fu…
matiasbenedetto May 2, 2023
92206bf
get_merge_data: add support for site origin
oandregal May 2, 2023
844035b
get_site_data_from_wp_global_styles: add PHPDoc
oandregal May 2, 2023
f37bfb7
Update name of new filter
oandregal May 2, 2023
9e10570
add site global styles under 'site' key instead of 'custom' in global…
matiasbenedetto May 3, 2023
8f64434
REST controller: fix lint issue
oandregal May 4, 2023
f08658b
Add PHPDoc for get_site_item
oandregal May 4, 2023
79bb97f
Move every method after the properties
oandregal May 4, 2023
9e7c951
get_site_item: follow existing patterns
oandregal May 4, 2023
d4ba090
Remove unnecessary change
oandregal May 4, 2023
a45f8c4
Make linter happy
oandregal May 4, 2023
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
3 changes: 2 additions & 1 deletion lib/class-wp-theme-json-gutenberg.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ class WP_Theme_JSON_Gutenberg {
'default',
'blocks',
'theme',
'site',
'custom',
);

Expand Down Expand Up @@ -595,7 +596,7 @@ public static function get_element_class_name( $element ) {
*
* @param array $theme_json A structure that follows the theme.json schema.
* @param string $origin Optional. What source of data this object represents.
* One of 'default', 'theme', or 'custom'. Default 'theme'.
* One of 'default', 'theme', 'site' or 'custom'. Default 'theme'.
*/
public function __construct( $theme_json = array(), $origin = 'theme' ) {
if ( ! in_array( $origin, static::VALID_ORIGINS, true ) ) {
Expand Down
172 changes: 171 additions & 1 deletion lib/class-wp-theme-json-resolver-gutenberg.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ class WP_Theme_JSON_Resolver_Gutenberg {
'core' => array(),
'blocks' => array(),
'theme' => array(),
'site' => array(),
'user' => array(),
);

Expand Down Expand Up @@ -64,6 +65,14 @@ class WP_Theme_JSON_Resolver_Gutenberg {
*/
protected static $user = null;

/**
* Container for data coming from the site.
*
* @since 6.3.0
* @var WP_Theme_JSON
*/
protected static $site = null;

/**
* Stores the ID of the custom post type
* that holds the user data.
Expand All @@ -73,6 +82,15 @@ class WP_Theme_JSON_Resolver_Gutenberg {
*/
protected static $user_custom_post_type_id = null;

/**
* Stores the ID of the custom post type
* that holds the site data.
*
* @since 6.3.0
* @var int
*/
protected static $site_custom_post_type_id = null;

/**
* Container to keep loaded i18n schema for `theme.json`.
*
Expand Down Expand Up @@ -478,6 +496,76 @@ public static function get_user_data_from_wp_global_styles( $theme, $create_post
return $user_cpt;
}

/**
* Returns the custom post type that contains the site's origin config
* or an empty array if none is found.
*
* This can also create and return a new draft custom post type.
*
* @since 6.3.0
*
* @param bool $create_post Optional. Whether a new custom post
* type should be created if none are
* found. Default false.
* @param array $post_status_filter Optional. Filter custom post type by
* post status. Default `array( 'publish' )`,
* so it only fetches published posts.
* @return array Custom Post Type for the user's origin config.
*/
public static function get_site_data_from_wp_global_styles( $create_post = false, $post_status_filter = array( 'publish' ) ) {
$theme = wp_get_theme();

/*
* Bail early if the theme does not support a theme.json.
*
* 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() && ! wp_theme_has_theme_json() ) {
return array();
}

$site_cpt = array();
$post_type_filter = 'wp_global_styles';
$post_name = 'wp-global-styles-site';
$args = array(
'posts_per_page' => 1,
'orderby' => 'date',
'order' => 'desc',
'post_type' => $post_type_filter,
'post_status' => $post_status_filter,
'ignore_sticky_posts' => true,
'no_found_rows' => true,
'update_post_meta_cache' => false,
'update_post_term_cache' => false,
'name' => $post_name,
);

$global_style_query = new WP_Query();
$recent_posts = $global_style_query->query( $args );
if ( count( $recent_posts ) === 1 ) {
$site_cpt = get_object_vars( $recent_posts[0] );
} elseif ( $create_post ) {
$cpt_post_id = wp_insert_post(
array(
'post_content' => '{"version": ' . WP_Theme_JSON_Gutenberg::LATEST_SCHEMA . ', "isGlobalStylesUserThemeJSON": true }',
'post_status' => 'publish',
'post_title' => 'Custom Styles Site', // Do not make string translatable, see https://core.trac.wordpress.org/ticket/54518.
'post_type' => $post_type_filter,
'post_name' => $post_name,
),
true
);
if ( ! is_wp_error( $cpt_post_id ) ) {
$site_cpt = get_object_vars( get_post( $cpt_post_id ) );
}
}

return $site_cpt;

}

/**
* Returns the user's origin config.
*
Expand Down Expand Up @@ -531,6 +619,59 @@ public static function get_user_data() {
return static::$user;
}

/**
* Returns the site's origin config.
*
* @since 6.3.0
*
* @return WP_Theme_JSON Entity that holds styles for site level data.
*/
public static function get_site_data() {
matiasbenedetto marked this conversation as resolved.
Show resolved Hide resolved
if ( null !== static::$site && static::has_same_registered_blocks( 'site' ) ) {
return static::$site;
}

$config = array();
$site_cpt = static::get_site_data_from_wp_global_styles();

if ( array_key_exists( 'post_content', $site_cpt ) ) {
$decoded_data = json_decode( $site_cpt['post_content'], true );

$json_decoding_error = json_last_error();
if ( JSON_ERROR_NONE !== $json_decoding_error ) {
trigger_error( 'Error when decoding a theme.json schema for site data. ' . json_last_error_msg() );
/**
* Filters the data provided by the user for global styles & settings.
*
* @since 6.1.0
*
* @param WP_Theme_JSON_Data Class to access and update the underlying data.
*/
$theme_json = apply_filters( 'wp_theme_json_data_site', new WP_Theme_JSON_Data_Gutenberg( $config, 'site' ) );
$config = $theme_json->get_data();
return new WP_Theme_JSON_Gutenberg( $config, 'site' );
}

// Very important to verify that the flag isGlobalStylesUserThemeJSON is true.
// If it's not true then the content was not escaped and is not safe.
if (
is_array( $decoded_data ) &&
isset( $decoded_data['isGlobalStylesUserThemeJSON'] ) &&
$decoded_data['isGlobalStylesUserThemeJSON']
) {
unset( $decoded_data['isGlobalStylesUserThemeJSON'] );
$config = $decoded_data;
}
}

/** This filter is documented in wp-includes/class-wp-theme-json-resolver.php */
$theme_json = apply_filters( 'wp_theme_json_data_site', new WP_Theme_JSON_Data_Gutenberg( $config, 'site' ) );
matiasbenedetto marked this conversation as resolved.
Show resolved Hide resolved
$config = $theme_json->get_data();
static::$site = new WP_Theme_JSON_Gutenberg( $config, 'site' );

return static::$site;
}

/**
* Returns the data merged from multiple origins.
*
Expand Down Expand Up @@ -561,7 +702,7 @@ public static function get_user_data() {
* added the `$origin` parameter.
* @since 6.1.0 Added block data and generation of spacingSizes array.
*
* @param string $origin Optional. To what level should we merge data:'default', 'blocks', 'theme' or 'custom'.
* @param string $origin Optional. To what level should we merge data:'default', 'blocks', 'theme', 'site', or 'custom'.
* 'custom' is used as default value as well as fallback value if the origin is unknown.
*
* @return WP_Theme_JSON
Expand Down Expand Up @@ -589,6 +730,12 @@ public static function get_merged_data( $origin = 'custom' ) {
return $result;
}

$result->merge( static::get_site_data() );
if ( 'site' === $origin ) {
$result->set_spacing_sizes();
return $result;
}

$result->merge( static::get_user_data() );
$result->set_spacing_sizes();
return $result;
Expand Down Expand Up @@ -616,6 +763,28 @@ public static function get_user_global_styles_post_id() {
return static::$user_custom_post_type_id;
}

/**
* Returns the ID of the custom post type
* that stores site data.
*
* @since 6.3.0
*
* @return integer|null
*/
public static function get_site_global_styles_post_id() {
if ( null !== static::$site_custom_post_type_id ) {
return static::$site_custom_post_type_id;
}

$site_cpt = static::get_site_data_from_wp_global_styles( true );

if ( array_key_exists( 'ID', $site_cpt ) ) {
static::$site_custom_post_type_id = $site_cpt['ID'];
}

return static::$site_custom_post_type_id;
}

/**
* Determines whether the active theme has a theme.json file.
*
Expand Down Expand Up @@ -670,6 +839,7 @@ public static function clean_cached_data() {
);
static::$theme = null;
static::$user = null;
static::$site = null;
static::$user_custom_post_type_id = null;
static::$i18n_schema = null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,47 @@ class Gutenberg_REST_Global_Styles_Controller_6_3 extends Gutenberg_REST_Global_
*/
private $revisions_controller;

/**
* Registers the controllers routes.
*
* @return void
*/
public function register_routes() {
parent::register_routes();

register_rest_route(
$this->namespace,
'/' . $this->rest_base,
array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_site_item' ),
'permission_callback' => array( $this, 'get_theme_items_permissions_check' ),
),
)
);

}

/**
* Return the global styles config for the site origin.
*
* @since 6.3.0
*
* @param WP_REST_Request $request The request instance.
*
* @return WP_REST_Response|WP_Error
*/
public function get_site_item( $request ) {
matiasbenedetto marked this conversation as resolved.
Show resolved Hide resolved
$post_id = WP_Theme_JSON_Resolver_Gutenberg::get_site_global_styles_post_id();
$post = $this->get_post( $post_id );
if ( is_wp_error( $post ) ) {
return $post;
}

return $this->prepare_item_for_response( $post, $request );
}

/**
* Prepares links for the request.
*
Expand Down Expand Up @@ -48,4 +89,76 @@ protected function prepare_links( $id ) {

return $links;
}

/**
* Prepare a global styles config output for response.
*
* @since 5.9.0
* @since 6.2 Handling of style.css was added to WP_Theme_JSON.
* @since 6.3 Added support for site origin in global styles.
*
* @param WP_Post $post Global Styles post object.
* @param WP_REST_Request $request Request object.
* @return WP_REST_Response Response object.
*/
public function prepare_item_for_response( $post, $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
$raw_config = json_decode( $post->post_content, true );
$is_global_styles_user_theme_json = isset( $raw_config['isGlobalStylesUserThemeJSON'] ) && true === $raw_config['isGlobalStylesUserThemeJSON'];
$config = array();
if ( $is_global_styles_user_theme_json ) {
$origin = ( isset( $post->post_name ) && 'wp-global-styles-site' === $post->post_name ) ? 'site' : 'custom';
Copy link
Member

Choose a reason for hiding this comment

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

Documenting what I learn: the only change for the prepare_item_for_response method is this line. Otherwise, it's a verbatim copy of the existing 6.2 method.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, that's correct. That line and the following one (which will be using $origin value) are the only changes.

$config = ( new WP_Theme_JSON_Gutenberg( $raw_config, $origin ) )->get_raw_data();
}

// Base fields for every post.
$data = array();
$fields = $this->get_fields_for_response( $request );

if ( rest_is_field_included( 'id', $fields ) ) {
$data['id'] = $post->ID;
}

if ( rest_is_field_included( 'title', $fields ) ) {
$data['title'] = array();
}
if ( rest_is_field_included( 'title.raw', $fields ) ) {
$data['title']['raw'] = $post->post_title;
}
if ( rest_is_field_included( 'title.rendered', $fields ) ) {
add_filter( 'protected_title_format', array( $this, 'protected_title_format' ) );

$data['title']['rendered'] = get_the_title( $post->ID );

remove_filter( 'protected_title_format', array( $this, 'protected_title_format' ) );
}

if ( rest_is_field_included( 'settings', $fields ) ) {
$data['settings'] = ! empty( $config['settings'] ) && $is_global_styles_user_theme_json ? $config['settings'] : new stdClass();
}

if ( rest_is_field_included( 'styles', $fields ) ) {
$data['styles'] = ! empty( $config['styles'] ) && $is_global_styles_user_theme_json ? $config['styles'] : new stdClass();
}

$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
$data = $this->add_additional_fields_to_object( $data, $request );
$data = $this->filter_response_by_context( $data, $context );

// Wrap the data in a response object.
$response = rest_ensure_response( $data );

if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) {
$links = $this->prepare_links( $post->ID );
$response->add_links( $links );
if ( ! empty( $links['self']['href'] ) ) {
$actions = $this->get_available_actions();
$self = $links['self']['href'];
foreach ( $actions as $rel ) {
$response->add_link( $rel, $self );
}
}
}

return $response;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ import { createContext } from '@wordpress/element';

export const DEFAULT_GLOBAL_STYLES_CONTEXT = {
user: {},
site: {},
base: {},
merged: {},
setUserConfig: () => {},
setSiteConfig: () => {},
};

export const GlobalStylesContext = createContext(
Expand Down
Loading