From ef0fd0b644532ac4a9b8ca0a1e6ec888c6a25728 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Wed, 29 May 2024 19:49:47 +1000 Subject: [PATCH 1/9] Block Styles: Extend block style variations as mechanism for achieving section styling --- .../block-supports/block-style-variations.php | 378 ++++++++++++++++++ .../class-wp-theme-json-resolver.php | 34 +- src/wp-includes/class-wp-theme-json.php | 106 ++++- src/wp-settings.php | 1 + .../style.css | 8 + .../styles/block-style-variation-a.json | 10 + .../theme.json | 4 + .../styles/block-style-variation-a.json | 10 + .../styles/block-style-variation-b.json | 10 + .../block-supports/block-style-variations.php | 132 ++++++ .../tests/theme/wpThemeJsonResolver.php | 163 +++++--- 11 files changed, 785 insertions(+), 71 deletions(-) create mode 100644 src/wp-includes/block-supports/block-style-variations.php create mode 100644 tests/phpunit/data/themedir1/block-theme-child-with-block-style-variations/style.css create mode 100644 tests/phpunit/data/themedir1/block-theme-child-with-block-style-variations/styles/block-style-variation-a.json create mode 100644 tests/phpunit/data/themedir1/block-theme-child-with-block-style-variations/theme.json create mode 100644 tests/phpunit/data/themedir1/block-theme/styles/block-style-variation-a.json create mode 100644 tests/phpunit/data/themedir1/block-theme/styles/block-style-variation-b.json create mode 100644 tests/phpunit/tests/block-supports/block-style-variations.php diff --git a/src/wp-includes/block-supports/block-style-variations.php b/src/wp-includes/block-supports/block-style-variations.php new file mode 100644 index 0000000000000..2b2865db3ec0a --- /dev/null +++ b/src/wp-includes/block-supports/block-style-variations.php @@ -0,0 +1,378 @@ +get_raw_data(); + $variation_data = $theme_json['styles']['blocks'][ $parsed_block['blockName'] ]['variations'][ $variation ] ?? array(); + + if ( empty( $variation_data ) ) { + return $parsed_block; + } + + $config = array( + 'version' => WP_Theme_JSON::LATEST_SCHEMA, + 'styles' => $variation_data, + ); + + $class_name = wp_get_block_style_variation_class_name( $parsed_block, $variation ); + $updated_class_name = $parsed_block['attrs']['className'] . " $class_name"; + + $class_name = ".$class_name"; + + if ( ! is_admin() ) { + remove_filter( 'wp_theme_json_get_style_nodes', 'wp_filter_out_block_nodes' ); + } + + $variation_theme_json = new WP_Theme_JSON( $config, 'blocks' ); + $variation_styles = $variation_theme_json->get_stylesheet( + array( 'styles' ), + array( 'custom' ), + array( + 'root_selector' => $class_name, + 'skip_root_layout_styles' => true, + 'scope' => $class_name, + ) + ); + + if ( ! is_admin() ) { + add_filter( 'wp_theme_json_get_style_nodes', 'wp_filter_out_block_nodes' ); + } + + if ( empty( $variation_styles ) ) { + return $parsed_block; + } + + wp_register_style( 'block-style-variation-styles', false, array( 'global-styles' ) ); + wp_add_inline_style( 'block-style-variation-styles', $variation_styles ); + + /* + * Add variation instance class name to block's className string so it can + * be enforced in the block markup via render_block filter. + */ + _wp_array_set( $parsed_block, array( 'attrs', 'className' ), $updated_class_name ); + + return $parsed_block; +} + +/** + * Ensure the variation block support class name generated and added to + * block attributes in the `render_block_data` filter gets applied to the + * block's markup. + * + * @see wp_render_block_style_variation_support_styles + * + * @since 6.6.0 + * @access private + * + * @param string $block_content Rendered block content. + * @param array $block Block object. + * + * @return string Filtered block content. + */ +function wp_render_block_style_variation_class_name( $block_content, $block ) { + if ( ! $block_content || empty( $block['attrs']['className'] ) ) { + return $block_content; + } + + /* + * Matches a class prefixed by `is-style`, followed by the + * variation slug, then `--`, and finally a hash. + * + * See `wp_get_block_style_variation_class_name` for class generation. + */ + preg_match( '/\bis-style-(\S+?--\w+)\b/', $block['attrs']['className'], $matches ); + + if ( empty( $matches ) ) { + return $block_content; + } + + $tags = new WP_HTML_Tag_Processor( $block_content ); + + if ( $tags->next_tag() ) { + /* + * Ensure the variation instance class name set in the + * `render_block_data` filter is applied in markup. + * See `wp_render_block_style_variation_support_styles`. + */ + $tags->add_class( $matches[0] ); + } + + return $tags->get_updated_html(); +} + +/** + * Collects block style variation data for merging with theme.json data. + * As each block style variation is processed it is registered if it hasn't + * been already. This registration is required for later sanitization of + * 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_and_register_block_style_variations( $variations ) { + $variations_data = array(); + + if ( empty( $variations ) ) { + return $variations_data; + } + + $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(); + + /* + * 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 : _wp_to_kebab_case( $variation['title'] ); + $variation_label = $variation['title'] ?? $variation_name; + + foreach ( $supported_blocks as $block_type ) { + $registered_styles = $registry->get_registered_styles_for_block( $block_type ); + + // Register block style variation if it hasn't already been registered. + if ( ! array_key_exists( $variation_name, $registered_styles ) ) { + register_block_style( + $block_type, + array( + 'name' => $variation_name, + 'label' => $variation_label, + ) + ); + } + + // 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_and_register_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_and_register_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_and_register_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. + * + * @since 6.6.0 + * @access private + */ +function wp_enqueue_block_style_variation_styles() { + wp_enqueue_style( 'block-style-variation-styles' ); +} + +// Register the block support. +WP_Block_Supports::get_instance()->register( 'block-style-variation', array() ); + +add_filter( 'render_block_data', 'wp_render_block_style_variation_support_styles', 10, 2 ); +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 ); diff --git a/src/wp-includes/class-wp-theme-json-resolver.php b/src/wp-includes/class-wp-theme-json-resolver.php index 3ad2df5b4040d..01f6b02f1f9f0 100644 --- a/src/wp-includes/class-wp-theme-json-resolver.php +++ b/src/wp-includes/class-wp-theme-json-resolver.php @@ -701,16 +701,46 @@ private static function recursively_iterate_json( $dir ) { return $nested_json_files; } + /** + * Determines if a supplied style variation matches the provided scope. + * + * For backwards compatibility, if a variation does not define any scope + * related property, e.g. `blockTypes`, it is assumed to be a theme style + * variation. + * + * @since 6.6.0 + * + * @param array $variation Theme.json shaped style variation object. + * @param string $scope Scope to check e.g. theme, block etc. + * + * @return boolean + */ + private static function style_variation_has_scope( $variation, $scope ) { + if ( 'block' === $scope ) { + return isset( $variation['blockTypes'] ); + } + + if ( 'theme' === $scope ) { + return ! isset( $variation['blockTypes'] ); + } + + return false; + } /** * Returns the style variations defined by the theme. * * @since 6.0.0 * @since 6.2.0 Returns parent theme variations if theme is a child. + * @since 6.6.0 Added configurable scope parameter to allow filtering + * theme.json partial files by the scope to which they + * can be applied e.g. theme vs block etc. + * + * @param string $scope The scope or type of style variation to retrieve e.g. theme, block etc. * * @return array */ - public static function get_style_variations() { + public static function get_style_variations( $scope = 'theme' ) { $variation_files = array(); $variations = array(); $base_directory = get_stylesheet_directory() . '/styles'; @@ -733,7 +763,7 @@ public static function get_style_variations() { ksort( $variation_files ); foreach ( $variation_files as $path => $file ) { $decoded_file = wp_json_file_decode( $path, array( 'associative' => true ) ); - if ( is_array( $decoded_file ) ) { + if ( is_array( $decoded_file ) && static::style_variation_has_scope( $decoded_file, $scope ) ) { $translated = static::translate( $decoded_file, wp_get_theme()->get( 'TextDomain' ) ); $variation = ( new WP_Theme_JSON( $translated ) )->get_raw_data(); if ( empty( $variation['title'] ) ) { diff --git a/src/wp-includes/class-wp-theme-json.php b/src/wp-includes/class-wp-theme-json.php index 61d1891322489..ef549a83a5c80 100644 --- a/src/wp-includes/class-wp-theme-json.php +++ b/src/wp-includes/class-wp-theme-json.php @@ -346,9 +346,11 @@ class WP_Theme_JSON { * @since 5.9.0 Renamed from `ALLOWED_TOP_LEVEL_KEYS` to `VALID_TOP_LEVEL_KEYS`, * added the `customTemplates` and `templateParts` values. * @since 6.3.0 Added the `description` value. + * @since 6.6.0 Added `blockTypes` to support block style variation theme.json partials. * @var string[] */ const VALID_TOP_LEVEL_KEYS = array( + 'blockTypes', 'customTemplates', 'description', 'patterns', @@ -823,6 +825,7 @@ protected static function do_opt_in_into_settings( &$context ) { * @since 5.8.0 * @since 5.9.0 Added the `$valid_block_names` and `$valid_element_name` parameters. * @since 6.3.0 Added the `$valid_variations` parameter. + * @since 6.6.0 Updated schema to allow extended block style variations. * * @param array $input Structure to sanitize. * @param array $valid_block_names List of valid block names. @@ -881,6 +884,27 @@ protected static function sanitize( $input, $valid_block_names, $valid_element_n $schema_styles_blocks = array(); $schema_settings_blocks = array(); + + /* + * Generate a schema for blocks. + * - Block styles can contain `elements` & `variations` definitions. + * - Variations definitions cannot be nested. + * - Variations can contain styles for inner `blocks`. + * - Variation inner `blocks` styles can contain `elements`. + * + * As each variation needs a `blocks` schema but further nested + * inner `blocks`, the overall schema will be generated in multiple passes. + */ + foreach ( $valid_block_names as $block ) { + $schema_settings_blocks[ $block ] = static::VALID_SETTINGS; + $schema_styles_blocks[ $block ] = $styles_non_top_level; + $schema_styles_blocks[ $block ]['elements'] = $schema_styles_elements; + } + + $block_style_variation_styles = static::VALID_STYLES; + $block_style_variation_styles['blocks'] = $schema_styles_blocks; + $block_style_variation_styles['elements'] = $schema_styles_elements; + foreach ( $valid_block_names as $block ) { // Build the schema for each block style variation. $style_variation_names = array(); @@ -897,12 +921,9 @@ protected static function sanitize( $input, $valid_block_names, $valid_element_n $schema_styles_variations = array(); if ( ! empty( $style_variation_names ) ) { - $schema_styles_variations = array_fill_keys( $style_variation_names, $styles_non_top_level ); + $schema_styles_variations = array_fill_keys( $style_variation_names, $block_style_variation_styles ); } - $schema_settings_blocks[ $block ] = static::VALID_SETTINGS; - $schema_styles_blocks[ $block ] = $styles_non_top_level; - $schema_styles_blocks[ $block ]['elements'] = $schema_styles_elements; $schema_styles_blocks[ $block ]['variations'] = $schema_styles_variations; } @@ -913,6 +934,12 @@ 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 ] ) ) { @@ -1016,16 +1043,36 @@ protected static function prepend_to_selector( $selector, $to_prepend ) { * @since 5.9.0 Added `duotone` key with CSS selector. * @since 6.1.0 Added `features` key with block support feature level selectors. * @since 6.3.0 Refactored and stabilized selectors API. + * @since 6.6.0 Updated to include block style variations from the block styles registry. * * @return array Block metadata. */ protected static function get_blocks_metadata() { - $registry = WP_Block_Type_Registry::get_instance(); - $blocks = $registry->get_all_registered(); + $registry = WP_Block_Type_Registry::get_instance(); + $blocks = $registry->get_all_registered(); + $style_registry = WP_Block_Styles_Registry::get_instance(); // Is there metadata for all currently registered blocks? $blocks = array_diff_key( $blocks, static::$blocks_metadata ); if ( empty( $blocks ) ) { + /* + * New block styles may have been registered within WP_Block_Styles_Registry. + * Update block metadata for any new block style variations. + */ + $registered_styles = $style_registry->get_all_registered(); + foreach ( static::$blocks_metadata as $block_name => $block_metadata ) { + if ( ! empty( $registered_styles[ $block_name ] ) ) { + $style_selectors = $block_metadata['styleVariations'] ?? array(); + + foreach ( $registered_styles[ $block_name ] as $block_style ) { + if ( ! isset( $style_selectors[ $block_style['name'] ] ) ) { + $style_selectors[ $block_style['name'] ] = static::get_block_style_variation_selector( $block_style['name'], $block_metadata['selector'] ); + } + } + + static::$blocks_metadata[ $block_name ]['styleVariations'] = $style_selectors; + } + } return static::$blocks_metadata; } @@ -1060,12 +1107,21 @@ protected static function get_blocks_metadata() { } // If the block has style variations, append their selectors to the block metadata. + $style_selectors = array(); if ( ! empty( $block_type->styles ) ) { - $style_selectors = array(); foreach ( $block_type->styles as $style ) { $style_selectors[ $style['name'] ] = static::get_block_style_variation_selector( $style['name'], static::$blocks_metadata[ $block_name ]['selector'] ); } - static::$blocks_metadata[ $block_name ]['styleVariations'] = $style_selectors; + } + + // Block style variations can be registered through the WP_Block_Styles_Registry as well as block.json. + $registered_styles = $style_registry->get_registered_styles_for_block( $block_name ); + foreach ( $registered_styles as $style ) { + $style_selectors[ $style['name'] ] = static::get_block_style_variation_selector( $style['name'], static::$blocks_metadata[ $block_name ]['selector'] ); + } + + if ( ! empty( $style_selectors ) ) { + static::$blocks_metadata[ $block_name ]['styleVariations'] = $style_selectors; } } @@ -1158,6 +1214,7 @@ public function get_settings() { * @since 5.8.0 * @since 5.9.0 Removed the `$type` parameter, added the `$types` and `$origins` parameters. * @since 6.3.0 Add fallback layout styles for Post Template when block gap support isn't available. + * @since 6.6.0 Added `skip_root_layout_styles` option to omit layout styles if desired. * * @param string[] $types Types of styles to load. Will load all by default. It accepts: * - `variables`: only the CSS Custom Properties for presets & custom ones. @@ -1165,9 +1222,10 @@ public function get_settings() { * - `presets`: only the classes for the presets. * @param string[] $origins A list of origins to include. By default it includes VALID_ORIGINS. * @param array $options An array of options for now used for internal purposes only (may change without notice). - * The options currently supported are 'scope' that makes sure all style are scoped to a - * given selector, and root_selector which overwrites and forces a given selector to be - * used on the root node. + * The options currently supported are: + * - 'scope' that makes sure all style are scoped to a given selector + * - `root_selector` which overwrites and forces a given selector to be used on the root node + * - `skip_root_layout_styles` which omits root layout styles from the generated stylesheet. * @return string The resulting stylesheet. */ public function get_stylesheet( $types = array( 'variables', 'styles', 'presets' ), $origins = null, $options = array() ) { @@ -1220,7 +1278,7 @@ public function get_stylesheet( $types = array( 'variables', 'styles', 'presets' } if ( in_array( 'styles', $types, true ) ) { - if ( false !== $root_style_key ) { + if ( false !== $root_style_key && empty( $options['skip_root_layout_styles'] ) ) { $stylesheet .= $this->get_root_layout_rules( $style_nodes[ $root_style_key ]['selector'], $style_nodes[ $root_style_key ] ); } $stylesheet .= $this->get_block_classes( $style_nodes ); @@ -3114,6 +3172,7 @@ protected static function filter_slugs( $node, $slugs ) { * * @since 5.9.0 * @since 6.3.2 Preserves global styles block variations when securing styles. + * @since 6.6.0 Updated to allow variation element styles. * * @param array $theme_json Structure to sanitize. * @return array Sanitized structure. @@ -3175,6 +3234,29 @@ public static function remove_insecure_properties( $theme_json ) { } $variation_output = static::remove_insecure_styles( $variation_input ); + + // Process a variation's elements and element pseudo selector styles. + if ( isset( $variation_input['elements'] ) ) { + foreach ( $valid_element_names as $element_name ) { + $element_input = $variation_input['elements'][ $element_name ] ?? null; + if ( $element_input ) { + $element_output = static::remove_insecure_styles( $element_input ); + + if ( isset( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $element_name ] ) ) { + foreach ( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $element_name ] as $pseudo_selector ) { + if ( isset( $element_input[ $pseudo_selector ] ) ) { + $element_output[ $pseudo_selector ] = static::remove_insecure_styles( $element_input[ $pseudo_selector ] ); + } + } + } + + if ( ! empty( $element_output ) ) { + _wp_array_set( $variation_output, array( 'elements', $element_name ), $element_output ); + } + } + } + } + if ( ! empty( $variation_output ) ) { _wp_array_set( $sanitized, $variation['path'], $variation_output ); } diff --git a/src/wp-settings.php b/src/wp-settings.php index 9ea496aa11f10..8467707abe601 100644 --- a/src/wp-settings.php +++ b/src/wp-settings.php @@ -366,6 +366,7 @@ require ABSPATH . WPINC . '/block-supports/utils.php'; require ABSPATH . WPINC . '/block-supports/align.php'; require ABSPATH . WPINC . '/block-supports/background.php'; +require ABSPATH . WPINC . '/block-supports/block-style-variations.php'; require ABSPATH . WPINC . '/block-supports/border.php'; require ABSPATH . WPINC . '/block-supports/colors.php'; require ABSPATH . WPINC . '/block-supports/custom-classname.php'; diff --git a/tests/phpunit/data/themedir1/block-theme-child-with-block-style-variations/style.css b/tests/phpunit/data/themedir1/block-theme-child-with-block-style-variations/style.css new file mode 100644 index 0000000000000..c1cc20aaf1f10 --- /dev/null +++ b/tests/phpunit/data/themedir1/block-theme-child-with-block-style-variations/style.css @@ -0,0 +1,8 @@ +/* +Theme Name: Block Theme Child With Block Style Variations Theme +Theme URI: https://wordpress.org/ +Description: For testing purposes only. +Template: block-theme +Version: 1.0.0 +Text Domain: block-theme-child-with-block-style-variations +*/ diff --git a/tests/phpunit/data/themedir1/block-theme-child-with-block-style-variations/styles/block-style-variation-a.json b/tests/phpunit/data/themedir1/block-theme-child-with-block-style-variations/styles/block-style-variation-a.json new file mode 100644 index 0000000000000..195321a33b336 --- /dev/null +++ b/tests/phpunit/data/themedir1/block-theme-child-with-block-style-variations/styles/block-style-variation-a.json @@ -0,0 +1,10 @@ +{ + "version": 3, + "blockTypes": [ "core/group", "core/columns", "core/media-text" ], + "styles": { + "color": { + "background": "darkcyan", + "text": "aliceblue" + } + } +} 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 new file mode 100644 index 0000000000000..a471d8f326a4a --- /dev/null +++ b/tests/phpunit/data/themedir1/block-theme-child-with-block-style-variations/theme.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://schemas.wp.org/trunk/theme.json", + "version": 3 +} diff --git a/tests/phpunit/data/themedir1/block-theme/styles/block-style-variation-a.json b/tests/phpunit/data/themedir1/block-theme/styles/block-style-variation-a.json new file mode 100644 index 0000000000000..356bc4fc3de7d --- /dev/null +++ b/tests/phpunit/data/themedir1/block-theme/styles/block-style-variation-a.json @@ -0,0 +1,10 @@ +{ + "version": 3, + "blockTypes": [ "core/group", "core/columns" ], + "styles": { + "color": { + "background": "indigo", + "text": "plum" + } + } +} diff --git a/tests/phpunit/data/themedir1/block-theme/styles/block-style-variation-b.json b/tests/phpunit/data/themedir1/block-theme/styles/block-style-variation-b.json new file mode 100644 index 0000000000000..8b79948517255 --- /dev/null +++ b/tests/phpunit/data/themedir1/block-theme/styles/block-style-variation-b.json @@ -0,0 +1,10 @@ +{ + "version": 3, + "blockTypes": [ "core/group", "core/columns" ], + "styles": { + "color": { + "background": "midnightblue", + "text": "lightblue" + } + } +} diff --git a/tests/phpunit/tests/block-supports/block-style-variations.php b/tests/phpunit/tests/block-supports/block-style-variations.php new file mode 100644 index 0000000000000..467144ffdc781 --- /dev/null +++ b/tests/phpunit/tests/block-supports/block-style-variations.php @@ -0,0 +1,132 @@ +theme_root = realpath( DIR_TESTDATA . '/themedir1' ); + + $this->orig_theme_dir = $GLOBALS['wp_theme_directories']; + + // /themes is necessary as theme.php functions assume /themes is the root if there is only one root. + $GLOBALS['wp_theme_directories'] = array( WP_CONTENT_DIR . '/themes', $this->theme_root ); + + add_filter( 'theme_root', array( $this, 'filter_set_theme_root' ) ); + add_filter( 'stylesheet_root', array( $this, 'filter_set_theme_root' ) ); + add_filter( 'template_root', array( $this, 'filter_set_theme_root' ) ); + + // Clear caches. + wp_clean_themes_cache(); + unset( $GLOBALS['wp_themes'] ); + } + + public function tear_down() { + $GLOBALS['wp_theme_directories'] = $this->orig_theme_dir; + wp_clean_themes_cache(); + unset( $GLOBALS['wp_themes'] ); + + // Reset data between tests. + wp_clean_theme_json_cache(); + parent::tear_down(); + } + + public function filter_set_theme_root() { + return $this->theme_root; + } + + /** + * Tests that block style variations registered via either + * `register_block_style` with a style object, or a standalone block style + * variation file within `/styles`, are added to the theme data. + * + * @ticket 61312 + */ + public function test_add_registered_block_styles_to_theme_data() { + switch_theme( 'block-theme' ); + + $variation_styles_data = array( + 'color' => array( + 'background' => 'darkslateblue', + 'text' => 'lavender', + ), + 'blocks' => array( + 'core/heading' => array( + 'color' => array( + 'text' => 'violet', + ), + ), + ), + 'elements' => array( + 'link' => array( + 'color' => array( + 'text' => 'fuchsia', + ), + ':hover' => array( + 'color' => array( + 'text' => 'deeppink', + ), + ), + ), + ), + ); + + register_block_style( + 'core/group', + array( + 'name' => 'my-variation', + 'style_data' => $variation_styles_data, + ) + ); + + $theme_json = WP_Theme_JSON_Resolver::get_theme_data()->get_raw_data(); + $group_styles = $theme_json['styles']['blocks']['core/group'] ?? array(); + $expected = array( + 'variations' => array( + 'my-variation' => $variation_styles_data, + + /* + * The following block style variations are registered + * automatically from their respective JSON files within the + * theme's `/styles` directory. + */ + 'block-style-variation-a' => array( + 'color' => array( + 'background' => 'indigo', + 'text' => 'plum', + ), + ), + 'block-style-variation-b' => array( + 'color' => array( + 'background' => 'midnightblue', + 'text' => 'lightblue', + ), + ), + ), + ); + + unregister_block_style( 'core/group', 'my-variation' ); + + $this->assertSameSetsWithIndex( $group_styles, $expected ); + } +} diff --git a/tests/phpunit/tests/theme/wpThemeJsonResolver.php b/tests/phpunit/tests/theme/wpThemeJsonResolver.php index 52cd20641f45e..581828ba6f29c 100644 --- a/tests/phpunit/tests/theme/wpThemeJsonResolver.php +++ b/tests/phpunit/tests/theme/wpThemeJsonResolver.php @@ -1023,97 +1023,146 @@ public function data_get_merged_data_returns_origin() { } /** - * Tests that get_style_variations returns all variations, including parent theme variations if the theme is a child, - * and that the child variation overwrites the parent variation of the same name. + * Tests that `get_style_variations` returns all the appropriate variations, + * including parent variations if the theme is a child, and that the child + * variation overwrites the parent variation of the same name. + * + * Note: This covers both theme and block style variations. * * @ticket 57545 + * @ticket 61312 * * @covers WP_Theme_JSON_Resolver::get_style_variations + * + * @dataProvider data_get_style_variations + * + * @param string $theme Name of the theme to use. + * @param string $scope Scope to filter variations by e.g. theme vs block. + * @param array $expected_variations Collection of expected variations. */ - public function test_get_style_variations_returns_all_variations() { - // Switch to a child theme. - switch_theme( 'block-theme-child' ); + public function test_get_style_variations( $theme, $scope, $expected_variations ) { + switch_theme( $theme ); wp_set_current_user( self::$administrator_id ); - $actual_settings = WP_Theme_JSON_Resolver::get_style_variations(); - $expected_settings = array( - array( - 'version' => WP_Theme_JSON::LATEST_SCHEMA, - 'title' => 'variation-a', - 'settings' => array( - 'blocks' => array( - 'core/paragraph' => array( - 'color' => array( - 'palette' => array( - 'theme' => array( - array( - 'slug' => 'dark', - 'name' => 'Dark', - 'color' => '#010101', + $actual_variations = WP_Theme_JSON_Resolver::get_style_variations( $scope ); + + wp_recursive_ksort( $actual_variations ); + wp_recursive_ksort( $expected_variations ); + + $this->assertSame( $expected_variations, $actual_variations ); + } + + /** + * Data provider for test_get_style_variations + * + * @return array + */ + public function data_get_style_variations() { + return array( + // @ticket 57545 + 'theme_style_variations' => array( + 'theme' => 'block-theme-child', + 'scope' => 'theme', + 'expected_variations' => array( + array( + 'version' => WP_Theme_JSON::LATEST_SCHEMA, + 'title' => 'variation-a', + 'settings' => array( + 'blocks' => array( + 'core/paragraph' => array( + 'color' => array( + 'palette' => array( + 'theme' => array( + array( + 'slug' => 'dark', + 'name' => 'Dark', + 'color' => '#010101', + ), + ), ), ), ), ), ), ), - ), - ), - array( - 'version' => WP_Theme_JSON::LATEST_SCHEMA, - 'title' => 'variation-b', - 'settings' => array( - 'blocks' => array( - 'core/post-title' => array( + array( + 'version' => WP_Theme_JSON::LATEST_SCHEMA, + 'title' => 'variation-b', + 'settings' => array( + 'blocks' => array( + 'core/post-title' => array( + 'color' => array( + 'palette' => array( + 'theme' => array( + array( + 'slug' => 'dark', + 'name' => 'Dark', + 'color' => '#010101', + ), + ), + ), + ), + ), + ), + ), + ), + array( + 'version' => WP_Theme_JSON::LATEST_SCHEMA, + 'title' => 'Block theme variation', + 'settings' => array( 'color' => array( 'palette' => array( 'theme' => array( array( - 'slug' => 'dark', - 'name' => 'Dark', - 'color' => '#010101', + 'slug' => 'foreground', + 'name' => 'Foreground', + 'color' => '#3F67C6', ), ), ), ), ), + 'styles' => array( + 'blocks' => array( + 'core/post-title' => array( + 'typography' => array( + 'fontWeight' => '700', + ), + ), + ), + ), ), ), ), - array( - 'version' => WP_Theme_JSON::LATEST_SCHEMA, - 'title' => 'Block theme variation', - 'settings' => array( - 'color' => array( - 'palette' => array( - 'theme' => array( - array( - 'slug' => 'foreground', - 'name' => 'Foreground', - 'color' => '#3F67C6', - ), + 'block_style_variations' => array( + 'theme' => 'block-theme-child-with-block-style-variations', + 'scope' => 'block', + 'expected_variations' => array( + array( + 'blockTypes' => array( 'core/group', 'core/columns', 'core/media-text' ), + 'version' => 3, + 'title' => 'block-style-variation-a', + 'styles' => array( + 'color' => array( + 'background' => 'darkcyan', + 'text' => 'aliceblue', ), ), ), - ), - 'styles' => array( - 'blocks' => array( - 'core/post-title' => array( - 'typography' => array( - 'fontWeight' => '700', + array( + 'blockTypes' => array( 'core/group', 'core/columns' ), + 'version' => 3, + 'title' => 'block-style-variation-b', + 'styles' => array( + 'color' => array( + 'background' => 'midnightblue', + 'text' => 'lightblue', ), ), ), ), ), ); - - wp_recursive_ksort( $actual_settings ); - wp_recursive_ksort( $expected_settings ); - - $this->assertSame( - $expected_settings, - $actual_settings - ); } /** From 3fcd1a84836335b797f28e4941f816b302760678 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Thu, 30 May 2024 14:07:22 +1000 Subject: [PATCH 2/9] Add new test theme to theme dir test --- tests/phpunit/tests/theme/themeDir.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/phpunit/tests/theme/themeDir.php b/tests/phpunit/tests/theme/themeDir.php index 1d6fb45db3341..a953a04bc5533 100644 --- a/tests/phpunit/tests/theme/themeDir.php +++ b/tests/phpunit/tests/theme/themeDir.php @@ -177,6 +177,7 @@ public function test_theme_list() { 'Block Theme', 'Block Theme Child Theme', 'Block Theme Child Deprecated Path', + 'Block Theme Child With Block Style Variations Theme', 'Block Theme Child with no theme.json', 'Block Theme Child Theme With Fluid Layout', 'Block Theme Child Theme With Fluid Typography', From fbad0fc745ce3cbceeed8252a10fe3bab31804df Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Thu, 30 May 2024 17:16:53 +1000 Subject: [PATCH 3/9] Bring block style variations hook up to date A couple of function were out of date with Gutenberg and a new bug fix is also due to land. --- .../block-supports/block-style-variations.php | 78 ++++++++++++++----- 1 file changed, 59 insertions(+), 19 deletions(-) diff --git a/src/wp-includes/block-supports/block-style-variations.php b/src/wp-includes/block-supports/block-style-variations.php index 2b2865db3ec0a..ef8a224c21234 100644 --- a/src/wp-includes/block-supports/block-style-variations.php +++ b/src/wp-includes/block-supports/block-style-variations.php @@ -23,22 +23,21 @@ function wp_get_block_style_variation_class_name( $block, $variation ) { } /** - * Determine a block style variation name from a CSS class string. + * Determines the block style variation names within a CSS class string. * * @since 6.6.0 - * @access private * * @param string $class_string CSS class string to look for a variation in. * - * @return string|null The block style variation name if found. + * @return array|null The block style variation name if found. */ function wp_get_block_style_variation_name_from_class( $class_string ) { if ( ! is_string( $class_string ) ) { return null; } - preg_match( '/\bis-style-(?!default)(\S+)\b/', $class_string, $matches ); - return $matches ? $matches[1] : null; + preg_match_all( '/\bis-style-(?!default)(\S+)\b/', $class_string, $matches ); + return $matches[1] ?? null; } /** @@ -59,46 +58,87 @@ function wp_get_block_style_variation_name_from_class( $class_string ) { * @return array The parsed block with block style variation classname added. */ function wp_render_block_style_variation_support_styles( $parsed_block ) { - $classes = $parsed_block['attrs']['className'] ?? null; - $variation = wp_get_block_style_variation_name_from_class( $classes ); + $classes = $parsed_block['attrs']['className'] ?? null; + $variations = wp_get_block_style_variation_name_from_class( $classes ); - if ( ! $variation ) { + if ( ! $variations ) { return $parsed_block; } - $tree = WP_Theme_JSON_Resolver::get_merged_data(); - $theme_json = $tree->get_raw_data(); - $variation_data = $theme_json['styles']['blocks'][ $parsed_block['blockName'] ]['variations'][ $variation ] ?? array(); + $tree = WP_Theme_JSON_Resolver::get_merged_data(); + $theme_json = $tree->get_raw_data(); + + // Only the first block style variation with data is supported. + $variation_data = array(); + foreach ( $variations as $variation ) { + $variation_data = $theme_json['styles']['blocks'][ $parsed_block['blockName'] ]['variations'][ $variation ] ?? array(); + + if ( ! empty( $variation_data ) ) { + break; + } + } if ( empty( $variation_data ) ) { return $parsed_block; } - $config = array( - 'version' => WP_Theme_JSON::LATEST_SCHEMA, - 'styles' => $variation_data, - ); - $class_name = wp_get_block_style_variation_class_name( $parsed_block, $variation ); $updated_class_name = $parsed_block['attrs']['className'] . " $class_name"; + $variation_instance = substr( $class_name, 9 ); + $class_name = ".$class_name"; - $class_name = ".$class_name"; + /* + * To support blocks with more complex selectors, that can apply styles to + * inner markup or even different inner elements depending on the feature, + * the variation style data needs to be manipulated some. If the root variation + * styles are moved under a variations property they will generate as desired. + * + * Example blocks that require this approach include: + * - Button: `.wp-block-button .wp-block-button__link` + * - Image: `.wp-block-image` and `.wp-block-image img` for borders, shadow etc. + */ + $elements_data = $variation_data['elements'] ?? array(); + $blocks_data = $variation_data['blocks'] ?? array(); + unset( $variation_data['elements'] ); + unset( $variation_data['blocks'] ); + + _wp_array_set( + $blocks_data, + array( $parsed_block['blockName'], 'variations', $variation_instance ), + $variation_data + ); + $config = array( + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'styles' => array( + 'elements' => $elements_data, + 'blocks' => $blocks_data, + ), + ); + + // Turn off filter that excludes block nodes. They are needed here. if ( ! is_admin() ) { remove_filter( 'wp_theme_json_get_style_nodes', 'wp_filter_out_block_nodes' ); } - $variation_theme_json = new WP_Theme_JSON( $config, 'blocks' ); + // Temporarily prevent variation instance from being sanitized while processing theme.json. + $styles_registry = WP_Block_Styles_Registry::get_instance(); + $styles_registry->register( $parsed_block['blockName'], array( 'name' => $variation_instance ) ); + + $variation_theme_json = new WP_Theme_JSON_Gutenberg( $config, 'blocks' ); $variation_styles = $variation_theme_json->get_stylesheet( array( 'styles' ), array( 'custom' ), array( - 'root_selector' => $class_name, 'skip_root_layout_styles' => true, 'scope' => $class_name, ) ); + // Clean up temporary block style now instance styles have been processed. + $styles_registry->unregister( $parsed_block['blockName'], $variation_instance ); + + // Restore filter that excludes block nodes. They are needed here. if ( ! is_admin() ) { add_filter( 'wp_theme_json_get_style_nodes', 'wp_filter_out_block_nodes' ); } From 8433777e3e8517c31fba82cfeb312003bf22d740 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Thu, 30 May 2024 18:19:10 +1000 Subject: [PATCH 4/9] Address feedback --- .../block-supports/block-style-variations.php | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/wp-includes/block-supports/block-style-variations.php b/src/wp-includes/block-supports/block-style-variations.php index ef8a224c21234..35d63af973aa4 100644 --- a/src/wp-includes/block-supports/block-style-variations.php +++ b/src/wp-includes/block-supports/block-style-variations.php @@ -8,7 +8,7 @@ */ /** - * Get the class name for this application of this block's variation styles. + * Generate class name for this application of this block's variation styles. * * @since 6.6.0 * @access private @@ -18,7 +18,7 @@ * * @return string The unique class name. */ -function wp_get_block_style_variation_class_name( $block, $variation ) { +function wp_create_block_style_variation_class_name( $block, $variation ) { return 'is-style-' . $variation . '--' . md5( serialize( $block ) ); } @@ -82,10 +82,10 @@ function wp_render_block_style_variation_support_styles( $parsed_block ) { return $parsed_block; } - $class_name = wp_get_block_style_variation_class_name( $parsed_block, $variation ); + $class_name = wp_create_block_style_variation_class_name( $parsed_block, $variation ); $updated_class_name = $parsed_block['attrs']['className'] . " $class_name"; $variation_instance = substr( $class_name, 9 ); - $class_name = ".$class_name"; + $selector = ".$class_name"; /* * To support blocks with more complex selectors, that can apply styles to @@ -109,14 +109,14 @@ function wp_render_block_style_variation_support_styles( $parsed_block ) { ); $config = array( - 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'version' => WP_Theme_JSON::LATEST_SCHEMA, 'styles' => array( 'elements' => $elements_data, 'blocks' => $blocks_data, ), ); - // Turn off filter that excludes block nodes. They are needed here. + // Turn off filter that excludes block nodes. They are needed here for the variation's inner block types. if ( ! is_admin() ) { remove_filter( 'wp_theme_json_get_style_nodes', 'wp_filter_out_block_nodes' ); } @@ -125,20 +125,20 @@ function wp_render_block_style_variation_support_styles( $parsed_block ) { $styles_registry = WP_Block_Styles_Registry::get_instance(); $styles_registry->register( $parsed_block['blockName'], array( 'name' => $variation_instance ) ); - $variation_theme_json = new WP_Theme_JSON_Gutenberg( $config, 'blocks' ); + $variation_theme_json = new WP_Theme_JSON( $config, 'blocks' ); $variation_styles = $variation_theme_json->get_stylesheet( array( 'styles' ), array( 'custom' ), array( 'skip_root_layout_styles' => true, - 'scope' => $class_name, + 'scope' => $selector, ) ); // Clean up temporary block style now instance styles have been processed. $styles_registry->unregister( $parsed_block['blockName'], $variation_instance ); - // Restore filter that excludes block nodes. They are needed here. + // Restore filter that excludes block nodes. if ( ! is_admin() ) { add_filter( 'wp_theme_json_get_style_nodes', 'wp_filter_out_block_nodes' ); } @@ -183,7 +183,7 @@ function wp_render_block_style_variation_class_name( $block_content, $block ) { * Matches a class prefixed by `is-style`, followed by the * variation slug, then `--`, and finally a hash. * - * See `wp_get_block_style_variation_class_name` for class generation. + * See `wp_create_block_style_variation_class_name` for class generation. */ preg_match( '/\bis-style-(\S+?--\w+)\b/', $block['attrs']['className'], $matches ); @@ -216,7 +216,7 @@ function wp_render_block_style_variation_class_name( $block_content, $block ) { * * @param array $variations Shared block style variations. * - * @return array Block variations data to be merged under styles.blocks + * @return array Block variations data to be merged under `styles.blocks`. */ function wp_resolve_and_register_block_style_variations( $variations ) { $variations_data = array(); From 47553b973004f889c179ab2d0b87c63c8b37ad23 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Thu, 30 May 2024 19:19:32 +1000 Subject: [PATCH 5/9] Add unit tests for variation class name utils --- .../wpCreateBlockStyleVariationClassName.php | 23 +++++++ .../wpGetBlockStyleVariationNameFromClass.php | 65 +++++++++++++++++++ 2 files changed, 88 insertions(+) create mode 100644 tests/phpunit/tests/block-supports/wpCreateBlockStyleVariationClassName.php create mode 100644 tests/phpunit/tests/block-supports/wpGetBlockStyleVariationNameFromClass.php diff --git a/tests/phpunit/tests/block-supports/wpCreateBlockStyleVariationClassName.php b/tests/phpunit/tests/block-supports/wpCreateBlockStyleVariationClassName.php new file mode 100644 index 0000000000000..4db86a3fa1506 --- /dev/null +++ b/tests/phpunit/tests/block-supports/wpCreateBlockStyleVariationClassName.php @@ -0,0 +1,23 @@ + 'test/block' ); + $actual = wp_create_block_style_variation_class_name( $block, 'my-variation' ); + $expected = 'is-style-my-variation--' . md5( serialize( $block ) ); + + $this->assertSame( $expected, $actual, 'Block style variation class name should be correct' ); + } +} diff --git a/tests/phpunit/tests/block-supports/wpGetBlockStyleVariationNameFromClass.php b/tests/phpunit/tests/block-supports/wpGetBlockStyleVariationNameFromClass.php new file mode 100644 index 0000000000000..d1bf080e18639 --- /dev/null +++ b/tests/phpunit/tests/block-supports/wpGetBlockStyleVariationNameFromClass.php @@ -0,0 +1,65 @@ +assertSame( + $expected, + $actual, + 'Block style variation names extracted from CSS class string should match' + ); + } + + /** + * Data provider. + * + * @return array + */ + public function data_block_style_variation_name_extraction() { + return array( + // @ticket 61312 + 'missing class string' => array( + 'class_string' => null, + 'expected' => null, + ), + // @ticket 61312 + 'empty class string' => array( + 'class_string' => '', + 'expected' => array(), + ), + // @ticket 61312 + 'no variation' => array( + 'class_string' => 'is-style no-variation', + 'expected' => array(), + ), + // @ticket 61312 + 'single variation' => array( + 'class_string' => 'custom-class is-style-outline', + 'expected' => array( 'outline' ), + ), + // @ticket 61312 + 'multiple variations' => array( + 'class_string' => 'is-style-light custom-class is-style-outline', + 'expected' => array( 'light', 'outline' ), + ), + ); + } +} From f573459ea544bf4ae5bc78c81fe6bd88906d80ed Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Thu, 30 May 2024 19:26:32 +1000 Subject: [PATCH 6/9] Improve inline explanation around fix for complex block selectors --- .../block-supports/block-style-variations.php | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/wp-includes/block-supports/block-style-variations.php b/src/wp-includes/block-supports/block-style-variations.php index 35d63af973aa4..62e6dfffef1c7 100644 --- a/src/wp-includes/block-supports/block-style-variations.php +++ b/src/wp-includes/block-supports/block-style-variations.php @@ -88,14 +88,21 @@ function wp_render_block_style_variation_support_styles( $parsed_block ) { $selector = ".$class_name"; /* - * To support blocks with more complex selectors, that can apply styles to - * inner markup or even different inner elements depending on the feature, - * the variation style data needs to be manipulated some. If the root variation - * styles are moved under a variations property they will generate as desired. + * Even though block style variations are effectively theme.json partials, + * they can't be processed completely as though they are. * - * Example blocks that require this approach include: - * - Button: `.wp-block-button .wp-block-button__link` - * - Image: `.wp-block-image` and `.wp-block-image img` for borders, shadow etc. + * Block styles support custom selectors to direct specific types of styles + * to inner elements. For example, borders on Image block's get applied to + * the inner `img` element rather than the wrapping `figure`. + * + * The following relocates the "root" block style variation styles to + * under an appropriate blocks property to leverage the preexisting style + * generation for simple block style variations. This way they get the + * custom selectors they need. + * + * The inner elements and block styles for the variation itself are + * still included at the top level but scoped by the variation's selector + * when the stylesheet is generated. */ $elements_data = $variation_data['elements'] ?? array(); $blocks_data = $variation_data['blocks'] ?? array(); From bdb755193af5c3235d7e311efd190ca2ecf81527 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Thu, 30 May 2024 20:01:10 +1000 Subject: [PATCH 7/9] Bring in feedback from Gutenberg PR --- .../block-supports/block-style-variations.php | 17 +++++++------ .../wpCreateBlockStyleVariationClassName.php | 23 ------------------ ...pCreateBlockStyleVariationInstanceName.php | 24 +++++++++++++++++++ 3 files changed, 32 insertions(+), 32 deletions(-) delete mode 100644 tests/phpunit/tests/block-supports/wpCreateBlockStyleVariationClassName.php create mode 100644 tests/phpunit/tests/block-supports/wpCreateBlockStyleVariationInstanceName.php diff --git a/src/wp-includes/block-supports/block-style-variations.php b/src/wp-includes/block-supports/block-style-variations.php index 62e6dfffef1c7..f78d9390bedef 100644 --- a/src/wp-includes/block-supports/block-style-variations.php +++ b/src/wp-includes/block-supports/block-style-variations.php @@ -8,7 +8,7 @@ */ /** - * Generate class name for this application of this block's variation styles. + * Generate block style variation instance name. * * @since 6.6.0 * @access private @@ -16,10 +16,10 @@ * @param array $block Block object. * @param string $variation Slug for the block style variation. * - * @return string The unique class name. + * @return string The unique variation name. */ -function wp_create_block_style_variation_class_name( $block, $variation ) { - return 'is-style-' . $variation . '--' . md5( serialize( $block ) ); +function wp_create_block_style_variation_instance_name( $block, $variation ) { + return $variation . '--' . md5( serialize( $block ) ); } /** @@ -82,10 +82,9 @@ function wp_render_block_style_variation_support_styles( $parsed_block ) { return $parsed_block; } - $class_name = wp_create_block_style_variation_class_name( $parsed_block, $variation ); + $variation_instance = wp_create_block_style_variation_instance_name( $parsed_block, $variation ); + $class_name = "is-style-$variation_instance"; $updated_class_name = $parsed_block['attrs']['className'] . " $class_name"; - $variation_instance = substr( $class_name, 9 ); - $selector = ".$class_name"; /* * Even though block style variations are effectively theme.json partials, @@ -138,7 +137,7 @@ function wp_render_block_style_variation_support_styles( $parsed_block ) { array( 'custom' ), array( 'skip_root_layout_styles' => true, - 'scope' => $selector, + 'scope' => ".$class_name", ) ); @@ -190,7 +189,7 @@ function wp_render_block_style_variation_class_name( $block_content, $block ) { * Matches a class prefixed by `is-style`, followed by the * variation slug, then `--`, and finally a hash. * - * See `wp_create_block_style_variation_class_name` for class generation. + * See `wp_create_block_style_variation_instance_name` for class generation. */ preg_match( '/\bis-style-(\S+?--\w+)\b/', $block['attrs']['className'], $matches ); diff --git a/tests/phpunit/tests/block-supports/wpCreateBlockStyleVariationClassName.php b/tests/phpunit/tests/block-supports/wpCreateBlockStyleVariationClassName.php deleted file mode 100644 index 4db86a3fa1506..0000000000000 --- a/tests/phpunit/tests/block-supports/wpCreateBlockStyleVariationClassName.php +++ /dev/null @@ -1,23 +0,0 @@ - 'test/block' ); - $actual = wp_create_block_style_variation_class_name( $block, 'my-variation' ); - $expected = 'is-style-my-variation--' . md5( serialize( $block ) ); - - $this->assertSame( $expected, $actual, 'Block style variation class name should be correct' ); - } -} diff --git a/tests/phpunit/tests/block-supports/wpCreateBlockStyleVariationInstanceName.php b/tests/phpunit/tests/block-supports/wpCreateBlockStyleVariationInstanceName.php new file mode 100644 index 0000000000000..1e37d48e8ed1d --- /dev/null +++ b/tests/phpunit/tests/block-supports/wpCreateBlockStyleVariationInstanceName.php @@ -0,0 +1,24 @@ + 'test/block' ); + $actual = wp_create_block_style_variation_instance_name( $block, 'my-variation' ); + $expected = 'my-variation--' . md5( serialize( $block ) ); + + $this->assertSame( $expected, $actual, 'Block style variation instance name should be correct' ); + } +} From cfadbb402df0f0c46e5bd0bf6be374ab5863fbbf Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Fri, 31 May 2024 10:04:08 +1000 Subject: [PATCH 8/9] Ensure block style variation styles load after block library --- src/wp-includes/block-supports/block-style-variations.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wp-includes/block-supports/block-style-variations.php b/src/wp-includes/block-supports/block-style-variations.php index f78d9390bedef..c8ba6e75aaa80 100644 --- a/src/wp-includes/block-supports/block-style-variations.php +++ b/src/wp-includes/block-supports/block-style-variations.php @@ -153,7 +153,7 @@ function wp_render_block_style_variation_support_styles( $parsed_block ) { return $parsed_block; } - wp_register_style( 'block-style-variation-styles', false, array( 'global-styles' ) ); + wp_register_style( 'block-style-variation-styles', false, array( 'global-styles', 'wp-block-library' ) ); wp_add_inline_style( 'block-style-variation-styles', $variation_styles ); /* From 3598752431c0d5ca3956638bb70192e72f17d088 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Fri, 31 May 2024 14:03:02 +1000 Subject: [PATCH 9/9] Fix indentation --- .../class-wp-theme-json-resolver.php | 4 ++-- src/wp-includes/class-wp-theme-json.php | 18 +++++++++--------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/wp-includes/class-wp-theme-json-resolver.php b/src/wp-includes/class-wp-theme-json-resolver.php index 01f6b02f1f9f0..814b291487352 100644 --- a/src/wp-includes/class-wp-theme-json-resolver.php +++ b/src/wp-includes/class-wp-theme-json-resolver.php @@ -717,11 +717,11 @@ private static function recursively_iterate_json( $dir ) { */ private static function style_variation_has_scope( $variation, $scope ) { if ( 'block' === $scope ) { - return isset( $variation['blockTypes'] ); + return isset( $variation['blockTypes'] ); } if ( 'theme' === $scope ) { - return ! isset( $variation['blockTypes'] ); + return ! isset( $variation['blockTypes'] ); } return false; diff --git a/src/wp-includes/class-wp-theme-json.php b/src/wp-includes/class-wp-theme-json.php index ef549a83a5c80..bdafc03863a3b 100644 --- a/src/wp-includes/class-wp-theme-json.php +++ b/src/wp-includes/class-wp-theme-json.php @@ -896,9 +896,9 @@ protected static function sanitize( $input, $valid_block_names, $valid_element_n * inner `blocks`, the overall schema will be generated in multiple passes. */ foreach ( $valid_block_names as $block ) { - $schema_settings_blocks[ $block ] = static::VALID_SETTINGS; - $schema_styles_blocks[ $block ] = $styles_non_top_level; - $schema_styles_blocks[ $block ]['elements'] = $schema_styles_elements; + $schema_settings_blocks[ $block ] = static::VALID_SETTINGS; + $schema_styles_blocks[ $block ] = $styles_non_top_level; + $schema_styles_blocks[ $block ]['elements'] = $schema_styles_elements; } $block_style_variation_styles = static::VALID_STYLES; @@ -1062,15 +1062,15 @@ protected static function get_blocks_metadata() { $registered_styles = $style_registry->get_all_registered(); foreach ( static::$blocks_metadata as $block_name => $block_metadata ) { if ( ! empty( $registered_styles[ $block_name ] ) ) { - $style_selectors = $block_metadata['styleVariations'] ?? array(); + $style_selectors = $block_metadata['styleVariations'] ?? array(); foreach ( $registered_styles[ $block_name ] as $block_style ) { if ( ! isset( $style_selectors[ $block_style['name'] ] ) ) { - $style_selectors[ $block_style['name'] ] = static::get_block_style_variation_selector( $block_style['name'], $block_metadata['selector'] ); + $style_selectors[ $block_style['name'] ] = static::get_block_style_variation_selector( $block_style['name'], $block_metadata['selector'] ); } } - static::$blocks_metadata[ $block_name ]['styleVariations'] = $style_selectors; + static::$blocks_metadata[ $block_name ]['styleVariations'] = $style_selectors; } } return static::$blocks_metadata; @@ -1117,11 +1117,11 @@ protected static function get_blocks_metadata() { // Block style variations can be registered through the WP_Block_Styles_Registry as well as block.json. $registered_styles = $style_registry->get_registered_styles_for_block( $block_name ); foreach ( $registered_styles as $style ) { - $style_selectors[ $style['name'] ] = static::get_block_style_variation_selector( $style['name'], static::$blocks_metadata[ $block_name ]['selector'] ); + $style_selectors[ $style['name'] ] = static::get_block_style_variation_selector( $style['name'], static::$blocks_metadata[ $block_name ]['selector'] ); } if ( ! empty( $style_selectors ) ) { - static::$blocks_metadata[ $block_name ]['styleVariations'] = $style_selectors; + static::$blocks_metadata[ $block_name ]['styleVariations'] = $style_selectors; } } @@ -3238,7 +3238,7 @@ public static function remove_insecure_properties( $theme_json ) { // Process a variation's elements and element pseudo selector styles. if ( isset( $variation_input['elements'] ) ) { foreach ( $valid_element_names as $element_name ) { - $element_input = $variation_input['elements'][ $element_name ] ?? null; + $element_input = $variation_input['elements'][ $element_name ] ?? null; if ( $element_input ) { $element_output = static::remove_insecure_styles( $element_input );