diff --git a/src/wp-includes/class-wp-block-supports.php b/src/wp-includes/class-wp-block-supports.php
index ec5bc9c8d6846..c5df0b9fbd241 100644
--- a/src/wp-includes/class-wp-block-supports.php
+++ b/src/wp-includes/class-wp-block-supports.php
@@ -179,30 +179,50 @@ function get_block_wrapper_attributes( $extra_attributes = array() ) {
return '';
}
- // This is hardcoded on purpose.
- // We only support a fixed list of attributes.
- $attributes_to_merge = array( 'style', 'class', 'id', 'aria-label' );
- $attributes = array();
- foreach ( $attributes_to_merge as $attribute_name ) {
- if ( empty( $new_attributes[ $attribute_name ] ) && empty( $extra_attributes[ $attribute_name ] ) ) {
- continue;
- }
-
- if ( empty( $new_attributes[ $attribute_name ] ) ) {
- $attributes[ $attribute_name ] = $extra_attributes[ $attribute_name ];
- continue;
- }
-
- if ( empty( $extra_attributes[ $attribute_name ] ) ) {
- $attributes[ $attribute_name ] = $new_attributes[ $attribute_name ];
+ // Attribute values are concatenated or overridden depending on the attribute type.
+ // This is hardcoded on purpose, as we only support a fixed list of attributes.
+ $attribute_merge_callbacks = array(
+ 'style' => static function ( $new_attribute, $extra_attribute ) {
+ $styles = array_filter(
+ array(
+ rtrim( trim( $extra_attribute ), ';' ),
+ rtrim( trim( $new_attribute ), ';' ),
+ )
+ );
+ return safecss_filter_attr( implode( ';', array_filter( $styles ) ) );
+ },
+ 'class' => static function ( $new_attribute, $extra_attribute ) {
+ $classes = array_merge(
+ preg_split( '/\s+/', $extra_attribute, -1, PREG_SPLIT_NO_EMPTY ),
+ preg_split( '/\s+/', $new_attribute, -1, PREG_SPLIT_NO_EMPTY )
+ );
+ $classes = array_unique( array_filter( $classes ) );
+ return implode( ' ', $classes );
+ },
+ 'id' => static function ( $new_attribute, $extra_attribute ) {
+ return '' !== $extra_attribute ? $extra_attribute : $new_attribute;
+ },
+ 'aria-label' => static function ( $new_attribute, $extra_attribute ) {
+ return '' !== $extra_attribute ? $extra_attribute : $new_attribute;
+ },
+ );
+
+ $attributes = array();
+ foreach ( $attribute_merge_callbacks as $attribute_name => $merge_callback ) {
+ $new_attribute = $new_attributes[ $attribute_name ] ?? '';
+ $extra_attribute = $extra_attributes[ $attribute_name ] ?? '';
+ $new_attribute = is_string( $new_attribute ) ? $new_attribute : '';
+ $extra_attribute = is_string( $extra_attribute ) ? $extra_attribute : '';
+
+ if ( '' === $new_attribute && '' === $extra_attribute ) {
continue;
}
- $attributes[ $attribute_name ] = $extra_attributes[ $attribute_name ] . ' ' . $new_attributes[ $attribute_name ];
+ $attributes[ $attribute_name ] = $merge_callback( $new_attribute, $extra_attribute );
}
foreach ( $extra_attributes as $attribute_name => $value ) {
- if ( ! in_array( $attribute_name, $attributes_to_merge, true ) ) {
+ if ( ! isset( $attribute_merge_callbacks[ $attribute_name ] ) ) {
$attributes[ $attribute_name ] = $value;
}
}
diff --git a/tests/phpunit/tests/blocks/supportedStyles.php b/tests/phpunit/tests/blocks/supportedStyles.php
index c733b7218d8c5..771c8f6089d15 100644
--- a/tests/phpunit/tests/blocks/supportedStyles.php
+++ b/tests/phpunit/tests/blocks/supportedStyles.php
@@ -99,28 +99,12 @@ private function render_example_block( $block ) {
$wrapper_attributes = get_block_wrapper_attributes(
array(
'class' => 'foo-bar-class',
- 'style' => 'test: style;',
+ 'style' => 'margin-top: 2px;',
)
);
return '
' . self::BLOCK_CONTENT . '
';
}
- /**
- * Runs assertions that the rendered output has expected class/style attrs.
- *
- * @param array $block Block to render.
- * @param string $expected_classes Expected output class attr string.
- * @param string $expected_styles Expected output styles attr string.
- */
- private function assert_styles_and_classes_match( $block, $expected_classes, $expected_styles ) {
- $styled_block = $this->render_example_block( $block );
- $class_list = $this->get_attribute_from_block( 'class', $styled_block );
- $style_list = $this->get_attribute_from_block( 'style', $styled_block );
-
- $this->assertSame( $expected_classes, $class_list, 'Class list does not match expected classes' );
- $this->assertSame( $expected_styles, $style_list, 'Style list does not match expected styles' );
- }
-
/**
* Runs assertions that the rendered output has expected content and class/style attrs.
*
@@ -196,7 +180,7 @@ public function test_named_color_support() {
);
$expected_classes = 'foo-bar-class wp-block-example has-text-color has-red-color has-background has-black-background-color';
- $expected_styles = 'test: style;';
+ $expected_styles = 'margin-top: 2px';
$this->assert_content_and_styles_and_classes_match( $block, $expected_classes, $expected_styles );
}
@@ -232,7 +216,7 @@ public function test_custom_color_support() {
'innerHTML' => array(),
);
- $expected_styles = 'test: style;color:#000;background-color:#fff;';
+ $expected_styles = 'margin-top: 2px;color:#000;background-color:#fff';
$expected_classes = 'foo-bar-class wp-block-example has-text-color has-background';
$this->assert_content_and_styles_and_classes_match( $block, $expected_classes, $expected_styles );
@@ -264,7 +248,7 @@ public function test_named_gradient_support() {
);
$expected_classes = 'foo-bar-class wp-block-example has-background has-red-gradient-background';
- $expected_styles = 'test: style;';
+ $expected_styles = 'margin-top: 2px';
$this->assert_content_and_styles_and_classes_match( $block, $expected_classes, $expected_styles );
}
@@ -295,7 +279,7 @@ public function test_custom_gradient_support() {
);
$expected_classes = 'foo-bar-class wp-block-example has-background';
- $expected_styles = 'test: style; background:some-gradient-style;';
+ $expected_styles = 'margin-top: 2px;background:some-gradient-style';
$this->assert_content_and_styles_and_classes_match( $block, $expected_classes, $expected_styles );
}
@@ -331,7 +315,7 @@ public function test_color_unsupported() {
);
$expected_classes = 'foo-bar-class wp-block-example';
- $expected_styles = 'test: style;';
+ $expected_styles = 'margin-top: 2px';
$this->assert_content_and_styles_and_classes_match( $block, $expected_classes, $expected_styles );
}
@@ -361,7 +345,7 @@ public function test_named_font_size() {
);
$expected_classes = 'foo-bar-class wp-block-example has-large-font-size';
- $expected_styles = 'test: style;';
+ $expected_styles = 'margin-top: 2px';
$this->assert_content_and_styles_and_classes_match( $block, $expected_classes, $expected_styles );
}
@@ -391,7 +375,7 @@ public function test_custom_font_size() {
);
$expected_classes = 'foo-bar-class wp-block-example';
- $expected_styles = 'test: style; font-size:10px;';
+ $expected_styles = 'margin-top: 2px;font-size:10px';
$this->assert_content_and_styles_and_classes_match( $block, $expected_classes, $expected_styles );
}
@@ -418,7 +402,7 @@ public function test_font_size_unsupported() {
);
$expected_classes = 'foo-bar-class wp-block-example';
- $expected_styles = 'test: style;';
+ $expected_styles = 'margin-top: 2px';
$this->assert_content_and_styles_and_classes_match( $block, $expected_classes, $expected_styles );
}
@@ -448,7 +432,7 @@ public function test_line_height() {
);
$expected_classes = 'foo-bar-class wp-block-example';
- $expected_styles = 'test: style; line-height:10;';
+ $expected_styles = 'margin-top: 2px;line-height:10';
$this->assert_content_and_styles_and_classes_match( $block, $expected_classes, $expected_styles );
}
@@ -474,7 +458,7 @@ public function test_line_height_unsupported() {
);
$expected_classes = 'foo-bar-class wp-block-example';
- $expected_styles = 'test: style;';
+ $expected_styles = 'margin-top: 2px';
$this->assert_content_and_styles_and_classes_match( $block, $expected_classes, $expected_styles );
}
@@ -502,7 +486,7 @@ public function test_block_alignment() {
);
$expected_classes = 'foo-bar-class wp-block-example alignwide';
- $expected_styles = 'test: style;';
+ $expected_styles = 'margin-top: 2px';
$this->assert_content_and_styles_and_classes_match( $block, $expected_classes, $expected_styles );
}
@@ -528,7 +512,7 @@ public function test_block_alignment_unsupported() {
);
$expected_classes = 'foo-bar-class wp-block-example';
- $expected_styles = 'test: style;';
+ $expected_styles = 'margin-top: 2px';
$this->assert_content_and_styles_and_classes_match( $block, $expected_classes, $expected_styles );
}
@@ -575,7 +559,7 @@ public function test_all_supported() {
);
$expected_classes = 'foo-bar-class wp-block-example has-text-color has-background alignwide';
- $expected_styles = 'test: style; color:#000; background-color:#fff; font-size:10px; line-height:20;';
+ $expected_styles = 'margin-top: 2px;color:#000;background-color:#fff;font-size:10px;line-height:20';
$this->assert_content_and_styles_and_classes_match( $block, $expected_classes, $expected_styles );
}
@@ -618,7 +602,7 @@ public function test_one_supported() {
);
$expected_classes = 'foo-bar-class wp-block-example';
- $expected_styles = 'test: style; font-size:10px;';
+ $expected_styles = 'margin-top: 2px;font-size:10px';
$this->assert_content_and_styles_and_classes_match( $block, $expected_classes, $expected_styles );
}
@@ -643,7 +627,7 @@ public function test_custom_classnames_support() {
'innerHTML' => array(),
);
- $expected_styles = 'test: style;';
+ $expected_styles = 'margin-top: 2px';
$expected_classes = 'foo-bar-class wp-block-example my-custom-classname';
$this->assert_content_and_styles_and_classes_match( $block, $expected_classes, $expected_styles );
@@ -671,7 +655,7 @@ public function test_custom_classnames_support_opt_out() {
'innerHTML' => array(),
);
- $expected_styles = 'test: style;';
+ $expected_styles = 'margin-top: 2px';
$expected_classes = 'foo-bar-class wp-block-example';
$this->assert_content_and_styles_and_classes_match( $block, $expected_classes, $expected_styles );
@@ -697,7 +681,7 @@ public function test_generated_classnames_support_opt_out() {
'innerHTML' => array(),
);
- $expected_styles = 'test: style;';
+ $expected_styles = 'margin-top: 2px';
$expected_classes = 'foo-bar-class';
$this->assert_content_and_styles_and_classes_match( $block, $expected_classes, $expected_styles );
@@ -764,4 +748,161 @@ static function ( $errno = 0, $errstr = '' ) use ( &$errors ) {
$this->assertEmpty( $errors, 'Libxml errors should be dropped.' );
}
+
+ /**
+ * Ensures that style, class, id, and aria-label attributes are correctly merged or overridden
+ * in get_block_wrapper_attributes().
+ *
+ * @ticket 64603
+ * @covers ::get_block_wrapper_attributes
+ *
+ * @dataProvider data_get_block_wrapper_attributes_merge_or_override
+ *
+ * @param array $data {
+ * Data from the provider.
+ *
+ * @type array $block_type_settings Block type settings.
+ * @type array $block_attrs Block attributes.
+ * @type array $extra_attributes Extra attributes passed to get_block_wrapper_attributes().
+ * @type string $expected_attribute Expected attribute string (e.g. 'id="user-id"').
+ * }
+ */
+ public function test_get_block_wrapper_attributes_merge_and_override( $data ) {
+ $block_name = 'core/example';
+ $block_type_settings = array_merge(
+ array(
+ 'attributes' => array(),
+ 'render_callback' => true,
+ ),
+ $data['block_type_settings']
+ );
+ $this->register_block_type( $block_name, $block_type_settings );
+
+ $block = array(
+ 'blockName' => $block_name,
+ 'attrs' => $data['block_attrs'],
+ 'innerBlock' => array(),
+ 'innerContent' => array(),
+ 'innerHTML' => array(),
+ );
+
+ WP_Block_Supports::init();
+ WP_Block_Supports::$block_to_render = $block;
+
+ $wrapper_attributes = get_block_wrapper_attributes( $data['extra_attributes'] );
+
+ $this->assertStringContainsString( $data['expected_attribute'], $wrapper_attributes );
+ }
+
+ /**
+ * Data provider for test_get_block_wrapper_attributes_merge_and_override.
+ *
+ * @return array[] Array of test cases.
+ */
+ public function data_get_block_wrapper_attributes_merge_or_override() {
+ return array(
+ 'extra style attributes are merged with block values' => array(
+ array(
+ 'block_type_settings' => array(
+ 'supports' => array(
+ 'color' => true,
+ ),
+ ),
+ 'block_attrs' => array(
+ 'style' => array(
+ 'color' => array(
+ 'text' => '#000',
+ ),
+ ),
+ ),
+ 'extra_attributes' => array(
+ // Redundant trailing semicolons should be stripped
+ 'style' => 'margin-top: 2px;;;',
+ ),
+ 'expected_attribute' => 'style="margin-top: 2px;color:#000"',
+ ),
+ ),
+ 'extra class attributes are merged with block values' => array(
+ array(
+ 'block_type_settings' => array(
+ 'supports' => array(
+ 'color' => true,
+ ),
+ ),
+ 'block_attrs' => array(
+ 'style' => array(
+ 'color' => array(
+ 'text' => '#000',
+ ),
+ ),
+ ),
+ 'extra_attributes' => array(
+ // Duplicate class names should be merged, and commas should be preserved.
+ 'class' => 'extra-class extra,class has-text-color',
+ ),
+ 'expected_attribute' => 'class="extra-class extra,class has-text-color wp-block-example"',
+ ),
+ ),
+ 'extra attributes override block-generated id' => array(
+ array(
+ 'block_type_settings' => array(
+ 'supports' => array(
+ 'anchor' => true,
+ ),
+ ),
+ 'block_attrs' => array(
+ 'anchor' => 'block-id',
+ ),
+ 'extra_attributes' => array(
+ 'id' => 'user-id',
+ ),
+ 'expected_attribute' => 'id="user-id"',
+ ),
+ ),
+ 'block-generated id is used when no extra provided' => array(
+ array(
+ 'block_type_settings' => array(
+ 'supports' => array(
+ 'anchor' => true,
+ ),
+ ),
+ 'block_attrs' => array(
+ 'anchor' => 'block-id',
+ ),
+ 'extra_attributes' => array(),
+ 'expected_attribute' => 'id="block-id"',
+ ),
+ ),
+ 'extra attributes override block-generated aria-label' => array(
+ array(
+ 'block_type_settings' => array(
+ 'supports' => array(
+ 'ariaLabel' => true,
+ ),
+ ),
+ 'block_attrs' => array(
+ 'ariaLabel' => 'Block aria-label',
+ ),
+ 'extra_attributes' => array(
+ 'aria-label' => 'User aria-label',
+ ),
+ 'expected_attribute' => 'aria-label="User aria-label"',
+ ),
+ ),
+ 'block-generated aria-label is used when no extra provided' => array(
+ array(
+ 'block_type_settings' => array(
+ 'supports' => array(
+ 'ariaLabel' => true,
+ ),
+ ),
+ 'block_attrs' => array(
+ 'ariaLabel' => 'Block aria-label',
+ ),
+ 'extra_attributes' => array(),
+ 'expected_attribute' => 'aria-label="Block aria-label"',
+ ),
+ ),
+ );
+ }
}