diff --git a/src/wp-includes/block-supports/block-style-variations.php b/src/wp-includes/block-supports/block-style-variations.php index bb663ee754fae..5d92ecfb12734 100644 --- a/src/wp-includes/block-supports/block-style-variations.php +++ b/src/wp-includes/block-supports/block-style-variations.php @@ -212,176 +212,6 @@ function wp_render_block_style_variation_class_name( $block_content, $block ) { return $tags->get_updated_html(); } -/** - * Collects block style variation data for merging with theme.json data. - * - * @since 6.6.0 - * @access private - * - * @param array $variations Shared block style variations. - * - * @return array Block variations data to be merged under `styles.blocks`. - */ -function wp_resolve_block_style_variations( $variations ) { - $variations_data = array(); - - if ( empty( $variations ) ) { - return $variations_data; - } - - $have_named_variations = ! wp_is_numeric_array( $variations ); - - foreach ( $variations as $key => $variation ) { - $supported_blocks = $variation['blockTypes'] ?? array(); - - /* - * Standalone theme.json partial files for block style variations - * will have their styles under a top-level property by the same name. - * Variations defined within an existing theme.json or theme style - * variation will themselves already be the required styles data. - */ - $variation_data = $variation['styles'] ?? $variation; - - if ( empty( $variation_data ) ) { - continue; - } - - /* - * Block style variations read in via standalone theme.json partials - * need to have their name set to the kebab case version of their title. - */ - $variation_name = $have_named_variations ? $key : ( $variation['slug'] ?? _wp_to_kebab_case( $variation['title'] ) ); - - foreach ( $supported_blocks as $block_type ) { - // Add block style variation data under current block type. - $path = array( $block_type, 'variations', $variation_name ); - _wp_array_set( $variations_data, $path, $variation_data ); - } - } - - return $variations_data; -} - -/** - * Merges variations data with existing theme.json data ensuring that the - * current theme.json data values take precedence. - * - * @since 6.6.0 - * @access private - * - * @param array $variations_data Block style variations data keyed by block type. - * @param WP_Theme_JSON_Data $theme_json Current theme.json data. - * @param string $origin Origin for the theme.json data. - * - * @return WP_Theme_JSON The merged theme.json data. - */ -function wp_merge_block_style_variations_data( $variations_data, $theme_json, $origin = 'theme' ) { - if ( empty( $variations_data ) ) { - return $theme_json; - } - - $variations_theme_json_data = array( - 'version' => WP_Theme_JSON::LATEST_SCHEMA, - 'styles' => array( 'blocks' => $variations_data ), - ); - - $variations_theme_json = new WP_Theme_JSON_Data( $variations_theme_json_data, $origin ); - - /* - * Merge the current theme.json data over shared variation data so that - * any explicit per block variation values take precedence. - */ - return $variations_theme_json->update_with( $theme_json->get_data() ); -} - -/** - * Merges any shared block style variation definitions from a theme style - * variation into their appropriate block type within theme json styles. Any - * custom user selections already made will take precedence over the shared - * style variation value. - * - * @since 6.6.0 - * @access private - * - * @param WP_Theme_JSON_Data $theme_json Current theme.json data. - * - * @return WP_Theme_JSON_Data - */ -function wp_resolve_block_style_variations_from_theme_style_variation( $theme_json ) { - $theme_json_data = $theme_json->get_data(); - $shared_variations = $theme_json_data['styles']['blocks']['variations'] ?? array(); - $variations_data = wp_resolve_block_style_variations( $shared_variations ); - - return wp_merge_block_style_variations_data( $variations_data, $theme_json, 'user' ); -} - -/** - * Merges block style variation data sourced from standalone partial - * theme.json files. - * - * @since 6.6.0 - * @access private - * - * @param WP_Theme_JSON_Data $theme_json Current theme.json data. - * - * @return WP_Theme_JSON_Data - */ -function wp_resolve_block_style_variations_from_theme_json_partials( $theme_json ) { - $block_style_variations = WP_Theme_JSON_Resolver::get_style_variations( 'block' ); - $variations_data = wp_resolve_block_style_variations( $block_style_variations ); - - return wp_merge_block_style_variations_data( $variations_data, $theme_json ); -} - -/** - * Merges shared block style variations registered within the - * `styles.blocks.variations` property of the primary theme.json file. - * - * @since 6.6.0 - * @access private - * - * @param WP_Theme_JSON_Data $theme_json Current theme.json data. - * - * @return WP_Theme_JSON_Data - */ -function wp_resolve_block_style_variations_from_primary_theme_json( $theme_json ) { - $theme_json_data = $theme_json->get_data(); - $block_style_variations = $theme_json_data['styles']['blocks']['variations'] ?? array(); - $variations_data = wp_resolve_block_style_variations( $block_style_variations ); - - return wp_merge_block_style_variations_data( $variations_data, $theme_json ); -} - -/** - * Merges block style variations registered via the block styles registry with a - * style object, under their appropriate block types within theme.json styles. - * Any variation values defined within the theme.json specific to a block type - * will take precedence over these shared definitions. - * - * @since 6.6.0 - * @access private - * - * @param WP_Theme_JSON_Data $theme_json Current theme.json data. - * - * @return WP_Theme_JSON_Data - */ -function wp_resolve_block_style_variations_from_styles_registry( $theme_json ) { - $registry = WP_Block_Styles_Registry::get_instance(); - $styles = $registry->get_all_registered(); - $variations_data = array(); - - foreach ( $styles as $block_type => $variations ) { - foreach ( $variations as $variation_name => $variation ) { - if ( ! empty( $variation['style_data'] ) ) { - $path = array( $block_type, 'variations', $variation_name ); - _wp_array_set( $variations_data, $path, $variation['style_data'] ); - } - } - } - - return wp_merge_block_style_variations_data( $variations_data, $theme_json ); -} - /** * Enqueues styles for block style variations. * @@ -399,53 +229,30 @@ function wp_enqueue_block_style_variation_styles() { add_filter( 'render_block', 'wp_render_block_style_variation_class_name', 10, 2 ); add_action( 'wp_enqueue_scripts', 'wp_enqueue_block_style_variation_styles', 1 ); -// Resolve block style variations from all their potential sources. The order here is deliberate. -add_filter( 'wp_theme_json_data_theme', 'wp_resolve_block_style_variations_from_primary_theme_json', 10, 1 ); -add_filter( 'wp_theme_json_data_theme', 'wp_resolve_block_style_variations_from_theme_json_partials', 10, 1 ); -add_filter( 'wp_theme_json_data_theme', 'wp_resolve_block_style_variations_from_styles_registry', 10, 1 ); - -add_filter( 'wp_theme_json_data_user', 'wp_resolve_block_style_variations_from_theme_style_variation', 10, 1 ); - /** - * Registers any block style variations contained within the provided - * theme.json data. + * Registers block style variations read in from theme.json partials. * * @since 6.6.0 * @access private * * @param array $variations Shared block style variations. */ -function wp_register_block_style_variations_from_theme_json_data( $variations ) { +function wp_register_block_style_variations_from_theme_json_partials( $variations ) { if ( empty( $variations ) ) { - return $variations; + return; } - $registry = WP_Block_Styles_Registry::get_instance(); - $have_named_variations = ! wp_is_numeric_array( $variations ); - - foreach ( $variations as $key => $variation ) { - $supported_blocks = $variation['blockTypes'] ?? array(); + $registry = WP_Block_Styles_Registry::get_instance(); - /* - * Standalone theme.json partial files for block style variations - * will have their styles under a top-level property by the same name. - * Variations defined within an existing theme.json or theme style - * variation will themselves already be the required styles data. - */ - $variation_data = $variation['styles'] ?? $variation; - - if ( empty( $variation_data ) ) { + foreach ( $variations as $variation ) { + if ( empty( $variation['blockTypes'] ) || empty( $variation['styles'] ) ) { continue; } - /* - * Block style variations read in via standalone theme.json partials - * need to have their name set to the kebab case version of their title. - */ - $variation_name = $have_named_variations ? $key : ( $variation['slug'] ?? _wp_to_kebab_case( $variation['title'] ) ); + $variation_name = $variation['slug'] ?? _wp_to_kebab_case( $variation['title'] ); $variation_label = $variation['title'] ?? $variation_name; - foreach ( $supported_blocks as $block_type ) { + foreach ( $variation['blockTypes'] as $block_type ) { $registered_styles = $registry->get_registered_styles_for_block( $block_type ); // Register block style variation if it hasn't already been registered. diff --git a/src/wp-includes/class-wp-theme-json-resolver.php b/src/wp-includes/class-wp-theme-json-resolver.php index a67ea80db4f89..a22d7b8d93031 100644 --- a/src/wp-includes/class-wp-theme-json-resolver.php +++ b/src/wp-includes/class-wp-theme-json-resolver.php @@ -231,7 +231,7 @@ protected static function has_same_registered_blocks( $origin ) { * @since 5.9.0 Theme supports have been inlined and the `$theme_support_data` argument removed. * @since 6.0.0 Added an `$options` parameter to allow the theme data to be returned without theme supports. * @since 6.6.0 Add support for 'default-font-sizes' and 'default-spacing-sizes' theme supports. - * Register the block style variations coming from the partials and the theme.json. + * Added registration and merging of block style variations from partial theme.json files and the block styles registry. * * @param array $deprecated Deprecated. Not used. * @param array $options { @@ -258,13 +258,29 @@ public static function get_theme_data( $deprecated = array(), $options = array() $theme_json_data = array( 'version' => WP_Theme_JSON::LATEST_SCHEMA ); } - // Register variations defined by the theme. - $variations = $theme_json_data['styles']['blocks']['variations'] ?? array(); - wp_register_block_style_variations_from_theme_json_data( $variations ); - - // Register variations defined by theme partials (theme.json files in the styles directory). + /* + * Register variations defined by theme partials (theme.json files in the styles directory). + * This is required so the variations pass sanitization of theme.json data. + */ $variations = static::get_style_variations( 'block' ); - wp_register_block_style_variations_from_theme_json_data( $variations ); + wp_register_block_style_variations_from_theme_json_partials( $variations ); + + /* + * Source variations from the block registry and block style variation files. Then, merge them into the existing theme.json data. + * + * In case the same style properties are defined in several sources, this is how we should resolve the values, + * from higher to lower priority: + * + * - styles.blocks.blockType.variations from theme.json + * - styles.variations from theme.json + * - variations from block style variation files + * - variations from block styles registry + * + * See test_add_registered_block_styles_to_theme_data and test_unwraps_block_style_variations. + * + */ + $theme_json_data = static::inject_variations_from_block_style_variation_files( $theme_json_data, $variations ); + $theme_json_data = static::inject_variations_from_block_styles_registry( $theme_json_data ); /** * Filters the data provided by the theme for global styles and settings. @@ -579,10 +595,6 @@ public static function get_user_data() { unset( $decoded_data['isGlobalStylesUserThemeJSON'] ); $config = $decoded_data; } - - // Register variations defined by the user. - $variations = $config['styles']['blocks']['variations'] ?? array(); - wp_register_block_style_variations_from_theme_json_data( $variations ); } /** This filter is documented in wp-includes/class-wp-theme-json-resolver.php */ @@ -885,7 +897,7 @@ public static function get_resolved_theme_uris( $theme_json ) { * * @since 6.6.0 * - * @param WP_Theme_JSON $theme_json A theme json instance. + * @param WP_Theme_JSON $theme_json A theme json instance. * @return WP_Theme_JSON Theme merged with resolved paths, if any found. */ public static function resolve_theme_file_uris( $theme_json ) { @@ -907,4 +919,84 @@ public static function resolve_theme_file_uris( $theme_json ) { return $theme_json; } + + /** + * Adds variations sourced from block style variations files to the supplied theme.json data. + * + * @since 6.6.0 + * + * @param array $data Array following the theme.json specification. + * @param array $variations Shared block style variations. + * @return array Theme json data including shared block style variation definitions. + */ + private static function inject_variations_from_block_style_variation_files( $data, $variations ) { + if ( empty( $variations ) ) { + return $data; + } + + foreach ( $variations as $variation ) { + if ( empty( $variation['styles'] ) || empty( $variation['blockTypes'] ) ) { + continue; + } + + $variation_name = $variation['slug'] ?? _wp_to_kebab_case( $variation['title'] ); + + foreach ( $variation['blockTypes'] as $block_type ) { + // First, override partial styles with any top-level styles. + $top_level_data = $data['styles']['variations'][ $variation_name ] ?? array(); + if ( ! empty( $top_level_data ) ) { + $variation['styles'] = array_replace_recursive( $variation['styles'], $top_level_data ); + } + + // Then, override styles so far with any block-level styles. + $block_level_data = $data['styles']['blocks'][ $block_type ]['variations'][ $variation_name ] ?? array(); + if ( ! empty( $block_level_data ) ) { + $variation['styles'] = array_replace_recursive( $variation['styles'], $block_level_data ); + } + + $path = array( 'styles', 'blocks', $block_type, 'variations', $variation_name ); + _wp_array_set( $data, $path, $variation['styles'] ); + } + } + + return $data; + } + + /** + * Adds variations sourced from the block styles registry to the supplied theme.json data. + * + * @since 6.6.0 + * + * @param array $data Array following the theme.json specification. + * @return array Theme json data including shared block style variation definitions. + */ + private static function inject_variations_from_block_styles_registry( $data ) { + $registry = WP_Block_Styles_Registry::get_instance(); + $styles = $registry->get_all_registered(); + + foreach ( $styles as $block_type => $variations ) { + foreach ( $variations as $variation_name => $variation ) { + if ( empty( $variation['style_data'] ) ) { + continue; + } + + // First, override registry styles with any top-level styles. + $top_level_data = $data['styles']['variations'][ $variation_name ] ?? array(); + if ( ! empty( $top_level_data ) ) { + $variation['style_data'] = array_replace_recursive( $variation['style_data'], $top_level_data ); + } + + // Then, override styles so far with any block-level styles. + $block_level_data = $data['styles']['blocks'][ $block_type ]['variations'][ $variation_name ] ?? array(); + if ( ! empty( $block_level_data ) ) { + $variation['style_data'] = array_replace_recursive( $variation['style_data'], $block_level_data ); + } + + $path = array( 'styles', 'blocks', $block_type, 'variations', $variation_name ); + _wp_array_set( $data, $path, $variation['style_data'] ); + } + } + + return $data; + } } diff --git a/src/wp-includes/class-wp-theme-json.php b/src/wp-includes/class-wp-theme-json.php index 311705b9adaa1..63e24340732cf 100644 --- a/src/wp-includes/class-wp-theme-json.php +++ b/src/wp-includes/class-wp-theme-json.php @@ -743,8 +743,8 @@ public static function get_element_class_name( $element ) { * Constructor. * * @since 5.8.0 - * @since 6.6.0 Key spacingScale by origin, and Pre-generate the - * spacingSizes from spacingScale. + * @since 6.6.0 Key spacingScale by origin, and Pre-generate the spacingSizes from spacingScale. + * Added unwrapping of shared block style variations into block type variations if registered. * * @param array $theme_json A structure that follows the theme.json schema. * @param string $origin Optional. What source of data this object represents. @@ -759,6 +759,7 @@ public function __construct( $theme_json = array( 'version' => self::LATEST_SCHE $valid_block_names = array_keys( static::get_blocks_metadata() ); $valid_element_names = array_keys( static::ELEMENTS ); $valid_variations = static::get_valid_block_style_variations(); + $this->theme_json = static::unwrap_shared_block_style_variations( $this->theme_json, $valid_variations ); $this->theme_json = static::sanitize( $this->theme_json, $valid_block_names, $valid_element_names, $valid_variations ); $this->theme_json = static::maybe_opt_in_into_settings( $this->theme_json ); @@ -802,6 +803,73 @@ public function __construct( $theme_json = array( 'version' => self::LATEST_SCHE } } + /** + * Unwraps shared block style variations. + * + * It takes the shared variations (styles.variations.variationName) and + * applies them to all the blocks that have the given variation registered + * (styles.blocks.blockType.variations.variationName). + * + * For example, given the `core/paragraph` and `core/group` blocks have + * registered the `section-a` style variation, and given the following input: + * + * { + * "styles": { + * "variations": { + * "section-a": { "color": { "background": "backgroundColor" } } + * } + * } + * } + * + * It returns the following output: + * + * { + * "styles": { + * "blocks": { + * "core/paragraph": { + * "variations": { + * "section-a": { "color": { "background": "backgroundColor" } } + * }, + * }, + * "core/group": { + * "variations": { + * "section-a": { "color": { "background": "backgroundColor" } } + * } + * } + * } + * } + * } + * + * @since 6.6.0 + * + * @param array $theme_json A structure that follows the theme.json schema. + * @param array $valid_variations Valid block style variations. + * @return array Theme json data with shared variation definitions unwrapped under appropriate block types. + */ + private static function unwrap_shared_block_style_variations( $theme_json, $valid_variations ) { + if ( empty( $theme_json['styles']['variations'] ) || empty( $valid_variations ) ) { + return $theme_json; + } + + $new_theme_json = $theme_json; + $variations = $new_theme_json['styles']['variations']; + + foreach ( $valid_variations as $block_type => $registered_variations ) { + foreach ( $registered_variations as $variation_name ) { + $block_level_data = $new_theme_json['styles']['blocks'][ $block_type ]['variations'][ $variation_name ] ?? array(); + $top_level_data = $variations[ $variation_name ] ?? array(); + $merged_data = array_replace_recursive( $top_level_data, $block_level_data ); + if ( ! empty( $merged_data ) ) { + _wp_array_set( $new_theme_json, array( 'styles', 'blocks', $block_type, 'variations', $variation_name ), $merged_data ); + } + } + } + + unset( $new_theme_json['styles']['variations'] ); + + return $new_theme_json; + } + /** * Enables some opt-in settings if theme declared support. * @@ -967,12 +1035,6 @@ protected static function sanitize( $input, $valid_block_names, $valid_element_n $schema['settings']['blocks'] = $schema_settings_blocks; $schema['settings']['typography']['fontFamilies'] = static::schema_in_root_and_per_origin( static::FONT_FAMILY_SCHEMA ); - /* - * Shared block style variations can be registered from the theme.json data so we can't - * validate them against pre-registered block style variations. - */ - $schema['styles']['blocks']['variations'] = null; - // Remove anything that's not present in the schema. foreach ( array( 'styles', 'settings' ) as $subtree ) { if ( ! isset( $input[ $subtree ] ) ) { diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-controller.php index 1453e6d1def38..0cb54c91ed3de 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-controller.php @@ -265,13 +265,9 @@ protected function prepare_item_for_database( $request ) { $config['styles'] = $existing_config['styles']; } - // Register theme-defined variations. - WP_Theme_JSON_Resolver::get_theme_data(); - - // Register user-defined variations. - if ( ! empty( $config['styles']['blocks']['variations'] ) ) { - wp_register_block_style_variations_from_theme_json_data( $config['styles']['blocks']['variations'] ); - } + // Register theme-defined variations e.g. from block style variation partials under `/styles`. + $variations = WP_Theme_JSON_Resolver::get_style_variations( 'block' ); + wp_register_block_style_variations_from_theme_json_partials( $variations ); if ( isset( $request['settings'] ) ) { $config['settings'] = $request['settings']; @@ -635,8 +631,12 @@ public function get_theme_items( $request ) { } $response = array(); - $variations = WP_Theme_JSON_Resolver::get_style_variations(); + // Register theme-defined variations e.g. from block style variation partials under `/styles`. + $partials = WP_Theme_JSON_Resolver::get_style_variations( 'block' ); + wp_register_block_style_variations_from_theme_json_partials( $partials ); + + $variations = WP_Theme_JSON_Resolver::get_style_variations(); foreach ( $variations as $variation ) { $variation_theme_json = new WP_Theme_JSON( $variation ); $resolved_theme_uris = WP_Theme_JSON_Resolver::get_resolved_theme_uris( $variation_theme_json ); diff --git a/src/wp-includes/theme-i18n.json b/src/wp-includes/theme-i18n.json index 8bda329add4ce..7ce52317ed65d 100644 --- a/src/wp-includes/theme-i18n.json +++ b/src/wp-includes/theme-i18n.json @@ -80,15 +80,6 @@ } } }, - "styles": { - "blocks": { - "variations": { - "*": { - "title": "Style variation name" - } - } - } - }, "customTemplates": [ { "title": "Custom template name" diff --git a/tests/phpunit/data/themedir1/block-theme-child-with-block-style-variations/theme.json b/tests/phpunit/data/themedir1/block-theme-child-with-block-style-variations/theme.json index a471d8f326a4a..fee9382dce147 100644 --- a/tests/phpunit/data/themedir1/block-theme-child-with-block-style-variations/theme.json +++ b/tests/phpunit/data/themedir1/block-theme-child-with-block-style-variations/theme.json @@ -1,4 +1,55 @@ { "$schema": "https://schemas.wp.org/trunk/theme.json", - "version": 3 + "version": 3, + "styles": { + "variations": { + "outline": { + "color": { + "background": "green", + "text": "white" + } + }, + "block-style-variation-a": { + "color": { + "background": "darkseagreen" + }, + "typography": { + "fontSize": "2em", + "lineHeight": "1.4em" + } + } + }, + "blocks": { + "core/button": { + "variations": { + "outline": { + "color": { + "background": "red" + } + } + } + }, + "core/media-text": { + "variations": { + "block-style-variation-a": { + "color": { + "background": "blue" + }, + "typography": { + "fontSize": "1.5em" + } + } + } + }, + "core/heading": { + "variations": { + "block-style-variation-b": { + "typography": { + "fontSize": "3em" + } + } + } + } + } + } } diff --git a/tests/phpunit/tests/block-supports/block-style-variations.php b/tests/phpunit/tests/block-supports/block-style-variations.php index c9115ad68ef2d..bc5a6b93b8310 100644 --- a/tests/phpunit/tests/block-supports/block-style-variations.php +++ b/tests/phpunit/tests/block-supports/block-style-variations.php @@ -62,15 +62,11 @@ public function filter_set_theme_root() { * * @ticket 61312 * @ticket 61440 + * @ticket 61451 */ public function test_add_registered_block_styles_to_theme_data() { switch_theme( 'block-theme' ); - // Register theme-defined variations. - WP_Theme_JSON_Resolver::get_theme_data(); - // Register user-defined variations. - WP_Theme_JSON_Resolver::get_user_data(); - $variation_styles_data = array( 'color' => array( 'background' => 'darkslateblue', @@ -125,14 +121,6 @@ public function test_add_registered_block_styles_to_theme_data() { $group_styles = $theme_json['styles']['blocks']['core/group'] ?? array(); $expected = array( 'variations' => array( - // @ticket 61440 - 'WithSlug' => array( - 'color' => array( - 'background' => 'aliceblue', - 'text' => 'midnightblue', - ), - ), - 'my-variation' => $variation_styles_data, /* * The following block style variations are registered @@ -151,12 +139,24 @@ public function test_add_registered_block_styles_to_theme_data() { 'text' => 'lightblue', ), ), + + /* + * Manually registered variations. + * @ticket 61440 + */ + 'WithSlug' => array( + 'color' => array( + 'background' => 'aliceblue', + 'text' => 'midnightblue', + ), + ), + 'my-variation' => $variation_styles_data, ), ); unregister_block_style( 'core/group', 'my-variation' ); unregister_block_style( 'core/group', 'WithSlug' ); - $this->assertSameSetsWithIndex( $expected, $group_styles ); + $this->assertSameSetsWithIndex( $expected, $group_styles, 'Variation data does not match' ); } } diff --git a/tests/phpunit/tests/rest-api/rest-global-styles-controller.php b/tests/phpunit/tests/rest-api/rest-global-styles-controller.php index 8e80aa2dfaf33..d4d0aef8e10d2 100644 --- a/tests/phpunit/tests/rest-api/rest-global-styles-controller.php +++ b/tests/phpunit/tests/rest-api/rest-global-styles-controller.php @@ -609,6 +609,7 @@ public function test_update_item_invalid_styles_css() { * * @covers WP_REST_Global_Styles_Controller_Gutenberg::update_item * @ticket 61312 + * @ticket 61451 */ public function test_update_item_with_custom_block_style_variations() { wp_set_current_user( self::$admin_id ); @@ -616,6 +617,18 @@ public function test_update_item_with_custom_block_style_variations() { grant_super_admin( self::$admin_id ); } + /* + * For variations to be resolved they have to have been registered + * via either a theme.json partial or through the WP_Block_Styles_Registry. + */ + register_block_style( + 'core/group', + array( + 'name' => 'fromThemeStyleVariation', + 'label' => 'From Theme Style Variation', + ) + ); + $group_variations = array( 'fromThemeStyleVariation' => array( 'color' => array( @@ -629,16 +642,16 @@ public function test_update_item_with_custom_block_style_variations() { $request->set_body_params( array( 'styles' => array( - 'blocks' => array( - 'variations' => array( - 'fromThemeStyleVariation' => array( - 'blockTypes' => array( 'core/group', 'core/columns' ), - 'color' => array( - 'background' => '#000000', - 'text' => '#ffffff', - ), + 'variations' => array( + 'fromThemeStyleVariation' => array( + 'blockTypes' => array( 'core/group', 'core/columns' ), + 'color' => array( + 'background' => '#000000', + 'text' => '#ffffff', ), ), + ), + 'blocks' => array( 'core/group' => array( 'variations' => $group_variations, ), diff --git a/tests/phpunit/tests/theme/wpThemeJson.php b/tests/phpunit/tests/theme/wpThemeJson.php index 3c5431a32018f..9e596dbed85ea 100644 --- a/tests/phpunit/tests/theme/wpThemeJson.php +++ b/tests/phpunit/tests/theme/wpThemeJson.php @@ -3930,6 +3930,92 @@ public function test_sanitize_for_unregistered_style_variations() { $this->assertSameSetsWithIndex( $expected, $sanitized_theme_json, 'Sanitized theme.json styles does not match' ); } + /** + * @ticket 61451 + */ + public function test_unwraps_block_style_variations() { + register_block_style( + array( 'core/paragraph', 'core/group' ), + array( + 'name' => 'myVariation', + 'label' => 'My variation', + ) + ); + + $input = new WP_Theme_JSON( + array( + 'version' => WP_Theme_JSON::LATEST_SCHEMA, + 'styles' => array( + 'variations' => array( + 'myVariation' => array( + 'color' => array( + 'background' => 'topLevel', + 'gradient' => 'topLevel', + ), + 'typography' => array( + 'fontFamily' => 'topLevel', + ), + ), + ), + 'blocks' => array( + 'core/paragraph' => array( + 'variations' => array( + 'myVariation' => array( + 'color' => array( + 'background' => 'blockLevel', + 'text' => 'blockLevel', + ), + 'outline' => array( + 'offset' => 'blockLevel', + ), + ), + ), + ), + ), + ), + ) + ); + + $expected = array( + 'version' => WP_Theme_JSON::LATEST_SCHEMA, + 'styles' => array( + 'blocks' => array( + 'core/paragraph' => array( + 'variations' => array( + 'myVariation' => array( + 'color' => array( + 'background' => 'blockLevel', + 'gradient' => 'topLevel', + 'text' => 'blockLevel', + ), + 'typography' => array( + 'fontFamily' => 'topLevel', + ), + 'outline' => array( + 'offset' => 'blockLevel', + ), + ), + ), + ), + 'core/group' => array( + 'variations' => array( + 'myVariation' => array( + 'color' => array( + 'background' => 'topLevel', + 'gradient' => 'topLevel', + ), + 'typography' => array( + 'fontFamily' => 'topLevel', + ), + ), + ), + ), + ), + ), + ); + $this->assertSameSetsWithIndex( $expected, $input->get_raw_data(), 'Unwrapped block style variations do not match' ); + } + /** * @ticket 57583 * diff --git a/tests/phpunit/tests/theme/wpThemeJsonResolver.php b/tests/phpunit/tests/theme/wpThemeJsonResolver.php index 0cb102b7accf4..5ad65e8862218 100644 --- a/tests/phpunit/tests/theme/wpThemeJsonResolver.php +++ b/tests/phpunit/tests/theme/wpThemeJsonResolver.php @@ -1321,4 +1321,95 @@ public function test_get_resolved_theme_uris() { $this->assertSame( $expected_data, $actual ); } + + /** + * Tests that block style variations data gets merged in the following + * priority order, from highest priority to lowest. + * + * - `styles.blocks.blockType.variations` from theme.json + * - `styles.variations` from theme.json + * - variations from block style variation files under `/styles` + * - variations from `WP_Block_Styles_Registry` + * + * @ticket 61451 + */ + public function test_block_style_variation_merge_order() { + switch_theme( 'block-theme-child-with-block-style-variations' ); + + /* + * Register style for a block that isn't included in the block style variation's partial + * theme.json's blockTypes. The name must match though so we can ensure the partial's + * styles do not get applied to this block. + */ + register_block_style( + 'core/heading', + array( + 'name' => 'block-style-variation-b', + 'label' => 'Heading only variation', + ) + ); + + // Register variation for a block that will be partially overridden at all levels. + register_block_style( + 'core/media-text', + array( + 'name' => 'block-style-variation-a', + 'label' => 'Block Style Variation A', + 'style_data' => array( + 'color' => array( + 'background' => 'pink', + 'gradient' => 'var(--custom)', + ), + ), + ) + ); + + $data = WP_Theme_JSON_Resolver::get_theme_data()->get_raw_data(); + $block_styles = $data['styles']['blocks'] ?? array(); + $actual = array_intersect_key( + $block_styles, + array_flip( array( 'core/button', 'core/media-text', 'core/heading' ) ) + ); + $expected = array( + 'core/button' => array( + 'variations' => array( + 'outline' => array( + 'color' => array( + 'background' => 'red', + 'text' => 'white', + ), + ), + ), + ), + 'core/media-text' => array( + 'variations' => array( + 'block-style-variation-a' => array( + 'color' => array( + 'background' => 'blue', + 'gradient' => 'var(--custom)', + 'text' => 'aliceblue', + ), + 'typography' => array( + 'fontSize' => '1.5em', + 'lineHeight' => '1.4em', + ), + ), + ), + ), + 'core/heading' => array( + 'variations' => array( + 'block-style-variation-b' => array( + 'typography' => array( + 'fontSize' => '3em', + ), + ), + ), + ), + ); + + unregister_block_style( 'core/heading', 'block-style-variation-b' ); + unregister_block_style( 'core/media-text', 'block-style-variation-a' ); + + $this->assertSameSetsWithIndex( $expected, $actual, 'Merged variation styles do not match.' ); + } }