diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index fbbf9a05d1c817..a893aff8bafc6d 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -56,6 +56,7 @@ class WP_Theme_JSON_Gutenberg { 'default', 'blocks', 'theme', + 'site', 'custom', ); @@ -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 ) ) { diff --git a/lib/class-wp-theme-json-resolver-gutenberg.php b/lib/class-wp-theme-json-resolver-gutenberg.php index 2c61de7c5e24c2..c82ed72011be47 100644 --- a/lib/class-wp-theme-json-resolver-gutenberg.php +++ b/lib/class-wp-theme-json-resolver-gutenberg.php @@ -29,6 +29,7 @@ class WP_Theme_JSON_Resolver_Gutenberg { 'core' => array(), 'blocks' => array(), 'theme' => array(), + 'site' => array(), 'user' => array(), ); @@ -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. @@ -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`. * @@ -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. * @@ -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() { + 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' ) ); + $config = $theme_json->get_data(); + static::$site = new WP_Theme_JSON_Gutenberg( $config, 'site' ); + + return static::$site; + } + /** * Returns the data merged from multiple origins. * @@ -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 @@ -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; @@ -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. * @@ -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; } diff --git a/lib/compat/wordpress-6.3/class-gutenberg-rest-global-styles-controller-6-3.php b/lib/compat/wordpress-6.3/class-gutenberg-rest-global-styles-controller-6-3.php index 5eeb0a1014aed6..7a95242426cd96 100644 --- a/lib/compat/wordpress-6.3/class-gutenberg-rest-global-styles-controller-6-3.php +++ b/lib/compat/wordpress-6.3/class-gutenberg-rest-global-styles-controller-6-3.php @@ -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 ) { + $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. * @@ -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'; + $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; + } } diff --git a/packages/block-editor/src/components/global-styles/context.js b/packages/block-editor/src/components/global-styles/context.js index 630b5a2b9059d5..d3f7b65cf6a9ed 100644 --- a/packages/block-editor/src/components/global-styles/context.js +++ b/packages/block-editor/src/components/global-styles/context.js @@ -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( diff --git a/packages/block-editor/src/components/global-styles/hooks.js b/packages/block-editor/src/components/global-styles/hooks.js index 17e4cec9369f45..cf5a8381823dea 100644 --- a/packages/block-editor/src/components/global-styles/hooks.js +++ b/packages/block-editor/src/components/global-styles/hooks.js @@ -74,19 +74,24 @@ const VALID_SETTINGS = [ ]; export const useGlobalStylesReset = () => { - const { user: config, setUserConfig } = useContext( GlobalStylesContext ); + const { + user: config, + setUserConfig, + setSiteConfig, + } = useContext( GlobalStylesContext ); const canReset = !! config && ! fastDeepEqual( config, EMPTY_CONFIG ); return [ canReset, - useCallback( - () => setUserConfig( () => EMPTY_CONFIG ), - [ setUserConfig ] - ), + useCallback( () => { + setUserConfig( () => EMPTY_CONFIG ); + setSiteConfig( () => EMPTY_CONFIG ); + }, [ setUserConfig ] ), ]; }; export function useGlobalSetting( propertyPath, blockName, source = 'all' ) { - const { setUserConfig, ...configs } = useContext( GlobalStylesContext ); + const { setUserConfig, setSiteConfig, ...configs } = + useContext( GlobalStylesContext ); const appendedBlockPath = blockName ? '.blocks.' + blockName : ''; const appendedPropertyPath = propertyPath ? '.' + propertyPath : ''; @@ -128,13 +133,14 @@ export function useGlobalSetting( propertyPath, blockName, source = 'all' ) { appendedBlockPath, ] ); + const setConfig = source === 'site' ? setSiteConfig : setUserConfig; const setSetting = ( newValue ) => { - setUserConfig( ( currentConfig ) => { + setConfig( ( currentConfig ) => { // Deep clone `currentConfig` to avoid mutating it later. - const newUserConfig = JSON.parse( JSON.stringify( currentConfig ) ); - set( newUserConfig, contextualPath, newValue ); + const newConfig = JSON.parse( JSON.stringify( currentConfig ) ); + set( newConfig, contextualPath, newValue ); - return newUserConfig; + return newConfig; } ); }; @@ -151,7 +157,9 @@ export function useGlobalStyle( merged: mergedConfig, base: baseConfig, user: userConfig, + site: siteConfig, setUserConfig, + setSiteConfig, } = useContext( GlobalStylesContext ); const appendedPath = path ? '.' + path : ''; const finalPath = ! blockName @@ -159,7 +167,8 @@ export function useGlobalStyle( : `styles.blocks.${ blockName }${ appendedPath }`; const setStyle = ( newValue ) => { - setUserConfig( ( currentConfig ) => { + const setConfig = source === 'site' ? setSiteConfig : setUserConfig; + setConfig( ( currentConfig ) => { // Deep clone `currentConfig` to avoid mutating it later. const newUserConfig = JSON.parse( JSON.stringify( currentConfig ) ); set( @@ -196,6 +205,12 @@ export function useGlobalStyle( ? getValueFromVariable( mergedConfig, blockName, rawResult ) : rawResult; break; + case 'site': + rawResult = get( siteConfig, finalPath ); + result = shouldDecodeEncode + ? getValueFromVariable( mergedConfig, blockName, rawResult ) + : rawResult; + break; case 'base': rawResult = get( baseConfig, finalPath ); result = shouldDecodeEncode diff --git a/packages/core-data/src/actions.js b/packages/core-data/src/actions.js index 4c36e2505e7f3d..100bc61b37c2a5 100644 --- a/packages/core-data/src/actions.js +++ b/packages/core-data/src/actions.js @@ -149,6 +149,15 @@ export function __experimentalReceiveCurrentGlobalStylesId( }; } +export function __experimentalReceiveCurrentSiteGlobalStylesId( + currentSiteGlobalStylesId +) { + return { + type: 'RECEIVE_CURRENT_SITE_GLOBAL_STYLES_ID', + id: currentSiteGlobalStylesId, + }; +} + /** * Returns an action object used in signalling that the theme base global styles have been received * Ignored from documentation as it's internal to the data store. diff --git a/packages/core-data/src/entities.js b/packages/core-data/src/entities.js index 3b8a443bcf1e39..227921474d1f39 100644 --- a/packages/core-data/src/entities.js +++ b/packages/core-data/src/entities.js @@ -150,6 +150,14 @@ export const rootEntitiesConfig = [ plural: 'globalStylesVariations', // Should be different than name. getTitle: ( record ) => record?.title?.rendered || record?.title, }, + { + label: __( 'Global Styles Site' ), + name: 'siteGlobalStyles', + kind: 'root', + baseURL: '/wp/v2/global-styles', + baseURLParams: { context: 'edit' }, + getTitle: ( record ) => record?.title?.rendered || record?.title, + }, { label: __( 'Themes' ), name: 'theme', diff --git a/packages/core-data/src/reducer.js b/packages/core-data/src/reducer.js index 6d5fefd52ddf42..b9f470edb615ff 100644 --- a/packages/core-data/src/reducer.js +++ b/packages/core-data/src/reducer.js @@ -163,6 +163,15 @@ export function themeBaseGlobalStyles( state = {}, action ) { return state; } +export function currentSiteGlobalStylesId( state = undefined, action ) { + switch ( action.type ) { + case 'RECEIVE_CURRENT_SITE_GLOBAL_STYLES_ID': + return action.id; + } + + return state; +} + /** * Reducer managing the theme global styles variations. * @@ -647,6 +656,7 @@ export default combineReducers( { users, currentTheme, currentGlobalStylesId, + currentSiteGlobalStylesId, currentUser, themeGlobalStyleVariations, themeBaseGlobalStyles, diff --git a/packages/core-data/src/resolvers.js b/packages/core-data/src/resolvers.js index b33bb42e653379..57f4fcb3773427 100644 --- a/packages/core-data/src/resolvers.js +++ b/packages/core-data/src/resolvers.js @@ -453,6 +453,19 @@ __experimentalGetTemplateForLink.shouldInvalidate = ( action ) => { ); }; +export const __experimentalGetCurrentSiteGlobalStylesId = + () => + async ( { dispatch } ) => { + const siteGlobalStyles = await apiFetch( { + path: '/wp/v2/global-styles/', + } ); + if ( siteGlobalStyles ) { + dispatch.__experimentalReceiveCurrentSiteGlobalStylesId( + siteGlobalStyles.id + ); + } + }; + export const __experimentalGetCurrentGlobalStylesId = () => async ( { dispatch, resolveSelect } ) => { diff --git a/packages/core-data/src/selectors.ts b/packages/core-data/src/selectors.ts index 07f3c9f48c5ebb..a2cd92ef08b4c1 100644 --- a/packages/core-data/src/selectors.ts +++ b/packages/core-data/src/selectors.ts @@ -32,6 +32,7 @@ export interface State { blockPatterns: Array< unknown >; blockPatternCategories: Array< unknown >; currentGlobalStylesId: string; + currentSiteGlobalStylesId: string; currentTheme: string; currentUser: ET.User< 'edit' >; embedPreviews: Record< string, { html: string } >; @@ -948,6 +949,12 @@ export function __experimentalGetCurrentGlobalStylesId( state: State ): string { return state.currentGlobalStylesId; } +export function __experimentalGetCurrentSiteGlobalStylesId( + state: State +): string { + return state.currentSiteGlobalStylesId; +} + /** * Return theme supports data in the index. * diff --git a/packages/edit-site/src/components/global-styles/global-styles-provider.js b/packages/edit-site/src/components/global-styles/global-styles-provider.js index 7ddd518020569a..48708062945d59 100644 --- a/packages/edit-site/src/components/global-styles/global-styles-provider.js +++ b/packages/edit-site/src/components/global-styles/global-styles-provider.js @@ -34,6 +34,90 @@ export function mergeBaseAndUserConfigs( base, user ) { return mergeWith( {}, base, user, mergeTreesCustomizer ); } +export function mergeConfigs( base, site, user ) { + const baseConfig = mergeWith( {}, base, mergeTreesCustomizer ); + return mergeWith( baseConfig, site, user, mergeTreesCustomizer ); +} + +function useGlobalStylesSiteConfig() { + const { siteGlobalStylesId, isReady, settings, styles } = useSelect( + ( select ) => { + const { getEditedEntityRecord, hasFinishedResolution } = + select( coreStore ); + const _siteGlobalStylesId = + select( + coreStore + ).__experimentalGetCurrentSiteGlobalStylesId(); + const record = _siteGlobalStylesId + ? getEditedEntityRecord( + 'root', + 'siteGlobalStyles', + _siteGlobalStylesId + ) + : undefined; + let hasResolved = false; + if ( + hasFinishedResolution( + '__experimentalGetCurrentSiteGlobalStylesId' + ) + ) { + hasResolved = _siteGlobalStylesId + ? hasFinishedResolution( 'getEditedEntityRecord', [ + 'root', + 'siteGlobalStyles', + _siteGlobalStylesId, + ] ) + : true; + } + + return { + siteGlobalStylesId: _siteGlobalStylesId, + isReady: hasResolved, + settings: record?.settings, + styles: record?.styles, + }; + }, + [] + ); + + const { getEditedEntityRecord } = useSelect( coreStore ); + const { editEntityRecord } = useDispatch( coreStore ); + const config = useMemo( () => { + return { + settings: settings ?? {}, + styles: styles ?? {}, + }; + }, [ settings, styles ] ); + + const setConfig = useCallback( + ( callback, options = {} ) => { + const record = getEditedEntityRecord( + 'root', + 'siteGlobalStyles', + siteGlobalStylesId + ); + const currentConfig = { + styles: record?.styles ?? {}, + settings: record?.settings ?? {}, + }; + const updatedConfig = callback( currentConfig ); + editEntityRecord( + 'root', + 'siteGlobalStyles', + siteGlobalStylesId, + { + styles: cleanEmptyObject( updatedConfig.styles ) || {}, + settings: cleanEmptyObject( updatedConfig.settings ) || {}, + }, + options + ); + }, + [ siteGlobalStylesId ] + ); + + return [ isReady, config, setConfig ]; +} + function useGlobalStylesUserConfig() { const { globalStylesId, isReady, settings, styles } = useSelect( ( select ) => { @@ -126,27 +210,37 @@ function useGlobalStylesContext() { const [ isUserConfigReady, userConfig, setUserConfig ] = useGlobalStylesUserConfig(); const [ isBaseConfigReady, baseConfig ] = useGlobalStylesBaseConfig(); + const [ isSiteConfigReady, siteConfig, setSiteConfig ] = + useGlobalStylesSiteConfig(); + const mergedConfig = useMemo( () => { - if ( ! baseConfig || ! userConfig ) { + if ( ! baseConfig || ! userConfig || ! siteConfig ) { return {}; } - return mergeBaseAndUserConfigs( baseConfig, userConfig ); - }, [ userConfig, baseConfig ] ); + return mergeConfigs( baseConfig, siteConfig, userConfig ); + }, [ userConfig, baseConfig, siteConfig ] ); + const context = useMemo( () => { return { - isReady: isUserConfigReady && isBaseConfigReady, + isReady: + isUserConfigReady && isBaseConfigReady && isSiteConfigReady, + site: siteConfig, user: userConfig, base: baseConfig, merged: mergedConfig, setUserConfig, + setSiteConfig, }; }, [ mergedConfig, userConfig, + siteConfig, baseConfig, setUserConfig, + setSiteConfig, isUserConfigReady, isBaseConfigReady, + isSiteConfigReady, ] ); return context; diff --git a/phpunit/class-wp-theme-json-resolver-test.php b/phpunit/class-wp-theme-json-resolver-test.php index 4cc34bf97b32c7..4e7b62882c06fb 100644 --- a/phpunit/class-wp-theme-json-resolver-test.php +++ b/phpunit/class-wp-theme-json-resolver-test.php @@ -388,10 +388,12 @@ public function test_get_user_data_from_wp_global_styles_does_not_use_uncached_q * @param string $block_styles_text Message. * @param bool $theme_palette Whether the theme palette is present. * @param string $theme_palette_text Message. + * @param bool $site_palette Whether the site palette is present. + * @param string $site_palette_text Message. * @param bool $user_palette Whether the user palette is present. * @param string $user_palette_text Message. */ - public function test_get_merged_data_returns_origin( $origin, $core_palette, $core_palette_text, $block_styles, $block_styles_text, $theme_palette, $theme_palette_text, $user_palette, $user_palette_text ) { + public function test_get_merged_data_returns_origin( $origin, $core_palette, $core_palette_text, $block_styles, $block_styles_text, $theme_palette, $theme_palette_text, $site_palette, $site_palette_text, $user_palette, $user_palette_text ) { // Make sure there is data from the blocks origin. register_block_type( 'my/block-with-styles', @@ -418,18 +420,32 @@ public function test_get_merged_data_returns_origin( $origin, $core_palette, $co // Make sure there is data from the theme origin. switch_theme( 'block-theme' ); + // Make sure there is data from the site origin. + wp_set_current_user( self::$administrator_id ); + $site_cpt = WP_Theme_JSON_Resolver_Gutenberg::get_site_data_from_wp_global_styles( true ); + $site_config = json_decode( $site_cpt['post_content'], true ); + $site_config['settings']['color']['palette']['site'] = array( + array( + 'color' => 'olive', + 'name' => 'Olive green', + 'slug' => 'olive-green', + ), + ); + $site_cpt['post_content'] = wp_json_encode( $site_config ); + wp_update_post( $site_cpt, true, false ); + // Make sure there is data from the user origin. wp_set_current_user( self::$administrator_id ); - $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['settings']['color']['palette']['custom'] = array( + $user_cpt = WP_Theme_JSON_Resolver_Gutenberg::get_user_data_from_wp_global_styles( wp_get_theme(), true ); + $user_config = json_decode( $user_cpt['post_content'], true ); + $user_config['settings']['color']['palette']['custom'] = array( array( 'color' => 'hotpink', 'name' => 'My color', 'slug' => 'my-color', ), ); - $user_cpt['post_content'] = wp_json_encode( $config ); + $user_cpt['post_content'] = wp_json_encode( $user_config ); wp_update_post( $user_cpt, true, false ); $theme_json = WP_Theme_JSON_Resolver_Gutenberg::get_merged_data( $origin ); @@ -444,6 +460,7 @@ static function( $element ) { ); $this->assertSame( $block_styles, count( $styles ) === 1, $block_styles_text ); $this->assertSame( $theme_palette, isset( $settings['color']['palette']['theme'] ), $theme_palette_text ); + $this->assertSame( $site_palette, isset( $settings['color']['palette']['site'] ), $site_palette_text ); $this->assertSame( $user_palette, isset( $settings['color']['palette']['custom'] ), $user_palette_text ); unregister_block_type( 'my/block-with-styles' ); @@ -464,6 +481,8 @@ public function data_get_merged_data_returns_origin() { 'block_styles_text' => 'Block styles should not be present', 'theme_palette' => false, 'theme_palette_text' => 'Theme palette should not be present', + 'site_palette' => false, + 'site_palette_text' => 'Site palette should not be present', 'user_palette' => false, 'user_palette_text' => 'User palette should not be present', ), @@ -475,6 +494,8 @@ public function data_get_merged_data_returns_origin() { 'block_styles_text' => 'Block styles must be present', 'theme_palette' => false, 'theme_palette_text' => 'Theme palette should not be present', + 'site_palette' => false, + 'site_palette_text' => 'Site palette should not be present', 'user_palette' => false, 'user_palette_text' => 'User palette should not be present', ), @@ -486,9 +507,25 @@ public function data_get_merged_data_returns_origin() { 'block_styles_text' => 'Block styles must be present', 'theme_palette' => true, 'theme_palette_text' => 'Theme palette must be present', + 'site_palette' => false, + 'site_palette_text' => 'Site palette should not be present', 'user_palette' => false, 'user_palette_text' => 'User palette should not be present', ), + 'origin_site' => array( + 'origin' => 'site', + 'core_palette' => true, + 'core_palette_text' => 'Core palette must be present', + 'block_styles' => true, + 'block_styles_text' => 'Block styles must be present', + 'theme_palette' => true, + 'theme_palette_text' => 'Theme palette must be present', + 'site_palette' => true, + 'site_palette_text' => 'Site palette should be present', + 'user_palette' => false, + 'user_palette_text' => 'User palette should not be present', + + ), 'origin_custom' => array( 'origin' => 'custom', 'core_palette' => true, @@ -497,6 +534,8 @@ public function data_get_merged_data_returns_origin() { 'block_styles_text' => 'Block styles must be present', 'theme_palette' => true, 'theme_palette_text' => 'Theme palette must be present', + 'site_palette' => true, + 'site_palette_text' => 'Site palette must be present', 'user_palette' => true, 'user_palette_text' => 'User palette must be present', ), @@ -569,4 +608,16 @@ public function test_get_style_variations_returns_all_variations() { ); } + /** + * @covers WP_Theme_JSON_Resolver_Gutenberg::get_site_data_from_wp_global_styles + */ + public function test_get_site_data_from_wp_global_styles_create_post() { + $empty_array = WP_Theme_JSON_Resolver_Gutenberg::get_site_data_from_wp_global_styles(); + $this->assertIsArray( $empty_array ); + $this->assertSameSets( array(), $empty_array ); + $post_data = WP_Theme_JSON_Resolver_Gutenberg::get_site_data_from_wp_global_styles( true ); + $this->assertIsArray( $post_data ); + $this->assertArrayHasKey( 'ID', $post_data ); + } + }