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 374da09e8bc8c..54b96aa1dc064 100644 --- a/src/wp-includes/block-supports/elements.php +++ b/src/wp-includes/block-supports/elements.php @@ -237,22 +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 */ function wp_render_elements_class_name( $block_content, $block ) { - $class_string = $block['attrs']['className'] ?? ''; - preg_match( '/\bwp-elements-\S+\b/', $class_string, $matches ); + $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; + } - 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 9103fcba90b66..007ba8312e495 100644 --- a/tests/phpunit/tests/block-supports/wpRenderElementsSupport.php +++ b/tests/phpunit/tests/block-supports/wpRenderElementsSupport.php @@ -105,6 +105,60 @@ 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. + * + * 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(): void { + $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 ), // @phpstan-ignore argument.type (Intentionally passing bad attrs array.) + 'Block content should be returned unchanged when className is not a string' + ); + } + + /** + * 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 lacks a class with the expected prefix' + ); + } + /** * Data provider. *