diff --git a/src/wp-includes/block-supports/elements.php b/src/wp-includes/block-supports/elements.php index 54b96aa1dc064..d765a2c2b4b5a 100644 --- a/src/wp-includes/block-supports/elements.php +++ b/src/wp-includes/block-supports/elements.php @@ -12,11 +12,10 @@ * @since 6.0.0 * @access private * - * @param array $block Block object. * @return string The unique class name. */ -function wp_get_elements_class_name( $block ) { - return 'wp-elements-' . md5( serialize( $block ) ); +function wp_get_elements_class_name(): string { + return wp_unique_prefixed_id( 'wp-elements-' ); } /** @@ -109,6 +108,29 @@ function wp_should_add_elements_class_name( $block, $options ) { * * @param array $parsed_block The parsed block. * @return array The same parsed block with elements classname added if appropriate. + * + * @phpstan-param array{ + * blockName: string, + * attrs: array{ + * className?: string, + * style?: array{ + * elements?: array, + * ... + * }>, + * }, + * ... + * }, + * ... + * } $parsed_block + * @phpstan-return array{ + * blockName: string, + * attrs: array{ + * className?: string, + * ... + * }, + * ... + * } */ function wp_render_elements_support_styles( $parsed_block ) { /* @@ -129,9 +151,12 @@ function wp_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; + $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $parsed_block['blockName'] ); + if ( ! $block_type ) { + return $parsed_block; + } + $element_block_styles = $parsed_block['attrs']['style']['elements'] ?? null; if ( ! $element_block_styles ) { return $parsed_block; } @@ -157,7 +182,7 @@ function wp_render_elements_support_styles( $parsed_block ) { return $parsed_block; } - $class_name = wp_get_elements_class_name( $parsed_block ); + $class_name = wp_get_elements_class_name(); $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 ); @@ -197,7 +222,7 @@ function wp_render_elements_support_styles( $parsed_block ) { ) ); - if ( isset( $element_style_object[':hover'] ) ) { + if ( isset( $element_style_object[':hover'], $element_config['hover_selector'] ) ) { wp_style_engine_get_styles( $element_style_object[':hover'], array( diff --git a/tests/phpunit/tests/block-supports/wpRenderElementsSupport.php b/tests/phpunit/tests/block-supports/wpRenderElementsSupport.php index 007ba8312e495..ad60844813188 100644 --- a/tests/phpunit/tests/block-supports/wpRenderElementsSupport.php +++ b/tests/phpunit/tests/block-supports/wpRenderElementsSupport.php @@ -159,6 +159,70 @@ public function test_elements_block_support_class_with_invalid_elements_prefix() ); } + /** + * Tests that duplicate blocks get distinct elements class names + * on their rendered markup to avoid CSS cascade conflicts. + * + * @ticket 65435 + * + * @covers ::wp_get_elements_class_name + */ + public function test_elements_block_support_class_with_duplicate_blocks(): void { + $this->test_block_name = 'test/element-block-supports'; + + register_block_type( + $this->test_block_name, + array( + 'api_version' => 3, + 'attributes' => array( + 'style' => array( + 'type' => 'object', + ), + ), + 'supports' => array( + 'color' => array( + 'link' => true, + ), + ), + ) + ); + + $block = array( + 'blockName' => $this->test_block_name, + 'attrs' => array( + 'style' => array( + 'elements' => array( + 'link' => array( + 'color' => array( + 'text' => 'var:preset|color|vivid-red', + ), + ), + ), + ), + ), + ); + + $block_markup = '

Hello WordPress!

'; + $elements_class_names = array(); + $count = 2; + for ( $i = 0; $i < $count; $i++ ) { + $rendered_block = wp_render_elements_class_name( $block_markup, wp_render_elements_support_styles( $block ) ); + + $processor = new WP_HTML_Tag_Processor( $rendered_block ); + $this->assertTrue( $processor->next_tag( 'P' ), "Expected paragraph in block #$i." ); + $elements_class_name = array_first( + array_filter( + iterator_to_array( $processor->class_list() ), + fn( string $class_name ) => (bool) preg_match( '/^wp-elements-\d+$/', $class_name ) + ) + ); + $this->assertIsString( $elements_class_name, "Expected wp-elements class in block #$i." ); + $elements_class_names[] = $elements_class_name; + } + + $this->assertSame( $count, count( array_unique( $elements_class_names ) ), 'Expected each rendered block to have a unique wp-elements class name.' ); + } + /** * Data provider. * @@ -238,7 +302,7 @@ public function data_elements_block_support_class() { 'button' => array( 'color' => $color_styles ), ), 'block_markup' => '

Hello WordPress!

', - 'expected_markup' => '/^

Hello WordPress<\/a>!<\/p>$/', + 'expected_markup' => '/^

Hello WordPress<\/a>!<\/p>$/', ), 'link element styles apply class to wrapper' => array( 'color_settings' => array( 'link' => true ), @@ -246,7 +310,7 @@ public function data_elements_block_support_class() { 'link' => array( 'color' => $color_styles ), ), 'block_markup' => '

Hello WordPress!

', - 'expected_markup' => '/^

Hello WordPress<\/a>!<\/p>$/', + 'expected_markup' => '/^

Hello WordPress<\/a>!<\/p>$/', ), 'heading element styles apply class to wrapper' => array( 'color_settings' => array( 'heading' => true ), @@ -254,7 +318,7 @@ public function data_elements_block_support_class() { 'heading' => array( 'color' => $color_styles ), ), 'block_markup' => '

Hello WordPress!

', - 'expected_markup' => '/^

Hello WordPress<\/a>!<\/p>$/', + 'expected_markup' => '/^

Hello WordPress<\/a>!<\/p>$/', ), 'element styles apply class to wrapper when it has other classes' => array( 'color_settings' => array( 'link' => true ), @@ -262,7 +326,7 @@ public function data_elements_block_support_class() { 'link' => array( 'color' => $color_styles ), ), 'block_markup' => '

Hello WordPress!

', - 'expected_markup' => '/^

Hello WordPress<\/a>!<\/p>$/', + 'expected_markup' => '/^

Hello WordPress<\/a>!<\/p>$/', ), 'element styles apply class to wrapper when it has other attributes' => array( 'color_settings' => array( 'link' => true ), @@ -270,7 +334,7 @@ public function data_elements_block_support_class() { 'link' => array( 'color' => $color_styles ), ), 'block_markup' => '

Hello WordPress!

', - 'expected_markup' => '/^

Hello WordPress<\/a>!<\/p>$/', + 'expected_markup' => '/^

Hello WordPress<\/a>!<\/p>$/', ), ); } diff --git a/tests/phpunit/tests/block-supports/wpRenderElementsSupportStyles.php b/tests/phpunit/tests/block-supports/wpRenderElementsSupportStyles.php index 16ed26fc9c7bc..5c9fc8af5819d 100644 --- a/tests/phpunit/tests/block-supports/wpRenderElementsSupportStyles.php +++ b/tests/phpunit/tests/block-supports/wpRenderElementsSupportStyles.php @@ -68,6 +68,62 @@ public function test_elements_block_support_styles( $color_settings, $elements_s ); } + /** + * Tests that identical blocks with different elements styles + * generate distinct class names to avoid CSS cascade conflicts. + * + * @ticket 65435 + * + * @covers ::wp_get_elements_class_name + */ + public function test_elements_block_support_styles_with_duplicate_blocks(): void { + $this->test_block_name = 'test/element-block-supports'; + + register_block_type( + $this->test_block_name, + array( + 'api_version' => 3, + 'attributes' => array( + 'style' => array( + 'type' => 'object', + ), + ), + 'supports' => array( + 'color' => array( + 'link' => true, + ), + ), + ) + ); + + $block = array( + 'blockName' => $this->test_block_name, + 'attrs' => array( + 'style' => array( + 'elements' => array( + 'link' => array( + 'color' => array( + 'text' => 'blue', + ), + ), + ), + ), + ), + ); + + // Process two identical blocks with the same elements styles. + $count = 2; + for ( $i = 0; $i < $count; $i++ ) { + wp_render_elements_support_styles( $block ); + } + $actual_stylesheet = wp_style_engine_get_stylesheet_from_context( 'block-supports', array( 'prettify' => false ) ); + + // Count the number of distinct class names to confirm uniqueness. + $this->assertSame( $count, preg_match_all( '/\.wp-elements-(\d+)/', $actual_stylesheet, $matches ) ); + $unique_classes = array_unique( $matches[1] ); + $this->assertCount( $count, $unique_classes, 'Both blocks should produce distinct class names' ); + } + /** * Data provider. * @@ -127,7 +183,7 @@ public function data_elements_block_support_styles() { 'elements_styles' => array( 'button' => array( 'color' => $color_styles ), ), - 'expected_styles' => '/^.wp-elements-[a-f0-9]{32} .wp-element-button, .wp-elements-[a-f0-9]{32} .wp-block-button__link' . $color_css_rules . '$/', + 'expected_styles' => '/^.wp-elements-\d+ .wp-element-button, .wp-elements-\d+ .wp-block-button__link' . $color_css_rules . '$/', ), 'link element styles are applied' => array( 'color_settings' => array( 'link' => true ), @@ -139,15 +195,15 @@ public function data_elements_block_support_styles() { ), ), ), - 'expected_styles' => '/^.wp-elements-[a-f0-9]{32} a:where\(:not\(.wp-element-button\)\)' . $color_css_rules . - '.wp-elements-[a-f0-9]{32} a:where\(:not\(.wp-element-button\)\):hover' . $color_css_rules . '$/', + 'expected_styles' => '/^.wp-elements-\d+ a:where\(:not\(.wp-element-button\)\)' . $color_css_rules . + '.wp-elements-\d+ a:where\(:not\(.wp-element-button\)\):hover' . $color_css_rules . '$/', ), 'generic heading element styles are applied' => array( 'color_settings' => array( 'heading' => true ), 'elements_styles' => array( 'heading' => array( 'color' => $color_styles ), ), - 'expected_styles' => '/^.wp-elements-[a-f0-9]{32} h1, .wp-elements-[a-f0-9]{32} h2, .wp-elements-[a-f0-9]{32} h3, .wp-elements-[a-f0-9]{32} h4, .wp-elements-[a-f0-9]{32} h5, .wp-elements-[a-f0-9]{32} h6' . $color_css_rules . '$/', + 'expected_styles' => '/^.wp-elements-\d+ h1, .wp-elements-\d+ h2, .wp-elements-\d+ h3, .wp-elements-\d+ h4, .wp-elements-\d+ h5, .wp-elements-\d+ h6' . $color_css_rules . '$/', ), 'individual heading element styles are applied' => array( 'color_settings' => array( 'heading' => true ), @@ -159,12 +215,12 @@ public function data_elements_block_support_styles() { 'h5' => array( 'color' => $color_styles ), 'h6' => array( 'color' => $color_styles ), ), - 'expected_styles' => '/^.wp-elements-[a-f0-9]{32} h1' . $color_css_rules . - '.wp-elements-[a-f0-9]{32} h2' . $color_css_rules . - '.wp-elements-[a-f0-9]{32} h3' . $color_css_rules . - '.wp-elements-[a-f0-9]{32} h4' . $color_css_rules . - '.wp-elements-[a-f0-9]{32} h5' . $color_css_rules . - '.wp-elements-[a-f0-9]{32} h6' . $color_css_rules . '$/', + 'expected_styles' => '/^.wp-elements-\d+ h1' . $color_css_rules . + '.wp-elements-\d+ h2' . $color_css_rules . + '.wp-elements-\d+ h3' . $color_css_rules . + '.wp-elements-\d+ h4' . $color_css_rules . + '.wp-elements-\d+ h5' . $color_css_rules . + '.wp-elements-\d+ h6' . $color_css_rules . '$/', ), ); }