From 8386a036cbc2c8e6b922f22e5f1d51ff791acc99 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Sun, 31 May 2026 18:57:23 -0700 Subject: [PATCH 1/7] Elements: Guard against non-string className in render filter --- src/wp-includes/block-supports/elements.php | 4 +++ .../wpRenderElementsSupport.php | 30 +++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/src/wp-includes/block-supports/elements.php b/src/wp-includes/block-supports/elements.php index 374da09e8bc8c..1ea0f691d18d4 100644 --- a/src/wp-includes/block-supports/elements.php +++ b/src/wp-includes/block-supports/elements.php @@ -243,6 +243,10 @@ function wp_render_elements_support_styles( $parsed_block ) { */ function wp_render_elements_class_name( $block_content, $block ) { $class_string = $block['attrs']['className'] ?? ''; + + if ( ! is_string( $class_string ) ) { + return $block_content; + } preg_match( '/\bwp-elements-\S+\b/', $class_string, $matches ); if ( empty( $matches ) ) { diff --git a/tests/phpunit/tests/block-supports/wpRenderElementsSupport.php b/tests/phpunit/tests/block-supports/wpRenderElementsSupport.php index 9103fcba90b66..f77e608c4041a 100644 --- a/tests/phpunit/tests/block-supports/wpRenderElementsSupport.php +++ b/tests/phpunit/tests/block-supports/wpRenderElementsSupport.php @@ -105,6 +105,36 @@ public function test_elements_block_support_class( $color_settings, $elements_st ); } + /** + * Tests that a non-string `className` attribute does not cause a fatal + * error and the block content is returned unmodified. + * + * @ticket 65379 + * + * Block attributes such as `className` are always expected to be strings, + * however invalid stored data can result in other types being present. The + * render filter should fail gracefully rather than passing an array to + * `preg_match()`. + * + * @covers ::wp_render_elements_class_name + */ + public function test_elements_block_support_class_with_non_string_class_name() { + $block = array( + 'blockName' => 'core/paragraph', + 'attrs' => array( + 'className' => array( '0', '1' ), + ), + ); + + $block_content = "

Test

\n"; + + $this->assertSame( + $block_content, + wp_render_elements_class_name( $block_content, $block ), + 'Block content should be returned unchanged when className is not a string' + ); + } + /** * Data provider. * From 270928be319ff44f5c0503301378bde0fd04f794 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Mon, 8 Jun 2026 16:42:44 -0700 Subject: [PATCH 2/7] Re-order docblock Co-authored-by: Mukesh Panchal --- .../phpunit/tests/block-supports/wpRenderElementsSupport.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/phpunit/tests/block-supports/wpRenderElementsSupport.php b/tests/phpunit/tests/block-supports/wpRenderElementsSupport.php index f77e608c4041a..e156bfde37b23 100644 --- a/tests/phpunit/tests/block-supports/wpRenderElementsSupport.php +++ b/tests/phpunit/tests/block-supports/wpRenderElementsSupport.php @@ -109,13 +109,13 @@ public function test_elements_block_support_class( $color_settings, $elements_st * Tests that a non-string `className` attribute does not cause a fatal * error and the block content is returned unmodified. * - * @ticket 65379 - * * Block attributes such as `className` are always expected to be strings, * however invalid stored data can result in other types being present. The * render filter should fail gracefully rather than passing an array to * `preg_match()`. * + * @ticket 65379 + * * @covers ::wp_render_elements_class_name */ public function test_elements_block_support_class_with_non_string_class_name() { From be0e3359335d5628c99d5338b3f60a4da85092a2 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Mon, 8 Jun 2026 16:45:26 -0700 Subject: [PATCH 3/7] Add type hint --- tests/phpunit/tests/block-supports/wpRenderElementsSupport.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/phpunit/tests/block-supports/wpRenderElementsSupport.php b/tests/phpunit/tests/block-supports/wpRenderElementsSupport.php index e156bfde37b23..b5f2366ef4349 100644 --- a/tests/phpunit/tests/block-supports/wpRenderElementsSupport.php +++ b/tests/phpunit/tests/block-supports/wpRenderElementsSupport.php @@ -118,7 +118,7 @@ public function test_elements_block_support_class( $color_settings, $elements_st * * @covers ::wp_render_elements_class_name */ - public function test_elements_block_support_class_with_non_string_class_name() { + public function test_elements_block_support_class_with_non_string_class_name(): void { $block = array( 'blockName' => 'core/paragraph', 'attrs' => array( From 7e6542d316682dde0d0ebbeade60ae276efed669 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Mon, 8 Jun 2026 16:51:47 -0700 Subject: [PATCH 4/7] Add array shape docs for $block param --- src/wp-includes/block-supports/elements.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/wp-includes/block-supports/elements.php b/src/wp-includes/block-supports/elements.php index 1ea0f691d18d4..7c79b97df663a 100644 --- a/src/wp-includes/block-supports/elements.php +++ b/src/wp-includes/block-supports/elements.php @@ -240,6 +240,8 @@ function wp_render_elements_support_styles( $parsed_block ) { * @param string $block_content Rendered block content. * @param array $block Block object. * @return string Filtered block content. + * + * @phpstan-param array{ attrs: array{ className: string, ... }, ... } $block */ function wp_render_elements_class_name( $block_content, $block ) { $class_string = $block['attrs']['className'] ?? ''; From d5aee5ca1c3d9f0e2148b9066343d078d8232570 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Mon, 8 Jun 2026 17:06:51 -0700 Subject: [PATCH 5/7] Align the wp_render_elements_class_name() and wp_render_custom_css_class_name() implementations --- src/wp-includes/block-supports/custom-css.php | 21 +++++------ src/wp-includes/block-supports/elements.php | 37 +++++++++++++------ .../wpRenderElementsSupport.php | 24 ++++++++++++ 3 files changed, 60 insertions(+), 22 deletions(-) diff --git a/src/wp-includes/block-supports/custom-css.php b/src/wp-includes/block-supports/custom-css.php index d4331ae3706ae..c947609a35698 100644 --- a/src/wp-includes/block-supports/custom-css.php +++ b/src/wp-includes/block-supports/custom-css.php @@ -98,32 +98,31 @@ function wp_enqueue_block_custom_css() { * } $block */ function wp_render_custom_css_class_name( $block_content, $block ) { - $class_name_attr = $block['attrs']['className'] ?? null; - - if ( ! is_string( $class_name_attr ) || ! str_contains( $class_name_attr, 'wp-custom-css-' ) ) { + $class_name_attr = $block['attrs']['className'] ?? null; + $class_name_prefix = 'wp-custom-css-'; + if ( ! is_string( $class_name_attr ) || ! str_contains( $class_name_attr, $class_name_prefix ) ) { return $block_content; } // Parse out the 'wp-custom-css-*' class name added by wp_render_custom_css_support_styles(). - $custom_class_name = null; - $token_delimiter = " \t\f\r\n"; - $class_token = strtok( $class_name_attr, $token_delimiter ); + $matched_class_name = null; + $token_delimiter = " \t\f\r\n"; + $class_token = strtok( $class_name_attr, $token_delimiter ); while ( false !== $class_token ) { - if ( str_starts_with( $class_token, 'wp-custom-css-' ) ) { - $custom_class_name = $class_token; + if ( str_starts_with( $class_token, $class_name_prefix ) ) { + $matched_class_name = $class_token; break; } $class_token = strtok( $token_delimiter ); } - if ( null === $custom_class_name ) { + if ( null === $matched_class_name ) { return $block_content; } $tags = new WP_HTML_Tag_Processor( $block_content ); - if ( $tags->next_tag() ) { $tags->add_class( 'has-custom-css' ); - $tags->add_class( $custom_class_name ); + $tags->add_class( $matched_class_name ); } return $tags->get_updated_html(); diff --git a/src/wp-includes/block-supports/elements.php b/src/wp-includes/block-supports/elements.php index 7c79b97df663a..54b96aa1dc064 100644 --- a/src/wp-includes/block-supports/elements.php +++ b/src/wp-includes/block-supports/elements.php @@ -237,28 +237,43 @@ function wp_render_elements_support_styles( $parsed_block ) { * @see wp_render_elements_support_styles * @since 6.6.0 * - * @param string $block_content Rendered block content. - * @param array $block Block object. - * @return string Filtered block content. + * @param string $block_content Rendered block content. + * @param array $block Block object. + * @return string Filtered block content. * - * @phpstan-param array{ attrs: array{ className: string, ... }, ... } $block + * @phpstan-param array{ + * attrs: array{ + * className?: string, + * ... + * }, + * ... + * } $block */ function wp_render_elements_class_name( $block_content, $block ) { - $class_string = $block['attrs']['className'] ?? ''; - - if ( ! is_string( $class_string ) ) { + $class_name_attr = $block['attrs']['className'] ?? null; + $class_name_prefix = 'wp-elements-'; + if ( ! is_string( $class_name_attr ) || ! str_contains( $class_name_attr, $class_name_prefix ) ) { return $block_content; } - preg_match( '/\bwp-elements-\S+\b/', $class_string, $matches ); - if ( empty( $matches ) ) { + // Parse out the 'wp-elements-*' class name. + $matched_class_name = null; + $token_delimiter = " \t\f\r\n"; + $class_token = strtok( $class_name_attr, $token_delimiter ); + while ( false !== $class_token ) { + if ( str_starts_with( $class_token, $class_name_prefix ) ) { + $matched_class_name = $class_token; + break; + } + $class_token = strtok( $token_delimiter ); + } + if ( null === $matched_class_name ) { return $block_content; } $tags = new WP_HTML_Tag_Processor( $block_content ); - if ( $tags->next_tag() ) { - $tags->add_class( $matches[0] ); + $tags->add_class( $matched_class_name ); } return $tags->get_updated_html(); diff --git a/tests/phpunit/tests/block-supports/wpRenderElementsSupport.php b/tests/phpunit/tests/block-supports/wpRenderElementsSupport.php index b5f2366ef4349..9a81ce0efc067 100644 --- a/tests/phpunit/tests/block-supports/wpRenderElementsSupport.php +++ b/tests/phpunit/tests/block-supports/wpRenderElementsSupport.php @@ -135,6 +135,30 @@ public function test_elements_block_support_class_with_non_string_class_name(): ); } + /** + * Tests that a 'my-wp-elements-*' class name is skipped from processing. + * + * @ticket 65379 + * + * @covers ::wp_render_elements_class_name + */ + public function test_elements_block_support_class_with_invalid_elements_prefix(): void { + $block = array( + 'blockName' => 'core/paragraph', + 'attrs' => array( + 'className' => 'my-wp-elements-foo', + ), + ); + + $block_content = "

Test

\n"; + + $this->assertSame( + $block_content, + wp_render_elements_class_name( $block_content, $block ), + 'Block content should be returned unchanged when className is not a string' + ); + } + /** * Data provider. * From 4265f5f1561895287e41b7774457b212a26e6b6c Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Mon, 8 Jun 2026 17:17:06 -0700 Subject: [PATCH 6/7] Fix assertion message --- tests/phpunit/tests/block-supports/wpRenderElementsSupport.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/phpunit/tests/block-supports/wpRenderElementsSupport.php b/tests/phpunit/tests/block-supports/wpRenderElementsSupport.php index 9a81ce0efc067..8e3f555de52ca 100644 --- a/tests/phpunit/tests/block-supports/wpRenderElementsSupport.php +++ b/tests/phpunit/tests/block-supports/wpRenderElementsSupport.php @@ -155,7 +155,7 @@ public function test_elements_block_support_class_with_invalid_elements_prefix() $this->assertSame( $block_content, wp_render_elements_class_name( $block_content, $block ), - 'Block content should be returned unchanged when className is not a string' + 'Block content should be returned unchanged when className lacks a class with the expected prefix' ); } From b074209c7eff0538dd3ad128adb23376b4d906eb Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Mon, 8 Jun 2026 17:28:04 -0700 Subject: [PATCH 7/7] Add phpstan-ignore --- tests/phpunit/tests/block-supports/wpRenderElementsSupport.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/phpunit/tests/block-supports/wpRenderElementsSupport.php b/tests/phpunit/tests/block-supports/wpRenderElementsSupport.php index 8e3f555de52ca..007ba8312e495 100644 --- a/tests/phpunit/tests/block-supports/wpRenderElementsSupport.php +++ b/tests/phpunit/tests/block-supports/wpRenderElementsSupport.php @@ -130,7 +130,7 @@ public function test_elements_block_support_class_with_non_string_class_name(): $this->assertSame( $block_content, - wp_render_elements_class_name( $block_content, $block ), + wp_render_elements_class_name( $block_content, $block ), // @phpstan-ignore argument.type (Intentionally passing bad attrs array.) 'Block content should be returned unchanged when className is not a string' ); }