diff --git a/src/wp-includes/block-supports/custom-css.php b/src/wp-includes/block-supports/custom-css.php index 2cd7644ed54b1..d4331ae3706ae 100644 --- a/src/wp-includes/block-supports/custom-css.php +++ b/src/wp-includes/block-supports/custom-css.php @@ -12,17 +12,28 @@ * * @param array $parsed_block The parsed block. * @return array The same parsed block with custom CSS class name added if appropriate. + * + * @phpstan-param array{ + * blockName: string|null, + * attrs: array{ + * className?: string, + * style?: array{ + * css?: string, + * ... + * }, + * ... + * }, + * ... + * } $parsed_block */ function wp_render_custom_css_support_styles( $parsed_block ) { - $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $parsed_block['blockName'] ); - - if ( ! block_has_support( $block_type, 'customCSS', true ) ) { + $custom_css = $parsed_block['attrs']['style']['css'] ?? null; + if ( ! is_string( $custom_css ) || '' === trim( $custom_css ) ) { return $parsed_block; } - $custom_css = trim( $parsed_block['attrs']['style']['css'] ?? '' ); - - if ( empty( $custom_css ) ) { + $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $parsed_block['blockName'] ); + if ( ! block_has_support( $block_type, 'customCSS', true ) ) { return $parsed_block; } @@ -32,9 +43,10 @@ function wp_render_custom_css_support_styles( $parsed_block ) { } // Generate a unique class name for this block instance. - $class_name = wp_unique_id_from_values( $parsed_block, 'wp-custom-css-' ); - $updated_class_name = isset( $parsed_block['attrs']['className'] ) - ? $parsed_block['attrs']['className'] . " $class_name" + $class_name = wp_unique_id_from_values( $parsed_block, 'wp-custom-css-' ); + $existing_class_name = $parsed_block['attrs']['className'] ?? null; + $updated_class_name = is_string( $existing_class_name ) + ? "$existing_class_name $class_name" : $class_name; _wp_array_set( $parsed_block, array( 'attrs', 'className' ), $updated_class_name ); @@ -68,7 +80,7 @@ function wp_enqueue_block_custom_css() { /** * Applies the custom CSS class name to the block's rendered HTML. * - * The class name is generated in `wp_render_custom_css_support_styles` + * The class name is generated in {@see wp_render_custom_css_support_styles()} * and stored in block attributes. This filter adds it to the actual markup. * * @since 7.0.0 @@ -76,12 +88,34 @@ function wp_enqueue_block_custom_css() { * @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_custom_css_class_name( $block_content, $block ) { - $class_string = $block['attrs']['className'] ?? ''; - preg_match( '/\bwp-custom-css-\S+\b/', $class_string, $matches ); + $class_name_attr = $block['attrs']['className'] ?? null; + + if ( ! is_string( $class_name_attr ) || ! str_contains( $class_name_attr, 'wp-custom-css-' ) ) { + return $block_content; + } - if ( empty( $matches ) ) { + // 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 ); + while ( false !== $class_token ) { + if ( str_starts_with( $class_token, 'wp-custom-css-' ) ) { + $custom_class_name = $class_token; + break; + } + $class_token = strtok( $token_delimiter ); + } + if ( null === $custom_class_name ) { return $block_content; } @@ -89,7 +123,7 @@ function wp_render_custom_css_class_name( $block_content, $block ) { if ( $tags->next_tag() ) { $tags->add_class( 'has-custom-css' ); - $tags->add_class( $matches[0] ); + $tags->add_class( $custom_class_name ); } return $tags->get_updated_html(); diff --git a/src/wp-includes/blocks.php b/src/wp-includes/blocks.php index cc1ac60667773..6a6418d966457 100644 --- a/src/wp-includes/blocks.php +++ b/src/wp-includes/blocks.php @@ -2604,9 +2604,9 @@ function unregister_block_style( $block_name, $block_style_name ) { * @since 5.8.0 * @since 6.4.0 The `$feature` parameter now supports a string. * - * @param WP_Block_Type $block_type Block type to check for support. - * @param string|array $feature Feature slug, or path to a specific feature to check support for. - * @param mixed $default_value Optional. Fallback value for feature support. Default false. + * @param WP_Block_Type|null $block_type Block type to check for support. + * @param string|array $feature Feature slug, or path to a specific feature to check support for. + * @param mixed $default_value Optional. Fallback value for feature support. Default false. * @return bool Whether the feature is supported. */ function block_has_support( $block_type, $feature, $default_value = false ) { diff --git a/src/wp-includes/html-api/class-wp-html-tag-processor.php b/src/wp-includes/html-api/class-wp-html-tag-processor.php index 8397ecf520fa2..4015b352c153c 100644 --- a/src/wp-includes/html-api/class-wp-html-tag-processor.php +++ b/src/wp-includes/html-api/class-wp-html-tag-processor.php @@ -1175,6 +1175,8 @@ public function paused_at_incomplete_token(): bool { * // Outputs: "free lang-en " * * @since 6.4.0 + * + * @return Generator */ public function class_list() { if ( self::STATE_MATCHED_TAG !== $this->parser_state ) { diff --git a/tests/phpunit/tests/block-supports/wpRenderCustomCssClassName.php b/tests/phpunit/tests/block-supports/wpRenderCustomCssClassName.php index 0bcbc6c708468..1247ca65a1a33 100644 --- a/tests/phpunit/tests/block-supports/wpRenderCustomCssClassName.php +++ b/tests/phpunit/tests/block-supports/wpRenderCustomCssClassName.php @@ -29,9 +29,18 @@ public function test_adds_class_to_content( $block_content, $block, $expected_cl /** * Data provider. * - * @return array + * @return array */ - public function data_adds_class_to_content() { + public function data_adds_class_to_content(): array { return array( 'class is added to block content' => array( 'block_content' => '
Test content
', @@ -53,6 +62,16 @@ public function data_adds_class_to_content() { ), 'expected_class' => 'wp-custom-css-mixed123', ), + 'class between whitespace is added' => array( + 'block_content' => '
Test content
', + 'block' => array( + 'blockName' => 'core/paragraph', + 'attrs' => array( + 'className' => "\twp-custom-css-123abc\t", + ), + ), + 'expected_class' => 'wp-custom-css-123abc', + ), ); } @@ -113,6 +132,15 @@ public function data_returns_unchanged_content() { ), ), ), + 'prefixed custom CSS class' => array( + 'block_content' => '
Test content
', + 'block' => array( + 'blockName' => 'core/paragraph', + 'attrs' => array( + 'className' => 'my-wp-custom-css-456def', + ), + ), + ), 'className is not set in attrs' => array( 'block_content' => '
Test content
', 'block' => array(