From a22dab79620bda9a9d97ad1bbeec1d6be648d0c3 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Mon, 4 Mar 2024 11:58:57 +1000 Subject: [PATCH 1/3] Elements: Merge element style and classname generation to single filter Making the class name and style generation occur in the same filter allows the same block data to be used to generate the class regardless of if that data is filtered further elsewhere. Note: There's still possibility of class name conflict but greatly reduced. --- lib/block-supports/elements.php | 120 +++++++++++++++++++------------- 1 file changed, 70 insertions(+), 50 deletions(-) diff --git a/lib/block-supports/elements.php b/lib/block-supports/elements.php index 2f73d3d972dca..4cfd065954d41 100644 --- a/lib/block-supports/elements.php +++ b/lib/block-supports/elements.php @@ -6,22 +6,21 @@ */ /** - * Update the block content with elements class names. + * Determines whether an elements class name should be added to the block. * - * @param string $block_content Rendered block content. - * @param array $block Block object. - * @return string Filtered block content. + * @param array $block Block object. + * @param array $options Per element type options e.g. whether to skip serialization. + * + * @return boolean Whether the block needs an elements class name. */ -function gutenberg_render_elements_support( $block_content, $block ) { - if ( ! $block_content || ! isset( $block['attrs']['style']['elements'] ) ) { - return $block_content; +function gutenberg_should_add_elements_class_name( $block, $options ) { + if ( ! isset( $block['attrs']['style']['elements'] ) ) { + return false; } - $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block['blockName'] ); - $element_color_properties = array( 'button' => array( - 'skip' => wp_should_skip_block_supports_serialization( $block_type, 'color', 'button' ), + 'skip' => $options['button']['skip'] ?? false, 'paths' => array( array( 'button', 'color', 'text' ), array( 'button', 'color', 'background' ), @@ -29,14 +28,14 @@ function gutenberg_render_elements_support( $block_content, $block ) { ), ), 'link' => array( - 'skip' => wp_should_skip_block_supports_serialization( $block_type, 'color', 'link' ), + 'skip' => $options['link']['skip'] ?? false, 'paths' => array( array( 'link', 'color', 'text' ), array( 'link', ':hover', 'color', 'text' ), ), ), 'heading' => array( - 'skip' => wp_should_skip_block_supports_serialization( $block_type, 'color', 'heading' ), + 'skip' => $options['heading']['skip'] ?? false, 'paths' => array( array( 'heading', 'color', 'text' ), array( 'heading', 'color', 'background' ), @@ -63,14 +62,6 @@ function gutenberg_render_elements_support( $block_content, $block ) { ), ); - $skip_all_element_color_serialization = $element_color_properties['button']['skip'] && - $element_color_properties['link']['skip'] && - $element_color_properties['heading']['skip']; - - if ( $skip_all_element_color_serialization ) { - return $block_content; - } - $elements_style_attributes = $block['attrs']['style']['elements']; foreach ( $element_color_properties as $element_config ) { @@ -80,47 +71,31 @@ function gutenberg_render_elements_support( $block_content, $block ) { foreach ( $element_config['paths'] as $path ) { if ( null !== _wp_array_get( $elements_style_attributes, $path, null ) ) { - /* - * It only takes a single custom attribute to require that the custom - * class name be added to the block, so once one is found there's no - * need to continue looking for others. - * - * As is done with the layout hook, this code assumes that the block - * contains a single wrapper and that it's the first element in the - * rendered output. That first element, if it exists, gets the class. - */ - $tags = new WP_HTML_Tag_Processor( $block_content ); - if ( $tags->next_tag() ) { - $tags->add_class( wp_get_elements_class_name( $elements_style_attributes ) ); - } - - return $tags->get_updated_html(); + return true; } } } - // If no custom attributes were found then there's nothing to modify. - return $block_content; + return false; } /** - * Render the elements stylesheet. + * Render the elements stylesheet and adds elements class name to block as required. * * In the case of nested blocks we want the parent element styles to be rendered before their descendants. * This solves the issue of an element (e.g.: link color) being styled in both the parent and a descendant: * we want the descendant style to take priority, and this is done by loading it after, in DOM order. * - * @param string|null $pre_render The pre-rendered content. Default null. - * @param array $block The block being rendered. + * @param array $parsed_block The parsed block. * - * @return null + * @return array The same parsed block with elements classname added if appropriate. */ -function gutenberg_render_elements_support_styles( $pre_render, $block ) { - $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block['blockName'] ); - $element_block_styles = isset( $block['attrs']['style']['elements'] ) ? $block['attrs']['style']['elements'] : null; +function gutenberg_render_elements_support_styles( $parsed_block ) { + $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $parsed_block['blockName'] ); + $element_block_styles = $parsed_block['attrs']['style']['elements'] ?? null; if ( ! $element_block_styles ) { - return null; + return $parsed_block; } $skip_link_color_serialization = wp_should_skip_block_supports_serialization( $block_type, 'color', 'link' ); @@ -131,11 +106,25 @@ function gutenberg_render_elements_support_styles( $pre_render, $block ) { $skip_button_color_serialization; if ( $skips_all_element_color_serialization ) { - return null; + return $parsed_block; + } + + $options = array( + 'button' => array( 'skip' => $skip_button_color_serialization ), + 'link' => array( 'skip' => $skip_link_color_serialization ), + 'heading' => array( 'skip' => $skip_heading_color_serialization ), + ); + + if ( ! gutenberg_should_add_elements_class_name( $parsed_block, $options ) ) { + return $parsed_block; } - $class_name = wp_get_elements_class_name( $element_block_styles ); + $class_name = wp_get_elements_class_name( $parsed_block ); + $updated_class_name = isset( $parsed_block['attrs']['className'] ) ? $parsed_block['attrs']['className'] . " $class_name" : $class_name; + + _wp_array_set( $parsed_block, array( 'attrs', 'className' ), $updated_class_name ); + // Generate element styles based on selector and store in style engine for enqueuing. $element_types = array( 'button' => array( 'selector' => ".$class_name .wp-element-button, .$class_name .wp-block-button__link", @@ -200,11 +189,42 @@ function gutenberg_render_elements_support_styles( $pre_render, $block ) { } } - return null; + return $parsed_block; +} + +/** + * Ensure the elements block support class name generated and added to + * block attributes in the `render_block_data` filter gets applied to the + * block's markup. + * + * @see gutenberg_render_elements_support_styles + * + * @param string $block_content Rendered block content. + * @param array $block Block object. + * + * @return string Filtered block content. + */ +function gutenberg_render_elements_class_name( $block_content, $block ) { + $class_string = $block['attrs']['className'] ?? ''; + preg_match( '/\bwp-elements-\S+\b/', $class_string, $matches ); + + if ( empty( $matches ) ) { + return $block_content; + } + + $tags = new WP_HTML_Tag_Processor( $block_content ); + + if ( $tags->next_tag() ) { + // Ensure the elements class name set in render_block_data filter is applied in markup. + // See `gutenberg_render_elements_support_styles`. + $tags->add_class( $matches[0] ); + } + + return $tags->get_updated_html(); } // Remove WordPress core filters to avoid rendering duplicate elements stylesheet & attaching classes twice. remove_filter( 'render_block', 'wp_render_elements_support', 10, 2 ); remove_filter( 'pre_render_block', 'wp_render_elements_support_styles', 10, 2 ); -add_filter( 'render_block', 'gutenberg_render_elements_support', 10, 2 ); -add_filter( 'pre_render_block', 'gutenberg_render_elements_support_styles', 10, 2 ); +add_filter( 'render_block', 'gutenberg_render_elements_class_name', 10, 2 ); +add_filter( 'render_block_data', 'gutenberg_render_elements_support_styles', 10, 2 ); From cc0e88116029db7db004520080a4a73cc2702c26 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Mon, 4 Mar 2024 12:42:17 +1000 Subject: [PATCH 2/3] Update the unit tests --- phpunit/block-supports/elements-test.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/phpunit/block-supports/elements-test.php b/phpunit/block-supports/elements-test.php index a25a5a8b3d785..827b624dea241 100644 --- a/phpunit/block-supports/elements-test.php +++ b/phpunit/block-supports/elements-test.php @@ -72,7 +72,12 @@ public function test_elements_block_support_class( $color_settings, $elements_st ), ); - $actual = gutenberg_render_elements_support( $block_markup, $block ); + // To ensure a consistent elements class name it is generated within a + // `render_block_data` filter and stored in the `className` attribute. + // As a result the block data needs to be passed through the same + // function for this test. + $filtered_block = gutenberg_render_elements_support_styles( $block ); + $actual = gutenberg_render_elements_class_name( $block_markup, $filtered_block ); $this->assertMatchesRegularExpression( $expected_markup, @@ -192,7 +197,7 @@ public function test_elements_block_support_styles( $color_settings, $elements_s ), ); - gutenberg_render_elements_support_styles( null, $block ); + gutenberg_render_elements_support_styles( $block ); $actual_stylesheet = gutenberg_style_engine_get_stylesheet_from_context( 'block-supports' ); $this->assertMatchesRegularExpression( From 7719742d3994f5fe841df861a7b02712627a2a81 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Mon, 4 Mar 2024 13:03:10 +1000 Subject: [PATCH 3/3] Remove core render_block filter for adding class to markup --- lib/block-supports/elements.php | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/block-supports/elements.php b/lib/block-supports/elements.php index 4cfd065954d41..eefab9ceffbaf 100644 --- a/lib/block-supports/elements.php +++ b/lib/block-supports/elements.php @@ -226,5 +226,6 @@ function gutenberg_render_elements_class_name( $block_content, $block ) { // Remove WordPress core filters to avoid rendering duplicate elements stylesheet & attaching classes twice. remove_filter( 'render_block', 'wp_render_elements_support', 10, 2 ); remove_filter( 'pre_render_block', 'wp_render_elements_support_styles', 10, 2 ); +remove_filter( 'render_block', 'wp_render_elements_class_name', 10, 2 ); add_filter( 'render_block', 'gutenberg_render_elements_class_name', 10, 2 ); add_filter( 'render_block_data', 'gutenberg_render_elements_support_styles', 10, 2 );