diff options
Diffstat (limited to 'wp-includes/block-supports/block-style-variations.php')
-rw-r--r-- | wp-includes/block-supports/block-style-variations.php | 312 |
1 files changed, 312 insertions, 0 deletions
diff --git a/wp-includes/block-supports/block-style-variations.php b/wp-includes/block-supports/block-style-variations.php new file mode 100644 index 0000000..77659bf --- /dev/null +++ b/wp-includes/block-supports/block-style-variations.php @@ -0,0 +1,312 @@ +<?php +/** + * Block support to enable per-section styling of block types via + * block style variations. + * + * @package WordPress + * @since 6.6.0 + */ + +/** + * Generate block style variation instance name. + * + * @since 6.6.0 + * @access private + * + * @param array $block Block object. + * @param string $variation Slug for the block style variation. + * + * @return string The unique variation name. + */ +function wp_create_block_style_variation_instance_name( $block, $variation ) { + return $variation . '--' . md5( serialize( $block ) ); +} + +/** + * Determines the block style variation names within a CSS class string. + * + * @since 6.6.0 + * + * @param string $class_string CSS class string to look for a variation in. + * + * @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_all( '/\bis-style-(?!default)(\S+)\b/', $class_string, $matches ); + return $matches[1] ?? null; +} + +/** + * Recursively resolves any `ref` values within a block style variation's data. + * + * @since 6.6.0 + * @access private + * + * @param array $variation_data Reference to the variation data being processed. + * @param array $theme_json Theme.json data to retrieve referenced values from. + */ +function wp_resolve_block_style_variation_ref_values( &$variation_data, $theme_json ) { + foreach ( $variation_data as $key => &$value ) { + // Only need to potentially process arrays. + if ( is_array( $value ) ) { + // If ref value is set, attempt to find its matching value and update it. + if ( array_key_exists( 'ref', $value ) ) { + // Clean up any invalid ref value. + if ( empty( $value['ref'] ) || ! is_string( $value['ref'] ) ) { + unset( $variation_data[ $key ] ); + } + + $value_path = explode( '.', $value['ref'] ?? '' ); + $ref_value = _wp_array_get( $theme_json, $value_path ); + + // Only update the current value if the referenced path matched a value. + if ( null === $ref_value ) { + unset( $variation_data[ $key ] ); + } else { + $value = $ref_value; + } + } else { + // Recursively look for ref instances. + wp_resolve_block_style_variation_ref_values( $value, $theme_json ); + } + } + } +} +/** + * Render the block style variation's styles. + * + * In the case of nested blocks with variations applied, we want the parent + * variation's styles to be rendered before their descendants. This solves the + * issue of a block type being styled in both the parent and descendant: we want + * the descendant style to take priority, and this is done by loading it after, + * in the DOM order. This is why the variation stylesheet generation is in a + * different filter. + * + * @since 6.6.0 + * @access private + * + * @param array $parsed_block The parsed block. + * + * @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; + $variations = wp_get_block_style_variation_name_from_class( $classes ); + + if ( ! $variations ) { + return $parsed_block; + } + + $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; + } + + /* + * Recursively resolve any ref values with the appropriate value within the + * theme_json data. + */ + wp_resolve_block_style_variation_ref_values( $variation_data, $theme_json ); + + $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"; + + /* + * Even though block style variations are effectively theme.json partials, + * they can't be processed completely as though they are. + * + * 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(); + 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::LATEST_SCHEMA, + 'styles' => array( + 'elements' => $elements_data, + 'blocks' => $blocks_data, + ), + ); + + // 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' ); + } + + // 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( $config, 'blocks' ); + $variation_styles = $variation_theme_json->get_stylesheet( + array( 'styles' ), + array( 'custom' ), + array( + 'include_block_style_variations' => true, + '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. + 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-block-library' ) ); + 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_create_block_style_variation_instance_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(); +} + +/** + * 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 ); + +/** + * 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_partials( $variations ) { + if ( empty( $variations ) ) { + return; + } + + $registry = WP_Block_Styles_Registry::get_instance(); + + foreach ( $variations as $variation ) { + if ( empty( $variation['blockTypes'] ) || empty( $variation['styles'] ) ) { + continue; + } + + $variation_name = $variation['slug'] ?? _wp_to_kebab_case( $variation['title'] ); + $variation_label = $variation['title'] ?? $variation_name; + + 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. + if ( ! array_key_exists( $variation_name, $registered_styles ) ) { + register_block_style( + $block_type, + array( + 'name' => $variation_name, + 'label' => $variation_label, + ) + ); + } + } + } +} |