Editor: Guard against non-string className in wp_render_elements_class_name() block render filter callback.

The `wp_render_elements_class_name()` function reads the `className` block attribute and passes it straight to `preg_match()`. While `className` is expected to be a string, malformed or corrupted stored block data can hold another type, such as an array, which triggers a fatal `TypeError` on PHP 8+. Guard against this by returning the block content unchanged when `className` is not a string.

While here, align the implementation with `wp_render_custom_css_class_name()` from r62359: replace the regular expression with a `str_contains()` short-circuit followed by an HTML-spec-compliant `strtok()` walk over the class tokens. This also corrects a latent matching bug: the previous `\bwp-elements-\S+\b` pattern treated the hyphen as a word boundary, so a class such as `my-wp-elements-foo` was erroneously matched. Tokenizing the attribute first ensures only a standalone `wp-elements-*` class is applied.

Add regression tests for the non-string and substring-prefix cases, and resolve PHPStan errors at rule level 10 (`missingType.iterableValue`, `offsetAccess.nonOffsetAccessible`, `argument.type`) by adding an array shape to the `@phpstan-param` tag.

Developed in https://github.com/WordPress/wordpress-develop/pull/12028 and https://github.com/WordPress/gutenberg/pull/78841.
Follow-up to r58074, r62359.

Props aaronrobertshaw, andrewserong, mukesh27, westonruter.
Fixes #65379.

Built from https://develop.svn.wordpress.org/trunk@62475


git-svn-id: http://core.svn.wordpress.org/trunk@61756 1a063a9b-81f0-0310-95a4-ce76da25c4cd
This commit is contained in:
Weston Ruter
2026-06-09 00:49:31 +00:00
parent 628bd24697
commit 5ab5a53640
3 changed files with 40 additions and 20 deletions
+10 -11
View File
@@ -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();
+29 -8
View File
@@ -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();
+1 -1
View File
@@ -16,7 +16,7 @@
*
* @global string $wp_version
*/
$wp_version = '7.1-alpha-62474';
$wp_version = '7.1-alpha-62475';
/**
* Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.