From e1dbb38e9b58caf2524409341599611bc1e8709a Mon Sep 17 00:00:00 2001 From: Glen Davies Date: Fri, 23 Jan 2026 12:53:33 +1300 Subject: [PATCH 01/12] Add files to backport https://github.com/WordPress/gutenberg/pull/73959 --- src/wp-includes/block-editor.php | 2 + src/wp-includes/block-supports/custom-css.php | 99 +++++++++++++++++++ src/wp-includes/class-wp-theme-json.php | 3 +- src/wp-settings.php | 1 + 4 files changed, 104 insertions(+), 1 deletion(-) create mode 100644 src/wp-includes/block-supports/custom-css.php diff --git a/src/wp-includes/block-editor.php b/src/wp-includes/block-editor.php index af873178eb7aa..9d0cfc1cec9f0 100644 --- a/src/wp-includes/block-editor.php +++ b/src/wp-includes/block-editor.php @@ -658,6 +658,8 @@ function get_block_editor_settings( array $custom_settings, $block_editor_contex $editor_settings = apply_filters_deprecated( 'block_editor_settings', array( $editor_settings, $post ), '5.8.0', 'block_editor_settings_all' ); } + $settings['canEditCSS'] = current_user_can( 'edit_css' ); + return $editor_settings; } diff --git a/src/wp-includes/block-supports/custom-css.php b/src/wp-includes/block-supports/custom-css.php new file mode 100644 index 0000000000000..509372ecef41f --- /dev/null +++ b/src/wp-includes/block-supports/custom-css.php @@ -0,0 +1,99 @@ +get_registered( $parsed_block['blockName'] ); + + if ( ! block_has_support( $block_type, 'customCSS', true ) ) { + return $parsed_block; + } + + $custom_css = trim( $parsed_block['attrs']['style']['css'] ?? '' ); + + if ( empty( $custom_css ) ) { + return $parsed_block; + } + + // Validate CSS doesn't contain HTML markup (same validation as global styles REST API). + if ( preg_match( '#next_tag() ) { + $tags->add_class( $matches[0] ); + } + + return $tags->get_updated_html(); +} + +add_filter( 'render_block', 'gutenberg_render_custom_css_class_name', 10, 2 ); +add_filter( 'render_block_data', 'gutenberg_render_custom_css_support_styles', 10, 1 ); +add_action( 'wp_enqueue_scripts', 'gutenberg_enqueue_block_custom_css', 1 ); diff --git a/src/wp-includes/class-wp-theme-json.php b/src/wp-includes/class-wp-theme-json.php index c7929dad9e689..cf657364ebb93 100644 --- a/src/wp-includes/class-wp-theme-json.php +++ b/src/wp-includes/class-wp-theme-json.php @@ -1520,12 +1520,13 @@ public function get_stylesheet( $types = array( 'variables', 'styles', 'presets' * * @since 6.2.0 * @since 6.6.0 Enforced 0-1-0 specificity for block custom CSS selectors. + * @since 7.0.0 Made public for use in custom-css block support. * * @param string $css The CSS to process. * @param string $selector The selector to nest. * @return string The processed CSS. */ - protected function process_blocks_custom_css( $css, $selector ) { + public function process_blocks_custom_css( $css, $selector ) { $processed_css = ''; if ( empty( $css ) ) { diff --git a/src/wp-settings.php b/src/wp-settings.php index 60c220100f539..a509c37f5e51c 100644 --- a/src/wp-settings.php +++ b/src/wp-settings.php @@ -412,6 +412,7 @@ require ABSPATH . WPINC . '/block-supports/aria-label.php'; require ABSPATH . WPINC . '/block-supports/anchor.php'; require ABSPATH . WPINC . '/block-supports/block-visibility.php'; +require ABSPATH . WPINC . '/block-supports/custom-css.php'; require ABSPATH . WPINC . '/style-engine.php'; require ABSPATH . WPINC . '/style-engine/class-wp-style-engine.php'; require ABSPATH . WPINC . '/style-engine/class-wp-style-engine-css-declarations.php'; From 28e3da25fda23a96673f1f281fa0b1bc121103ea Mon Sep 17 00:00:00 2001 From: Glen Davies Date: Fri, 23 Jan 2026 13:04:53 +1300 Subject: [PATCH 02/12] Add test --- .../tests/block-supports/custom-css.php | 441 ++++++++++++++++++ 1 file changed, 441 insertions(+) create mode 100644 tests/phpunit/tests/block-supports/custom-css.php diff --git a/tests/phpunit/tests/block-supports/custom-css.php b/tests/phpunit/tests/block-supports/custom-css.php new file mode 100644 index 0000000000000..c94b5ff7c0611 --- /dev/null +++ b/tests/phpunit/tests/block-supports/custom-css.php @@ -0,0 +1,441 @@ +test_block_name = null; + } + + public function tear_down() { + if ( $this->test_block_name ) { + unregister_block_type( $this->test_block_name ); + } + $this->test_block_name = null; + parent::tear_down(); + } + + /** + * Registers a new block for testing custom CSS support. + * + * @param string $block_name Name for the test block. + * @param array $supports Array defining block support configuration. + * + * @return WP_Block_Type The block type for the newly registered test block. + */ + private function register_custom_css_block_with_support( $block_name, $supports = array() ) { + $this->test_block_name = $block_name; + register_block_type( + $this->test_block_name, + array( + 'api_version' => 3, + 'attributes' => array( + 'style' => array( + 'type' => 'object', + ), + ), + 'supports' => $supports, + ) + ); + $registry = WP_Block_Type_Registry::get_instance(); + + return $registry->get_registered( $this->test_block_name ); + } + + /** + * Tests that custom CSS support adds class name when block has custom CSS. + * + * @covers ::gutenberg_render_custom_css_support_styles + */ + public function test_custom_css_support_adds_class_name_when_css_present() { + $this->register_custom_css_block_with_support( + 'test/custom-css-block', + array( 'customCSS' => true ) + ); + + $parsed_block = array( + 'blockName' => 'test/custom-css-block', + 'attrs' => array( + 'style' => array( + 'css' => 'color: red;', + ), + ), + ); + + $result = gutenberg_render_custom_css_support_styles( $parsed_block ); + + $this->assertArrayHasKey( 'className', $result['attrs'], 'Block should have className added.' ); + $this->assertMatchesRegularExpression( '/wp-custom-css-/', $result['attrs']['className'], 'className should contain wp-custom-css- prefix.' ); + } + + /** + * Tests that custom CSS support preserves existing className. + * + * @covers ::gutenberg_render_custom_css_support_styles + */ + public function test_custom_css_support_preserves_existing_class_name() { + $this->register_custom_css_block_with_support( + 'test/custom-css-block-existing', + array( 'customCSS' => true ) + ); + + $parsed_block = array( + 'blockName' => 'test/custom-css-block-existing', + 'attrs' => array( + 'className' => 'my-existing-class', + 'style' => array( + 'css' => 'color: blue;', + ), + ), + ); + + $result = gutenberg_render_custom_css_support_styles( $parsed_block ); + + $this->assertStringContainsString( 'my-existing-class', $result['attrs']['className'], 'Existing className should be preserved.' ); + $this->assertMatchesRegularExpression( '/wp-custom-css-/', $result['attrs']['className'], 'className should also contain wp-custom-css- prefix.' ); + } + + /** + * Tests that custom CSS support returns unchanged block when support is disabled. + * + * @covers ::gutenberg_render_custom_css_support_styles + */ + public function test_custom_css_support_returns_unchanged_when_support_disabled() { + $this->register_custom_css_block_with_support( + 'test/custom-css-disabled', + array( 'customCSS' => false ) + ); + + $parsed_block = array( + 'blockName' => 'test/custom-css-disabled', + 'attrs' => array( + 'style' => array( + 'css' => 'color: green;', + ), + ), + ); + + $result = gutenberg_render_custom_css_support_styles( $parsed_block ); + + $this->assertArrayNotHasKey( 'className', $result['attrs'], 'Block should not have className added when support is disabled.' ); + } + + /** + * Tests that custom CSS support returns unchanged block when no CSS attribute present. + * + * @covers ::gutenberg_render_custom_css_support_styles + */ + public function test_custom_css_support_returns_unchanged_when_no_css() { + $this->register_custom_css_block_with_support( + 'test/custom-css-no-css', + array( 'customCSS' => true ) + ); + + $parsed_block = array( + 'blockName' => 'test/custom-css-no-css', + 'attrs' => array( + 'style' => array( + 'color' => 'red', + ), + ), + ); + + $result = gutenberg_render_custom_css_support_styles( $parsed_block ); + + $this->assertArrayNotHasKey( 'className', $result['attrs'], 'Block should not have className added when no CSS attribute.' ); + } + + /** + * Tests that custom CSS support returns unchanged block when CSS is empty. + * + * @covers ::gutenberg_render_custom_css_support_styles + */ + public function test_custom_css_support_returns_unchanged_when_css_empty() { + $this->register_custom_css_block_with_support( + 'test/custom-css-empty', + array( 'customCSS' => true ) + ); + + $parsed_block = array( + 'blockName' => 'test/custom-css-empty', + 'attrs' => array( + 'style' => array( + 'css' => '', + ), + ), + ); + + $result = gutenberg_render_custom_css_support_styles( $parsed_block ); + + $this->assertArrayNotHasKey( 'className', $result['attrs'], 'Block should not have className added when CSS is empty.' ); + } + + /** + * Tests that custom CSS support returns unchanged block when CSS is whitespace only. + * + * @covers ::gutenberg_render_custom_css_support_styles + */ + public function test_custom_css_support_returns_unchanged_when_css_whitespace_only() { + $this->register_custom_css_block_with_support( + 'test/custom-css-whitespace', + array( 'customCSS' => true ) + ); + + $parsed_block = array( + 'blockName' => 'test/custom-css-whitespace', + 'attrs' => array( + 'style' => array( + 'css' => ' ', + ), + ), + ); + + $result = gutenberg_render_custom_css_support_styles( $parsed_block ); + + $this->assertArrayNotHasKey( 'className', $result['attrs'], 'Block should not have className added when CSS is whitespace only.' ); + } + + /** + * Tests that custom CSS support returns unchanged block when style attribute is missing. + * + * @covers ::gutenberg_render_custom_css_support_styles + */ + public function test_custom_css_support_returns_unchanged_when_no_style_attribute() { + $this->register_custom_css_block_with_support( + 'test/custom-css-no-style', + array( 'customCSS' => true ) + ); + + $parsed_block = array( + 'blockName' => 'test/custom-css-no-style', + 'attrs' => array(), + ); + + $result = gutenberg_render_custom_css_support_styles( $parsed_block ); + + $this->assertArrayNotHasKey( 'className', $result['attrs'], 'Block should not have className added when no style attribute.' ); + } + + /** + * Tests that render_block filter adds custom CSS class to block content. + * + * @covers ::gutenberg_render_custom_css_class_name + */ + public function test_render_custom_css_class_name_adds_class_to_content() { + $block_content = '
Test content
'; + $block = array( + 'blockName' => 'core/paragraph', + 'attrs' => array( + 'className' => 'wp-custom-css-123abc', + ), + ); + + $result = gutenberg_render_custom_css_class_name( $block_content, $block ); + + $this->assertStringContainsString( 'wp-custom-css-123abc', $result, 'Custom CSS class should be added to block content.' ); + } + + /** + * Tests that render_block filter preserves existing classes when adding custom CSS class. + * + * @covers ::gutenberg_render_custom_css_class_name + */ + public function test_render_custom_css_class_name_preserves_existing_classes() { + $block_content = '
Test content
'; + $block = array( + 'blockName' => 'core/paragraph', + 'attrs' => array( + 'className' => 'wp-custom-css-456def', + ), + ); + + $result = gutenberg_render_custom_css_class_name( $block_content, $block ); + + $this->assertStringContainsString( 'existing-class', $result, 'Existing classes should be preserved.' ); + $this->assertStringContainsString( 'another-class', $result, 'All existing classes should be preserved.' ); + $this->assertStringContainsString( 'wp-custom-css-456def', $result, 'Custom CSS class should be added.' ); + } + + /** + * Tests that render_block filter returns unchanged content when no custom CSS class in attrs. + * + * @covers ::gutenberg_render_custom_css_class_name + */ + public function test_render_custom_css_class_name_returns_unchanged_when_no_custom_css_class() { + $block_content = '
Test content
'; + $block = array( + 'blockName' => 'core/paragraph', + 'attrs' => array( + 'className' => 'some-other-class', + ), + ); + + $result = gutenberg_render_custom_css_class_name( $block_content, $block ); + + $this->assertSame( $block_content, $result, 'Block content should remain unchanged when no custom CSS class.' ); + } + + /** + * Tests that render_block filter returns unchanged content when className is empty. + * + * @covers ::gutenberg_render_custom_css_class_name + */ + public function test_render_custom_css_class_name_returns_unchanged_when_classname_empty() { + $block_content = '
Test content
'; + $block = array( + 'blockName' => 'core/paragraph', + 'attrs' => array(), + ); + + $result = gutenberg_render_custom_css_class_name( $block_content, $block ); + + $this->assertSame( $block_content, $result, 'Block content should remain unchanged when className is empty.' ); + } + + /** + * Tests that render_block filter returns empty string when content is empty. + * + * @covers ::gutenberg_render_custom_css_class_name + */ + public function test_render_custom_css_class_name_returns_empty_when_content_empty() { + $block_content = ''; + $block = array( + 'blockName' => 'core/paragraph', + 'attrs' => array( + 'className' => 'wp-custom-css-789ghi', + ), + ); + + $result = gutenberg_render_custom_css_class_name( $block_content, $block ); + + $this->assertSame( '', $result, 'Result should be empty when block content is empty.' ); + } + + /** + * Tests that custom CSS class is extracted correctly when mixed with other classes. + * + * @covers ::gutenberg_render_custom_css_class_name + */ + public function test_render_custom_css_class_name_extracts_class_from_mixed_classnames() { + $block_content = '

Test content

'; + $block = array( + 'blockName' => 'core/paragraph', + 'attrs' => array( + 'className' => 'my-class wp-custom-css-mixed123 another-class', + ), + ); + + $result = gutenberg_render_custom_css_class_name( $block_content, $block ); + + $this->assertStringContainsString( 'wp-custom-css-mixed123', $result, 'Custom CSS class should be extracted and added.' ); + } + + /** + * Tests that custom CSS support is enabled by default. + * + * @covers ::gutenberg_render_custom_css_support_styles + */ + public function test_custom_css_support_enabled_by_default() { + $this->register_custom_css_block_with_support( + 'test/custom-css-default', + array() // No explicit customCSS support defined. + ); + + $parsed_block = array( + 'blockName' => 'test/custom-css-default', + 'attrs' => array( + 'style' => array( + 'css' => 'font-weight: bold;', + ), + ), + ); + + $result = gutenberg_render_custom_css_support_styles( $parsed_block ); + + $this->assertArrayHasKey( 'className', $result['attrs'], 'Block should have className added by default when customCSS support is not explicitly set.' ); + } + + /** + * Tests that custom CSS containing HTML opening tags is rejected. + * + * @covers ::gutenberg_render_custom_css_support_styles + */ + public function test_custom_css_rejects_html_opening_tags() { + $this->register_custom_css_block_with_support( + 'test/custom-css-html-open', + array( 'customCSS' => true ) + ); + + $parsed_block = array( + 'blockName' => 'test/custom-css-html-open', + 'attrs' => array( + 'style' => array( + 'css' => '', + ), + ), + ); + + $result = gutenberg_render_custom_css_support_styles( $parsed_block ); + + $this->assertArrayNotHasKey( 'className', $result['attrs'], 'Block should not have className added when CSS contains HTML opening tags.' ); + } + + /** + * Tests that custom CSS containing HTML closing tags is rejected. + * + * @covers ::gutenberg_render_custom_css_support_styles + */ + public function test_custom_css_rejects_html_closing_tags() { + $this->register_custom_css_block_with_support( + 'test/custom-css-html-close', + array( 'customCSS' => true ) + ); + + $parsed_block = array( + 'blockName' => 'test/custom-css-html-close', + 'attrs' => array( + 'style' => array( + 'css' => 'color: red;', + ), + ), + ); + + $result = gutenberg_render_custom_css_support_styles( $parsed_block ); + + $this->assertArrayNotHasKey( 'className', $result['attrs'], 'Block should not have className added when CSS contains HTML closing tags.' ); + } + + /** + * Tests that valid CSS without HTML markup is accepted. + * + * @covers ::gutenberg_render_custom_css_support_styles + */ + public function test_custom_css_accepts_valid_css() { + $this->register_custom_css_block_with_support( + 'test/custom-css-valid', + array( 'customCSS' => true ) + ); + + $parsed_block = array( + 'blockName' => 'test/custom-css-valid', + 'attrs' => array( + 'style' => array( + 'css' => 'color: red; background: url("image.png"); font-size: 16px;', + ), + ), + ); + + $result = gutenberg_render_custom_css_support_styles( $parsed_block ); + + $this->assertArrayHasKey( 'className', $result['attrs'], 'Block should have className added for valid CSS.' ); + } +} From 8646524545e00d23224ebe48426773c2a9d5bd62 Mon Sep 17 00:00:00 2001 From: Glen Davies Date: Fri, 23 Jan 2026 13:11:17 +1300 Subject: [PATCH 03/12] lint fix --- src/wp-includes/block-editor.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wp-includes/block-editor.php b/src/wp-includes/block-editor.php index 9d0cfc1cec9f0..0d9e3c0c1e067 100644 --- a/src/wp-includes/block-editor.php +++ b/src/wp-includes/block-editor.php @@ -659,7 +659,7 @@ function get_block_editor_settings( array $custom_settings, $block_editor_contex } $settings['canEditCSS'] = current_user_can( 'edit_css' ); - + return $editor_settings; } From 45a49735f444bd40c6038d44a278f5fcb3308231 Mon Sep 17 00:00:00 2001 From: Glen Davies Date: Thu, 12 Feb 2026 15:21:10 +1300 Subject: [PATCH 04/12] fix package name --- src/wp-includes/block-supports/custom-css.php | 2 +- tests/phpunit/tests/block-supports/custom-css.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/wp-includes/block-supports/custom-css.php b/src/wp-includes/block-supports/custom-css.php index 509372ecef41f..608c5055d894e 100644 --- a/src/wp-includes/block-supports/custom-css.php +++ b/src/wp-includes/block-supports/custom-css.php @@ -2,7 +2,7 @@ /** * Custom CSS block support. * - * @package gutenberg + * @package WordPress */ /** diff --git a/tests/phpunit/tests/block-supports/custom-css.php b/tests/phpunit/tests/block-supports/custom-css.php index c94b5ff7c0611..5a083d3662f31 100644 --- a/tests/phpunit/tests/block-supports/custom-css.php +++ b/tests/phpunit/tests/block-supports/custom-css.php @@ -2,7 +2,7 @@ /** * Test the custom CSS block support. * - * @package gutenberg + * @package WordPress */ class WP_Block_Supports_Custom_CSS_Test extends WP_UnitTestCase { From d00dd3362a3e226bc4ad16f9f45bb042de5cc530 Mon Sep 17 00:00:00 2001 From: Glen Davies Date: Thu, 12 Feb 2026 15:40:54 +1300 Subject: [PATCH 05/12] add change from https://github.com/WordPress/gutenberg/pull/74969 --- src/wp-includes/block-supports/custom-css.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/wp-includes/block-supports/custom-css.php b/src/wp-includes/block-supports/custom-css.php index 608c5055d894e..28c31f8cf4d95 100644 --- a/src/wp-includes/block-supports/custom-css.php +++ b/src/wp-includes/block-supports/custom-css.php @@ -88,6 +88,7 @@ function gutenberg_render_custom_css_class_name( $block_content, $block ) { $tags = new WP_HTML_Tag_Processor( $block_content ); if ( $tags->next_tag() ) { + $tags->add_class( 'has-custom-css' ); $tags->add_class( $matches[0] ); } From 6ed1b3bbd5fb343355e6d6c06732138abb2116c3 Mon Sep 17 00:00:00 2001 From: Glen Davies Date: Mon, 16 Feb 2026 12:55:58 +1300 Subject: [PATCH 06/12] fix tests --- src/wp-includes/block-supports/custom-css.php | 16 ++--- .../tests/block-supports/custom-css.php | 68 +++++++++---------- 2 files changed, 42 insertions(+), 42 deletions(-) diff --git a/src/wp-includes/block-supports/custom-css.php b/src/wp-includes/block-supports/custom-css.php index 28c31f8cf4d95..9a6ef1f4a34e7 100644 --- a/src/wp-includes/block-supports/custom-css.php +++ b/src/wp-includes/block-supports/custom-css.php @@ -13,7 +13,7 @@ * @param array $parsed_block The parsed block. * @return array The same parsed block with custom CSS class name added if appropriate. */ -function gutenberg_render_custom_css_support_styles( $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 ) ) { @@ -41,7 +41,7 @@ function gutenberg_render_custom_css_support_styles( $parsed_block ) { // Process the custom CSS using the same method as global styles. $selector = '.' . $class_name; - $processed_css = WP_Theme_JSON_Gutenberg::process_blocks_custom_css( $custom_css, $selector ); + $processed_css = WP_Theme_JSON::process_blocks_custom_css( $custom_css, $selector ); if ( ! empty( $processed_css ) ) { /* @@ -61,14 +61,14 @@ function gutenberg_render_custom_css_support_styles( $parsed_block ) { * * @since 7.0.0 */ -function gutenberg_enqueue_block_custom_css() { +function wp_enqueue_block_custom_css() { wp_enqueue_style( 'wp-block-custom-css' ); } /** * Applies the custom CSS class name to the block's rendered HTML. * - * The class name is generated in `gutenberg_render_custom_css_support_styles` + * The class name is generated in `wp_render_custom_css_support_styles` * and stored in block attributes. This filter adds it to the actual markup. * * @since 7.0.0 @@ -77,7 +77,7 @@ function gutenberg_enqueue_block_custom_css() { * @param array $block Block object. * @return string Filtered block content. */ -function gutenberg_render_custom_css_class_name( $block_content, $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 ); @@ -95,6 +95,6 @@ function gutenberg_render_custom_css_class_name( $block_content, $block ) { return $tags->get_updated_html(); } -add_filter( 'render_block', 'gutenberg_render_custom_css_class_name', 10, 2 ); -add_filter( 'render_block_data', 'gutenberg_render_custom_css_support_styles', 10, 1 ); -add_action( 'wp_enqueue_scripts', 'gutenberg_enqueue_block_custom_css', 1 ); +add_filter( 'render_block', 'wp_render_custom_css_class_name', 10, 2 ); +add_filter( 'render_block_data', 'wp_render_custom_css_support_styles', 10, 1 ); +add_action( 'wp_enqueue_scripts', 'wp_enqueue_block_custom_css', 1 ); diff --git a/tests/phpunit/tests/block-supports/custom-css.php b/tests/phpunit/tests/block-supports/custom-css.php index 5a083d3662f31..b69cc0379559c 100644 --- a/tests/phpunit/tests/block-supports/custom-css.php +++ b/tests/phpunit/tests/block-supports/custom-css.php @@ -54,7 +54,7 @@ private function register_custom_css_block_with_support( $block_name, $supports /** * Tests that custom CSS support adds class name when block has custom CSS. * - * @covers ::gutenberg_render_custom_css_support_styles + * @ticket 64544 */ public function test_custom_css_support_adds_class_name_when_css_present() { $this->register_custom_css_block_with_support( @@ -71,7 +71,7 @@ public function test_custom_css_support_adds_class_name_when_css_present() { ), ); - $result = gutenberg_render_custom_css_support_styles( $parsed_block ); + $result = wp_render_custom_css_support_styles( $parsed_block ); $this->assertArrayHasKey( 'className', $result['attrs'], 'Block should have className added.' ); $this->assertMatchesRegularExpression( '/wp-custom-css-/', $result['attrs']['className'], 'className should contain wp-custom-css- prefix.' ); @@ -80,7 +80,7 @@ public function test_custom_css_support_adds_class_name_when_css_present() { /** * Tests that custom CSS support preserves existing className. * - * @covers ::gutenberg_render_custom_css_support_styles + * @ticket 64544 */ public function test_custom_css_support_preserves_existing_class_name() { $this->register_custom_css_block_with_support( @@ -98,7 +98,7 @@ public function test_custom_css_support_preserves_existing_class_name() { ), ); - $result = gutenberg_render_custom_css_support_styles( $parsed_block ); + $result = wp_render_custom_css_support_styles( $parsed_block ); $this->assertStringContainsString( 'my-existing-class', $result['attrs']['className'], 'Existing className should be preserved.' ); $this->assertMatchesRegularExpression( '/wp-custom-css-/', $result['attrs']['className'], 'className should also contain wp-custom-css- prefix.' ); @@ -107,7 +107,7 @@ public function test_custom_css_support_preserves_existing_class_name() { /** * Tests that custom CSS support returns unchanged block when support is disabled. * - * @covers ::gutenberg_render_custom_css_support_styles + * @ticket 64544 */ public function test_custom_css_support_returns_unchanged_when_support_disabled() { $this->register_custom_css_block_with_support( @@ -124,7 +124,7 @@ public function test_custom_css_support_returns_unchanged_when_support_disabled( ), ); - $result = gutenberg_render_custom_css_support_styles( $parsed_block ); + $result = wp_render_custom_css_support_styles( $parsed_block ); $this->assertArrayNotHasKey( 'className', $result['attrs'], 'Block should not have className added when support is disabled.' ); } @@ -132,7 +132,7 @@ public function test_custom_css_support_returns_unchanged_when_support_disabled( /** * Tests that custom CSS support returns unchanged block when no CSS attribute present. * - * @covers ::gutenberg_render_custom_css_support_styles + * @ticket 64544 */ public function test_custom_css_support_returns_unchanged_when_no_css() { $this->register_custom_css_block_with_support( @@ -149,7 +149,7 @@ public function test_custom_css_support_returns_unchanged_when_no_css() { ), ); - $result = gutenberg_render_custom_css_support_styles( $parsed_block ); + $result = wp_render_custom_css_support_styles( $parsed_block ); $this->assertArrayNotHasKey( 'className', $result['attrs'], 'Block should not have className added when no CSS attribute.' ); } @@ -157,7 +157,7 @@ public function test_custom_css_support_returns_unchanged_when_no_css() { /** * Tests that custom CSS support returns unchanged block when CSS is empty. * - * @covers ::gutenberg_render_custom_css_support_styles + * @ticket 64544 */ public function test_custom_css_support_returns_unchanged_when_css_empty() { $this->register_custom_css_block_with_support( @@ -174,7 +174,7 @@ public function test_custom_css_support_returns_unchanged_when_css_empty() { ), ); - $result = gutenberg_render_custom_css_support_styles( $parsed_block ); + $result = wp_render_custom_css_support_styles( $parsed_block ); $this->assertArrayNotHasKey( 'className', $result['attrs'], 'Block should not have className added when CSS is empty.' ); } @@ -182,7 +182,7 @@ public function test_custom_css_support_returns_unchanged_when_css_empty() { /** * Tests that custom CSS support returns unchanged block when CSS is whitespace only. * - * @covers ::gutenberg_render_custom_css_support_styles + * @ticket 64544 */ public function test_custom_css_support_returns_unchanged_when_css_whitespace_only() { $this->register_custom_css_block_with_support( @@ -199,7 +199,7 @@ public function test_custom_css_support_returns_unchanged_when_css_whitespace_on ), ); - $result = gutenberg_render_custom_css_support_styles( $parsed_block ); + $result = wp_render_custom_css_support_styles( $parsed_block ); $this->assertArrayNotHasKey( 'className', $result['attrs'], 'Block should not have className added when CSS is whitespace only.' ); } @@ -207,7 +207,7 @@ public function test_custom_css_support_returns_unchanged_when_css_whitespace_on /** * Tests that custom CSS support returns unchanged block when style attribute is missing. * - * @covers ::gutenberg_render_custom_css_support_styles + * @ticket 64544 */ public function test_custom_css_support_returns_unchanged_when_no_style_attribute() { $this->register_custom_css_block_with_support( @@ -220,7 +220,7 @@ public function test_custom_css_support_returns_unchanged_when_no_style_attribut 'attrs' => array(), ); - $result = gutenberg_render_custom_css_support_styles( $parsed_block ); + $result = wp_render_custom_css_support_styles( $parsed_block ); $this->assertArrayNotHasKey( 'className', $result['attrs'], 'Block should not have className added when no style attribute.' ); } @@ -228,7 +228,7 @@ public function test_custom_css_support_returns_unchanged_when_no_style_attribut /** * Tests that render_block filter adds custom CSS class to block content. * - * @covers ::gutenberg_render_custom_css_class_name + * @@ticket 64544 */ public function test_render_custom_css_class_name_adds_class_to_content() { $block_content = '
Test content
'; @@ -239,7 +239,7 @@ public function test_render_custom_css_class_name_adds_class_to_content() { ), ); - $result = gutenberg_render_custom_css_class_name( $block_content, $block ); + $result = wp_render_custom_css_class_name( $block_content, $block ); $this->assertStringContainsString( 'wp-custom-css-123abc', $result, 'Custom CSS class should be added to block content.' ); } @@ -247,7 +247,7 @@ public function test_render_custom_css_class_name_adds_class_to_content() { /** * Tests that render_block filter preserves existing classes when adding custom CSS class. * - * @covers ::gutenberg_render_custom_css_class_name + * @ticket 64544 */ public function test_render_custom_css_class_name_preserves_existing_classes() { $block_content = '
Test content
'; @@ -258,7 +258,7 @@ public function test_render_custom_css_class_name_preserves_existing_classes() { ), ); - $result = gutenberg_render_custom_css_class_name( $block_content, $block ); + $result = wp_render_custom_css_class_name( $block_content, $block ); $this->assertStringContainsString( 'existing-class', $result, 'Existing classes should be preserved.' ); $this->assertStringContainsString( 'another-class', $result, 'All existing classes should be preserved.' ); @@ -268,7 +268,7 @@ public function test_render_custom_css_class_name_preserves_existing_classes() { /** * Tests that render_block filter returns unchanged content when no custom CSS class in attrs. * - * @covers ::gutenberg_render_custom_css_class_name + * @ticket 64544 */ public function test_render_custom_css_class_name_returns_unchanged_when_no_custom_css_class() { $block_content = '
Test content
'; @@ -279,7 +279,7 @@ public function test_render_custom_css_class_name_returns_unchanged_when_no_cust ), ); - $result = gutenberg_render_custom_css_class_name( $block_content, $block ); + $result = wp_render_custom_css_class_name( $block_content, $block ); $this->assertSame( $block_content, $result, 'Block content should remain unchanged when no custom CSS class.' ); } @@ -287,7 +287,7 @@ public function test_render_custom_css_class_name_returns_unchanged_when_no_cust /** * Tests that render_block filter returns unchanged content when className is empty. * - * @covers ::gutenberg_render_custom_css_class_name + * @ticket 64544 */ public function test_render_custom_css_class_name_returns_unchanged_when_classname_empty() { $block_content = '
Test content
'; @@ -296,7 +296,7 @@ public function test_render_custom_css_class_name_returns_unchanged_when_classna 'attrs' => array(), ); - $result = gutenberg_render_custom_css_class_name( $block_content, $block ); + $result = wp_render_custom_css_class_name( $block_content, $block ); $this->assertSame( $block_content, $result, 'Block content should remain unchanged when className is empty.' ); } @@ -304,7 +304,7 @@ public function test_render_custom_css_class_name_returns_unchanged_when_classna /** * Tests that render_block filter returns empty string when content is empty. * - * @covers ::gutenberg_render_custom_css_class_name + * @ticket 64544 */ public function test_render_custom_css_class_name_returns_empty_when_content_empty() { $block_content = ''; @@ -315,7 +315,7 @@ public function test_render_custom_css_class_name_returns_empty_when_content_emp ), ); - $result = gutenberg_render_custom_css_class_name( $block_content, $block ); + $result = wp_render_custom_css_class_name( $block_content, $block ); $this->assertSame( '', $result, 'Result should be empty when block content is empty.' ); } @@ -323,7 +323,7 @@ public function test_render_custom_css_class_name_returns_empty_when_content_emp /** * Tests that custom CSS class is extracted correctly when mixed with other classes. * - * @covers ::gutenberg_render_custom_css_class_name + * @ticket 64544 */ public function test_render_custom_css_class_name_extracts_class_from_mixed_classnames() { $block_content = '

Test content

'; @@ -334,7 +334,7 @@ public function test_render_custom_css_class_name_extracts_class_from_mixed_clas ), ); - $result = gutenberg_render_custom_css_class_name( $block_content, $block ); + $result = wp_render_custom_css_class_name( $block_content, $block ); $this->assertStringContainsString( 'wp-custom-css-mixed123', $result, 'Custom CSS class should be extracted and added.' ); } @@ -342,7 +342,7 @@ public function test_render_custom_css_class_name_extracts_class_from_mixed_clas /** * Tests that custom CSS support is enabled by default. * - * @covers ::gutenberg_render_custom_css_support_styles + * @ticket 64544 */ public function test_custom_css_support_enabled_by_default() { $this->register_custom_css_block_with_support( @@ -359,7 +359,7 @@ public function test_custom_css_support_enabled_by_default() { ), ); - $result = gutenberg_render_custom_css_support_styles( $parsed_block ); + $result = wp_render_custom_css_support_styles( $parsed_block ); $this->assertArrayHasKey( 'className', $result['attrs'], 'Block should have className added by default when customCSS support is not explicitly set.' ); } @@ -367,7 +367,7 @@ public function test_custom_css_support_enabled_by_default() { /** * Tests that custom CSS containing HTML opening tags is rejected. * - * @covers ::gutenberg_render_custom_css_support_styles + * @ticket 64544 */ public function test_custom_css_rejects_html_opening_tags() { $this->register_custom_css_block_with_support( @@ -384,7 +384,7 @@ public function test_custom_css_rejects_html_opening_tags() { ), ); - $result = gutenberg_render_custom_css_support_styles( $parsed_block ); + $result = wp_render_custom_css_support_styles( $parsed_block ); $this->assertArrayNotHasKey( 'className', $result['attrs'], 'Block should not have className added when CSS contains HTML opening tags.' ); } @@ -392,7 +392,7 @@ public function test_custom_css_rejects_html_opening_tags() { /** * Tests that custom CSS containing HTML closing tags is rejected. * - * @covers ::gutenberg_render_custom_css_support_styles + * @ticket 64544 */ public function test_custom_css_rejects_html_closing_tags() { $this->register_custom_css_block_with_support( @@ -409,7 +409,7 @@ public function test_custom_css_rejects_html_closing_tags() { ), ); - $result = gutenberg_render_custom_css_support_styles( $parsed_block ); + $result = wp_render_custom_css_support_styles( $parsed_block ); $this->assertArrayNotHasKey( 'className', $result['attrs'], 'Block should not have className added when CSS contains HTML closing tags.' ); } @@ -417,7 +417,7 @@ public function test_custom_css_rejects_html_closing_tags() { /** * Tests that valid CSS without HTML markup is accepted. * - * @covers ::gutenberg_render_custom_css_support_styles + * @ticket 64544 */ public function test_custom_css_accepts_valid_css() { $this->register_custom_css_block_with_support( @@ -434,7 +434,7 @@ public function test_custom_css_accepts_valid_css() { ), ); - $result = gutenberg_render_custom_css_support_styles( $parsed_block ); + $result = wp_render_custom_css_support_styles( $parsed_block ); $this->assertArrayHasKey( 'className', $result['attrs'], 'Block should have className added for valid CSS.' ); } From f51d7693781b42d1c93ed69f504b0185dcb3328d Mon Sep 17 00:00:00 2001 From: Glen Davies Date: Mon, 16 Feb 2026 12:59:45 +1300 Subject: [PATCH 07/12] add https://github.com/WordPress/gutenberg/pull/75052 --- src/wp-includes/block-supports/custom-css.php | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/wp-includes/block-supports/custom-css.php b/src/wp-includes/block-supports/custom-css.php index 9a6ef1f4a34e7..9d5b13426f4ef 100644 --- a/src/wp-includes/block-supports/custom-css.php +++ b/src/wp-includes/block-supports/custom-css.php @@ -98,3 +98,36 @@ function wp_render_custom_css_class_name( $block_content, $block ) { add_filter( 'render_block', 'wp_render_custom_css_class_name', 10, 2 ); add_filter( 'render_block_data', 'wp_render_custom_css_support_styles', 10, 1 ); add_action( 'wp_enqueue_scripts', 'wp_enqueue_block_custom_css', 1 ); + +/** + * Registers the style block attribute for block types that support it. + * + * @param WP_Block_Type $block_type Block Type. + */ +function wp_register_custom_css_support( $block_type ) { + // Setup attributes and styles within that if needed. + if ( ! $block_type->attributes ) { + $block_type->attributes = array(); + } + + // Check for existing style attribute definition e.g. from block.json. + if ( array_key_exists( 'style', $block_type->attributes ) ) { + return; + } + + $has_custom_css_support = block_has_support( $block_type, array( 'customCSS' ), true ); + + if ( $has_custom_css_support ) { + $block_type->attributes['style'] = array( + 'type' => 'object', + ); + } +} + +// Register the block support. +WP_Block_Supports::get_instance()->register( + 'custom-css', + array( + 'register_attribute' => 'wp_register_custom_css_support', + ) +); From 8e774afa4bd89bedc16d8c21ad9f879ba94c5c9a Mon Sep 17 00:00:00 2001 From: Glen Davies Date: Mon, 16 Feb 2026 13:40:54 +1300 Subject: [PATCH 08/12] another test fix --- src/wp-includes/class-wp-theme-json.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wp-includes/class-wp-theme-json.php b/src/wp-includes/class-wp-theme-json.php index cf657364ebb93..b989bab4a6474 100644 --- a/src/wp-includes/class-wp-theme-json.php +++ b/src/wp-includes/class-wp-theme-json.php @@ -1526,7 +1526,7 @@ public function get_stylesheet( $types = array( 'variables', 'styles', 'presets' * @param string $selector The selector to nest. * @return string The processed CSS. */ - public function process_blocks_custom_css( $css, $selector ) { + public static function process_blocks_custom_css( $css, $selector ) { $processed_css = ''; if ( empty( $css ) ) { From 6240c647da644c84aeb5fa0c9df6ec6d5c3081de Mon Sep 17 00:00:00 2001 From: Glen Davies Date: Tue, 17 Feb 2026 11:23:34 +1300 Subject: [PATCH 09/12] Suggested updates from review --- src/wp-includes/block-editor.php | 2 +- .../tests/block-supports/custom-css.php | 441 ------------------ .../wpRenderCustomCssClassName.php | 134 ++++++ .../wpRenderCustomCssSupportStyles.php | 267 +++++++++++ 4 files changed, 402 insertions(+), 442 deletions(-) delete mode 100644 tests/phpunit/tests/block-supports/custom-css.php create mode 100644 tests/phpunit/tests/block-supports/wpRenderCustomCssClassName.php create mode 100644 tests/phpunit/tests/block-supports/wpRenderCustomCssSupportStyles.php diff --git a/src/wp-includes/block-editor.php b/src/wp-includes/block-editor.php index 0d9e3c0c1e067..18152756d5b73 100644 --- a/src/wp-includes/block-editor.php +++ b/src/wp-includes/block-editor.php @@ -658,7 +658,7 @@ function get_block_editor_settings( array $custom_settings, $block_editor_contex $editor_settings = apply_filters_deprecated( 'block_editor_settings', array( $editor_settings, $post ), '5.8.0', 'block_editor_settings_all' ); } - $settings['canEditCSS'] = current_user_can( 'edit_css' ); + $editor_settings['canEditCSS'] = current_user_can( 'edit_css' ); return $editor_settings; } diff --git a/tests/phpunit/tests/block-supports/custom-css.php b/tests/phpunit/tests/block-supports/custom-css.php deleted file mode 100644 index b69cc0379559c..0000000000000 --- a/tests/phpunit/tests/block-supports/custom-css.php +++ /dev/null @@ -1,441 +0,0 @@ -test_block_name = null; - } - - public function tear_down() { - if ( $this->test_block_name ) { - unregister_block_type( $this->test_block_name ); - } - $this->test_block_name = null; - parent::tear_down(); - } - - /** - * Registers a new block for testing custom CSS support. - * - * @param string $block_name Name for the test block. - * @param array $supports Array defining block support configuration. - * - * @return WP_Block_Type The block type for the newly registered test block. - */ - private function register_custom_css_block_with_support( $block_name, $supports = array() ) { - $this->test_block_name = $block_name; - register_block_type( - $this->test_block_name, - array( - 'api_version' => 3, - 'attributes' => array( - 'style' => array( - 'type' => 'object', - ), - ), - 'supports' => $supports, - ) - ); - $registry = WP_Block_Type_Registry::get_instance(); - - return $registry->get_registered( $this->test_block_name ); - } - - /** - * Tests that custom CSS support adds class name when block has custom CSS. - * - * @ticket 64544 - */ - public function test_custom_css_support_adds_class_name_when_css_present() { - $this->register_custom_css_block_with_support( - 'test/custom-css-block', - array( 'customCSS' => true ) - ); - - $parsed_block = array( - 'blockName' => 'test/custom-css-block', - 'attrs' => array( - 'style' => array( - 'css' => 'color: red;', - ), - ), - ); - - $result = wp_render_custom_css_support_styles( $parsed_block ); - - $this->assertArrayHasKey( 'className', $result['attrs'], 'Block should have className added.' ); - $this->assertMatchesRegularExpression( '/wp-custom-css-/', $result['attrs']['className'], 'className should contain wp-custom-css- prefix.' ); - } - - /** - * Tests that custom CSS support preserves existing className. - * - * @ticket 64544 - */ - public function test_custom_css_support_preserves_existing_class_name() { - $this->register_custom_css_block_with_support( - 'test/custom-css-block-existing', - array( 'customCSS' => true ) - ); - - $parsed_block = array( - 'blockName' => 'test/custom-css-block-existing', - 'attrs' => array( - 'className' => 'my-existing-class', - 'style' => array( - 'css' => 'color: blue;', - ), - ), - ); - - $result = wp_render_custom_css_support_styles( $parsed_block ); - - $this->assertStringContainsString( 'my-existing-class', $result['attrs']['className'], 'Existing className should be preserved.' ); - $this->assertMatchesRegularExpression( '/wp-custom-css-/', $result['attrs']['className'], 'className should also contain wp-custom-css- prefix.' ); - } - - /** - * Tests that custom CSS support returns unchanged block when support is disabled. - * - * @ticket 64544 - */ - public function test_custom_css_support_returns_unchanged_when_support_disabled() { - $this->register_custom_css_block_with_support( - 'test/custom-css-disabled', - array( 'customCSS' => false ) - ); - - $parsed_block = array( - 'blockName' => 'test/custom-css-disabled', - 'attrs' => array( - 'style' => array( - 'css' => 'color: green;', - ), - ), - ); - - $result = wp_render_custom_css_support_styles( $parsed_block ); - - $this->assertArrayNotHasKey( 'className', $result['attrs'], 'Block should not have className added when support is disabled.' ); - } - - /** - * Tests that custom CSS support returns unchanged block when no CSS attribute present. - * - * @ticket 64544 - */ - public function test_custom_css_support_returns_unchanged_when_no_css() { - $this->register_custom_css_block_with_support( - 'test/custom-css-no-css', - array( 'customCSS' => true ) - ); - - $parsed_block = array( - 'blockName' => 'test/custom-css-no-css', - 'attrs' => array( - 'style' => array( - 'color' => 'red', - ), - ), - ); - - $result = wp_render_custom_css_support_styles( $parsed_block ); - - $this->assertArrayNotHasKey( 'className', $result['attrs'], 'Block should not have className added when no CSS attribute.' ); - } - - /** - * Tests that custom CSS support returns unchanged block when CSS is empty. - * - * @ticket 64544 - */ - public function test_custom_css_support_returns_unchanged_when_css_empty() { - $this->register_custom_css_block_with_support( - 'test/custom-css-empty', - array( 'customCSS' => true ) - ); - - $parsed_block = array( - 'blockName' => 'test/custom-css-empty', - 'attrs' => array( - 'style' => array( - 'css' => '', - ), - ), - ); - - $result = wp_render_custom_css_support_styles( $parsed_block ); - - $this->assertArrayNotHasKey( 'className', $result['attrs'], 'Block should not have className added when CSS is empty.' ); - } - - /** - * Tests that custom CSS support returns unchanged block when CSS is whitespace only. - * - * @ticket 64544 - */ - public function test_custom_css_support_returns_unchanged_when_css_whitespace_only() { - $this->register_custom_css_block_with_support( - 'test/custom-css-whitespace', - array( 'customCSS' => true ) - ); - - $parsed_block = array( - 'blockName' => 'test/custom-css-whitespace', - 'attrs' => array( - 'style' => array( - 'css' => ' ', - ), - ), - ); - - $result = wp_render_custom_css_support_styles( $parsed_block ); - - $this->assertArrayNotHasKey( 'className', $result['attrs'], 'Block should not have className added when CSS is whitespace only.' ); - } - - /** - * Tests that custom CSS support returns unchanged block when style attribute is missing. - * - * @ticket 64544 - */ - public function test_custom_css_support_returns_unchanged_when_no_style_attribute() { - $this->register_custom_css_block_with_support( - 'test/custom-css-no-style', - array( 'customCSS' => true ) - ); - - $parsed_block = array( - 'blockName' => 'test/custom-css-no-style', - 'attrs' => array(), - ); - - $result = wp_render_custom_css_support_styles( $parsed_block ); - - $this->assertArrayNotHasKey( 'className', $result['attrs'], 'Block should not have className added when no style attribute.' ); - } - - /** - * Tests that render_block filter adds custom CSS class to block content. - * - * @@ticket 64544 - */ - public function test_render_custom_css_class_name_adds_class_to_content() { - $block_content = '
Test content
'; - $block = array( - 'blockName' => 'core/paragraph', - 'attrs' => array( - 'className' => 'wp-custom-css-123abc', - ), - ); - - $result = wp_render_custom_css_class_name( $block_content, $block ); - - $this->assertStringContainsString( 'wp-custom-css-123abc', $result, 'Custom CSS class should be added to block content.' ); - } - - /** - * Tests that render_block filter preserves existing classes when adding custom CSS class. - * - * @ticket 64544 - */ - public function test_render_custom_css_class_name_preserves_existing_classes() { - $block_content = '
Test content
'; - $block = array( - 'blockName' => 'core/paragraph', - 'attrs' => array( - 'className' => 'wp-custom-css-456def', - ), - ); - - $result = wp_render_custom_css_class_name( $block_content, $block ); - - $this->assertStringContainsString( 'existing-class', $result, 'Existing classes should be preserved.' ); - $this->assertStringContainsString( 'another-class', $result, 'All existing classes should be preserved.' ); - $this->assertStringContainsString( 'wp-custom-css-456def', $result, 'Custom CSS class should be added.' ); - } - - /** - * Tests that render_block filter returns unchanged content when no custom CSS class in attrs. - * - * @ticket 64544 - */ - public function test_render_custom_css_class_name_returns_unchanged_when_no_custom_css_class() { - $block_content = '
Test content
'; - $block = array( - 'blockName' => 'core/paragraph', - 'attrs' => array( - 'className' => 'some-other-class', - ), - ); - - $result = wp_render_custom_css_class_name( $block_content, $block ); - - $this->assertSame( $block_content, $result, 'Block content should remain unchanged when no custom CSS class.' ); - } - - /** - * Tests that render_block filter returns unchanged content when className is empty. - * - * @ticket 64544 - */ - public function test_render_custom_css_class_name_returns_unchanged_when_classname_empty() { - $block_content = '
Test content
'; - $block = array( - 'blockName' => 'core/paragraph', - 'attrs' => array(), - ); - - $result = wp_render_custom_css_class_name( $block_content, $block ); - - $this->assertSame( $block_content, $result, 'Block content should remain unchanged when className is empty.' ); - } - - /** - * Tests that render_block filter returns empty string when content is empty. - * - * @ticket 64544 - */ - public function test_render_custom_css_class_name_returns_empty_when_content_empty() { - $block_content = ''; - $block = array( - 'blockName' => 'core/paragraph', - 'attrs' => array( - 'className' => 'wp-custom-css-789ghi', - ), - ); - - $result = wp_render_custom_css_class_name( $block_content, $block ); - - $this->assertSame( '', $result, 'Result should be empty when block content is empty.' ); - } - - /** - * Tests that custom CSS class is extracted correctly when mixed with other classes. - * - * @ticket 64544 - */ - public function test_render_custom_css_class_name_extracts_class_from_mixed_classnames() { - $block_content = '

Test content

'; - $block = array( - 'blockName' => 'core/paragraph', - 'attrs' => array( - 'className' => 'my-class wp-custom-css-mixed123 another-class', - ), - ); - - $result = wp_render_custom_css_class_name( $block_content, $block ); - - $this->assertStringContainsString( 'wp-custom-css-mixed123', $result, 'Custom CSS class should be extracted and added.' ); - } - - /** - * Tests that custom CSS support is enabled by default. - * - * @ticket 64544 - */ - public function test_custom_css_support_enabled_by_default() { - $this->register_custom_css_block_with_support( - 'test/custom-css-default', - array() // No explicit customCSS support defined. - ); - - $parsed_block = array( - 'blockName' => 'test/custom-css-default', - 'attrs' => array( - 'style' => array( - 'css' => 'font-weight: bold;', - ), - ), - ); - - $result = wp_render_custom_css_support_styles( $parsed_block ); - - $this->assertArrayHasKey( 'className', $result['attrs'], 'Block should have className added by default when customCSS support is not explicitly set.' ); - } - - /** - * Tests that custom CSS containing HTML opening tags is rejected. - * - * @ticket 64544 - */ - public function test_custom_css_rejects_html_opening_tags() { - $this->register_custom_css_block_with_support( - 'test/custom-css-html-open', - array( 'customCSS' => true ) - ); - - $parsed_block = array( - 'blockName' => 'test/custom-css-html-open', - 'attrs' => array( - 'style' => array( - 'css' => '', - ), - ), - ); - - $result = wp_render_custom_css_support_styles( $parsed_block ); - - $this->assertArrayNotHasKey( 'className', $result['attrs'], 'Block should not have className added when CSS contains HTML opening tags.' ); - } - - /** - * Tests that custom CSS containing HTML closing tags is rejected. - * - * @ticket 64544 - */ - public function test_custom_css_rejects_html_closing_tags() { - $this->register_custom_css_block_with_support( - 'test/custom-css-html-close', - array( 'customCSS' => true ) - ); - - $parsed_block = array( - 'blockName' => 'test/custom-css-html-close', - 'attrs' => array( - 'style' => array( - 'css' => 'color: red;', - ), - ), - ); - - $result = wp_render_custom_css_support_styles( $parsed_block ); - - $this->assertArrayNotHasKey( 'className', $result['attrs'], 'Block should not have className added when CSS contains HTML closing tags.' ); - } - - /** - * Tests that valid CSS without HTML markup is accepted. - * - * @ticket 64544 - */ - public function test_custom_css_accepts_valid_css() { - $this->register_custom_css_block_with_support( - 'test/custom-css-valid', - array( 'customCSS' => true ) - ); - - $parsed_block = array( - 'blockName' => 'test/custom-css-valid', - 'attrs' => array( - 'style' => array( - 'css' => 'color: red; background: url("image.png"); font-size: 16px;', - ), - ), - ); - - $result = wp_render_custom_css_support_styles( $parsed_block ); - - $this->assertArrayHasKey( 'className', $result['attrs'], 'Block should have className added for valid CSS.' ); - } -} diff --git a/tests/phpunit/tests/block-supports/wpRenderCustomCssClassName.php b/tests/phpunit/tests/block-supports/wpRenderCustomCssClassName.php new file mode 100644 index 0000000000000..5faa266ccf747 --- /dev/null +++ b/tests/phpunit/tests/block-supports/wpRenderCustomCssClassName.php @@ -0,0 +1,134 @@ +assertStringContainsString( $expected_class, $result, 'Custom CSS class should be present in the output.' ); + } + + /** + * Data provider. + * + * @return array + */ + public function data_adds_class_to_content() { + return array( + 'class is added to block content' => array( + 'block_content' => '
Test content
', + 'block' => array( + 'blockName' => 'core/paragraph', + 'attrs' => array( + 'className' => 'wp-custom-css-123abc', + ), + ), + 'expected_class' => 'wp-custom-css-123abc', + ), + 'class is extracted from mixed class names' => array( + 'block_content' => '

Test content

', + 'block' => array( + 'blockName' => 'core/paragraph', + 'attrs' => array( + 'className' => 'my-class wp-custom-css-mixed123 another-class', + ), + ), + 'expected_class' => 'wp-custom-css-mixed123', + ), + ); + } + + /** + * Tests that existing classes are preserved when the custom CSS class is added. + * + * @ticket 64544 + * + * @covers ::wp_render_custom_css_class_name + */ + public function test_preserves_existing_classes() { + $block_content = '
Test content
'; + $block = array( + 'blockName' => 'core/paragraph', + 'attrs' => array( + 'className' => 'wp-custom-css-456def', + ), + ); + + $result = wp_render_custom_css_class_name( $block_content, $block ); + + $this->assertStringContainsString( 'existing-class', $result, 'Existing classes should be preserved.' ); + $this->assertStringContainsString( 'another-class', $result, 'All existing classes should be preserved.' ); + $this->assertStringContainsString( 'wp-custom-css-456def', $result, 'Custom CSS class should be added.' ); + } + + /** + * Tests that block content is returned unchanged when no custom CSS class should be applied. + * + * @ticket 64544 + * + * @covers ::wp_render_custom_css_class_name + * + * @dataProvider data_returns_unchanged_content + * + * @param string $block_content The rendered block content. + * @param array $block The block data. + */ + public function test_returns_unchanged_content( $block_content, $block ) { + $result = wp_render_custom_css_class_name( $block_content, $block ); + + $this->assertSame( $block_content, $result, 'Block content should remain unchanged.' ); + } + + /** + * Data provider. + * + * @return array + */ + public function data_returns_unchanged_content() { + return array( + 'no custom CSS class in attrs' => array( + 'block_content' => '
Test content
', + 'block' => array( + 'blockName' => 'core/paragraph', + 'attrs' => array( + 'className' => 'some-other-class', + ), + ), + ), + 'className is not set in attrs' => array( + 'block_content' => '
Test content
', + 'block' => array( + 'blockName' => 'core/paragraph', + 'attrs' => array(), + ), + ), + 'block content is empty' => array( + 'block_content' => '', + 'block' => array( + 'blockName' => 'core/paragraph', + 'attrs' => array( + 'className' => 'wp-custom-css-789ghi', + ), + ), + ), + ); + } +} diff --git a/tests/phpunit/tests/block-supports/wpRenderCustomCssSupportStyles.php b/tests/phpunit/tests/block-supports/wpRenderCustomCssSupportStyles.php new file mode 100644 index 0000000000000..0048b379f8f33 --- /dev/null +++ b/tests/phpunit/tests/block-supports/wpRenderCustomCssSupportStyles.php @@ -0,0 +1,267 @@ +test_block_name = null; + } + + public function tear_down() { + if ( $this->test_block_name ) { + unregister_block_type( $this->test_block_name ); + } + $this->test_block_name = null; + parent::tear_down(); + } + + /** + * Tests that custom CSS support adds a class name when valid CSS is present. + * + * @ticket 64544 + * + * @covers ::wp_render_custom_css_support_styles + * + * @dataProvider data_adds_class_name + * + * @param string $block_name The test block name to register. + * @param array $supports The block support configuration. + * @param array $parsed_block The parsed block data. + */ + public function test_adds_class_name( $block_name, $supports, $parsed_block ) { + $this->test_block_name = $block_name; + register_block_type( + $this->test_block_name, + array( + 'api_version' => 3, + 'attributes' => array( + 'style' => array( + 'type' => 'object', + ), + ), + 'supports' => $supports, + ) + ); + + $result = wp_render_custom_css_support_styles( $parsed_block ); + + $this->assertArrayHasKey( 'className', $result['attrs'], 'Block should have className added.' ); + $this->assertMatchesRegularExpression( '/wp-custom-css-/', $result['attrs']['className'], 'className should contain wp-custom-css- prefix.' ); + } + + /** + * Data provider. + * + * @return array + */ + public function data_adds_class_name() { + return array( + 'class name is added when custom CSS is present' => array( + 'block_name' => 'test/custom-css-block', + 'supports' => array( 'customCSS' => true ), + 'parsed_block' => array( + 'blockName' => 'test/custom-css-block', + 'attrs' => array( + 'style' => array( + 'css' => 'color: red;', + ), + ), + ), + ), + 'class name is added when support is not explicitly set' => array( + 'block_name' => 'test/custom-css-default', + 'supports' => array(), + 'parsed_block' => array( + 'blockName' => 'test/custom-css-default', + 'attrs' => array( + 'style' => array( + 'css' => 'font-weight: bold;', + ), + ), + ), + ), + 'class name is added for valid CSS with url() values' => array( + 'block_name' => 'test/custom-css-valid', + 'supports' => array( 'customCSS' => true ), + 'parsed_block' => array( + 'blockName' => 'test/custom-css-valid', + 'attrs' => array( + 'style' => array( + 'css' => 'color: red; background: url("image.png"); font-size: 16px;', + ), + ), + ), + ), + ); + } + + /** + * Tests that existing className is preserved when custom CSS class is added. + * + * @ticket 64544 + * + * @covers ::wp_render_custom_css_support_styles + */ + public function test_preserves_existing_class_name() { + $this->test_block_name = 'test/custom-css-block-existing'; + register_block_type( + $this->test_block_name, + array( + 'api_version' => 3, + 'attributes' => array( + 'style' => array( + 'type' => 'object', + ), + ), + 'supports' => array( 'customCSS' => true ), + ) + ); + + $parsed_block = array( + 'blockName' => 'test/custom-css-block-existing', + 'attrs' => array( + 'className' => 'my-existing-class', + 'style' => array( + 'css' => 'color: blue;', + ), + ), + ); + + $result = wp_render_custom_css_support_styles( $parsed_block ); + + $this->assertStringContainsString( 'my-existing-class', $result['attrs']['className'], 'Existing className should be preserved.' ); + $this->assertMatchesRegularExpression( '/wp-custom-css-/', $result['attrs']['className'], 'className should also contain wp-custom-css- prefix.' ); + } + + /** + * Tests that custom CSS support does not add a class name when CSS should not be applied. + * + * @ticket 64544 + * + * @covers ::wp_render_custom_css_support_styles + * + * @dataProvider data_does_not_add_class_name + * + * @param string $block_name The test block name to register. + * @param array $supports The block support configuration. + * @param array $parsed_block The parsed block data. + */ + public function test_does_not_add_class_name( $block_name, $supports, $parsed_block ) { + $this->test_block_name = $block_name; + register_block_type( + $this->test_block_name, + array( + 'api_version' => 3, + 'attributes' => array( + 'style' => array( + 'type' => 'object', + ), + ), + 'supports' => $supports, + ) + ); + + $result = wp_render_custom_css_support_styles( $parsed_block ); + + $this->assertArrayNotHasKey( 'className', $result['attrs'], 'Block should not have className added.' ); + } + + /** + * Data provider. + * + * @return array + */ + public function data_does_not_add_class_name() { + return array( + 'support is disabled' => array( + 'block_name' => 'test/custom-css-disabled', + 'supports' => array( 'customCSS' => false ), + 'parsed_block' => array( + 'blockName' => 'test/custom-css-disabled', + 'attrs' => array( + 'style' => array( + 'css' => 'color: green;', + ), + ), + ), + ), + 'no CSS attribute present' => array( + 'block_name' => 'test/custom-css-no-css', + 'supports' => array( 'customCSS' => true ), + 'parsed_block' => array( + 'blockName' => 'test/custom-css-no-css', + 'attrs' => array( + 'style' => array( + 'color' => 'red', + ), + ), + ), + ), + 'CSS is empty' => array( + 'block_name' => 'test/custom-css-empty', + 'supports' => array( 'customCSS' => true ), + 'parsed_block' => array( + 'blockName' => 'test/custom-css-empty', + 'attrs' => array( + 'style' => array( + 'css' => '', + ), + ), + ), + ), + 'CSS is whitespace only' => array( + 'block_name' => 'test/custom-css-whitespace', + 'supports' => array( 'customCSS' => true ), + 'parsed_block' => array( + 'blockName' => 'test/custom-css-whitespace', + 'attrs' => array( + 'style' => array( + 'css' => ' ', + ), + ), + ), + ), + 'no style attribute' => array( + 'block_name' => 'test/custom-css-no-style', + 'supports' => array( 'customCSS' => true ), + 'parsed_block' => array( + 'blockName' => 'test/custom-css-no-style', + 'attrs' => array(), + ), + ), + 'CSS contains HTML opening tags' => array( + 'block_name' => 'test/custom-css-html-open', + 'supports' => array( 'customCSS' => true ), + 'parsed_block' => array( + 'blockName' => 'test/custom-css-html-open', + 'attrs' => array( + 'style' => array( + 'css' => '', + ), + ), + ), + ), + 'CSS contains HTML closing tags' => array( + 'block_name' => 'test/custom-css-html-close', + 'supports' => array( 'customCSS' => true ), + 'parsed_block' => array( + 'blockName' => 'test/custom-css-html-close', + 'attrs' => array( + 'style' => array( + 'css' => 'color: red;', + ), + ), + ), + ), + ); + } +} From 1d72caab8e615dce21f1e662d749b91172cad780 Mon Sep 17 00:00:00 2001 From: Glen Davies Date: Tue, 17 Feb 2026 11:26:56 +1300 Subject: [PATCH 10/12] Fix linting issue --- .../tests/block-supports/wpRenderCustomCssClassName.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/phpunit/tests/block-supports/wpRenderCustomCssClassName.php b/tests/phpunit/tests/block-supports/wpRenderCustomCssClassName.php index 5faa266ccf747..0b13dfb2e2258 100644 --- a/tests/phpunit/tests/block-supports/wpRenderCustomCssClassName.php +++ b/tests/phpunit/tests/block-supports/wpRenderCustomCssClassName.php @@ -33,7 +33,7 @@ public function test_adds_class_to_content( $block_content, $block, $expected_cl */ public function data_adds_class_to_content() { return array( - 'class is added to block content' => array( + 'class is added to block content' => array( 'block_content' => '
Test content
', 'block' => array( 'blockName' => 'core/paragraph', @@ -43,7 +43,7 @@ public function data_adds_class_to_content() { ), 'expected_class' => 'wp-custom-css-123abc', ), - 'class is extracted from mixed class names' => array( + 'class is extracted from mixed class names' => array( 'block_content' => '

Test content

', 'block' => array( 'blockName' => 'core/paragraph', From f21d224d52f2bebad35a16ced5014f951fc0528d Mon Sep 17 00:00:00 2001 From: Glen Davies Date: Tue, 17 Feb 2026 11:32:59 +1300 Subject: [PATCH 11/12] Fix linting issue --- .../phpunit/tests/block-supports/wpRenderCustomCssClassName.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/phpunit/tests/block-supports/wpRenderCustomCssClassName.php b/tests/phpunit/tests/block-supports/wpRenderCustomCssClassName.php index 0b13dfb2e2258..0bcbc6c708468 100644 --- a/tests/phpunit/tests/block-supports/wpRenderCustomCssClassName.php +++ b/tests/phpunit/tests/block-supports/wpRenderCustomCssClassName.php @@ -33,7 +33,7 @@ public function test_adds_class_to_content( $block_content, $block, $expected_cl */ public function data_adds_class_to_content() { return array( - 'class is added to block content' => array( + 'class is added to block content' => array( 'block_content' => '
Test content
', 'block' => array( 'blockName' => 'core/paragraph', From 0648143bfff8939dc13a96b49103f6edb6666d11 Mon Sep 17 00:00:00 2001 From: Glen Davies Date: Tue, 17 Feb 2026 11:50:41 +1300 Subject: [PATCH 12/12] Test fix --- tests/phpunit/tests/blocks/editor.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/phpunit/tests/blocks/editor.php b/tests/phpunit/tests/blocks/editor.php index 4241161388eb8..1de4dbf1719a6 100644 --- a/tests/phpunit/tests/blocks/editor.php +++ b/tests/phpunit/tests/blocks/editor.php @@ -583,7 +583,8 @@ public function test_get_block_editor_settings_deprecated_filter_post_editor() { $this->assertSameSets( array( - 'filter' => 'deprecated', + 'canEditCSS' => false, + 'filter' => 'deprecated', ), $settings );