From 654621960d1ee1356fb4b81248577cf81222752e Mon Sep 17 00:00:00 2001 From: Glen Davies Date: Wed, 25 Mar 2026 09:53:48 +1300 Subject: [PATCH 1/7] Block Supports: Strip custom CSS from blocks for users without edit_css capability Add capability-gated CSS stripping so that when a user without `edit_css` saves a post, any `style.css` attributes are surgically removed from block comments using `WP_Block_Parser::next_token()`. Props TODO. See #64771. --- src/wp-includes/block-supports/custom-css.php | 147 ++++++++++++++++++ .../wpStripCustomCssFromBlocks.php | 129 +++++++++++++++ 2 files changed, 276 insertions(+) create mode 100644 tests/phpunit/tests/block-supports/wpStripCustomCssFromBlocks.php diff --git a/src/wp-includes/block-supports/custom-css.php b/src/wp-includes/block-supports/custom-css.php index 9d5b13426f4ef..b5d977113c863 100644 --- a/src/wp-includes/block-supports/custom-css.php +++ b/src/wp-includes/block-supports/custom-css.php @@ -124,6 +124,153 @@ function wp_register_custom_css_support( $block_type ) { } } +/** + * Strips `style.css` attributes from all blocks in post content. + * + * Uses WP_Block_Parser::next_token() to scan block tokens and surgically + * replace only the attribute JSON that changed — no parse_blocks() + + * serialize_blocks() round-trip needed. + * + * @since 7.0.0 + * @access private + * + * @param string $content Post content to filter, expected to be escaped with slashes. + * @return string Filtered post content with block custom CSS removed. + */ +function wp_strip_custom_css_from_blocks( $content ) { + if ( ! has_blocks( $content ) ) { + return $content; + } + + $unslashed = stripslashes( $content ); + + $parser = new WP_Block_Parser(); + $parser->document = $unslashed; + $parser->offset = 0; + $end = strlen( $unslashed ); + $replacements = array(); + + while ( $parser->offset < $end ) { + $next_token = $parser->next_token(); + list( $token_type, , $attrs, $start_offset, $token_length ) = $next_token; + + if ( 'no-more-tokens' === $token_type ) { + break; + } + + $parser->offset = $start_offset + $token_length; + + if ( 'block-opener' !== $token_type && 'void-block' !== $token_type ) { + continue; + } + + if ( ! isset( $attrs['style']['css'] ) ) { + continue; + } + + // Remove css and clean up empty style. + unset( $attrs['style']['css'] ); + if ( empty( $attrs['style'] ) ) { + unset( $attrs['style'] ); + } + + // Locate the JSON portion within the token. + $token_string = substr( $unslashed, $start_offset, $token_length ); + $json_rel_start = strcspn( $token_string, '{' ); + $json_rel_end = strrpos( $token_string, '}' ); + + $json_start = $start_offset + $json_rel_start; + $json_length = $json_rel_end - $json_rel_start + 1; + + // Re-encode attributes. If attrs is now empty, remove JSON and trailing space. + if ( empty( $attrs ) ) { + // Remove the trailing space after JSON. + $replacements[] = array( $json_start, $json_length + 1, '' ); + } else { + $replacements[] = array( $json_start, $json_length, serialize_block_attributes( $attrs ) ); + } + } + + if ( empty( $replacements ) ) { + return $content; + } + + // Build the result by splicing replacements into the original string. + $result = ''; + $was_at = 0; + + foreach ( $replacements as $replacement ) { + list( $offset, $length, $new_json ) = $replacement; + $result .= substr( $unslashed, $was_at, $offset - $was_at ) . $new_json; + $was_at = $offset + $length; + } + + if ( $was_at < $end ) { + $result .= substr( $unslashed, $was_at ); + } + + return addslashes( $result ); +} + +/** + * Adds the filters to strip custom CSS from block content on save. + * + * @since 7.0.0 + * @access private + */ +function wp_custom_css_kses_init_filters() { + add_filter( 'content_save_pre', 'wp_strip_custom_css_from_blocks', 8 ); + add_filter( 'content_filtered_save_pre', 'wp_strip_custom_css_from_blocks', 8 ); +} + +/** + * Removes the filters that strip custom CSS from block content on save. + * + * @since 7.0.0 + * @access private + */ +function wp_custom_css_remove_filters() { + remove_filter( 'content_save_pre', 'wp_strip_custom_css_from_blocks', 8 ); + remove_filter( 'content_filtered_save_pre', 'wp_strip_custom_css_from_blocks', 8 ); +} + +/** + * Registers the custom CSS content filters if the user does not have the edit_css capability. + * + * @since 7.0.0 + * @access private + */ +function wp_custom_css_kses_init() { + wp_custom_css_remove_filters(); + if ( ! current_user_can( 'edit_css' ) ) { + wp_custom_css_kses_init_filters(); + } +} + +/** + * Initializes custom CSS content filters when imported data should be filtered. + * + * This filter is the last being executed on force_filtered_html_on_import. + * If the input of the filter is true it means we are in an import situation and should + * enable the custom CSS filters, independently of the user capabilities. + * + * @since 7.0.0 + * @access private + * + * @param mixed $arg Input argument of the filter. + * @return mixed Input argument of the filter. + */ +function wp_custom_css_force_filtered_html_on_import_filter( $arg ) { + if ( $arg ) { + wp_custom_css_kses_init_filters(); + } + return $arg; +} + +add_action( 'init', 'wp_custom_css_kses_init', 20 ); +add_action( 'set_current_user', 'wp_custom_css_kses_init' ); +add_filter( 'force_filtered_html_on_import', 'wp_custom_css_force_filtered_html_on_import_filter', 999 ); + // Register the block support. WP_Block_Supports::get_instance()->register( 'custom-css', diff --git a/tests/phpunit/tests/block-supports/wpStripCustomCssFromBlocks.php b/tests/phpunit/tests/block-supports/wpStripCustomCssFromBlocks.php new file mode 100644 index 0000000000000..e850428b110cc --- /dev/null +++ b/tests/phpunit/tests/block-supports/wpStripCustomCssFromBlocks.php @@ -0,0 +1,129 @@ +assertArrayNotHasKey( 'css', $blocks[0]['attrs']['style'] ?? array(), $message ); + } + + /** + * Data provider. + * + * @return array + */ + public function data_strips_css_from_blocks() { + return array( + 'single block' => array( + 'content' => '

Hello

', + 'message' => 'style.css should be stripped from block attributes.', + ), + 'empty style object is cleaned up' => array( + 'content' => '

Hello

', + 'message' => 'style.css should be stripped from block attributes.', + ), + ); + } + + /** + * Tests that style.css is stripped from nested inner blocks. + * + * @ticket 63 + */ + public function test_strips_css_from_inner_blocks() { + $content = '

Hello

'; + + $result = wp_unslash( wp_strip_custom_css_from_blocks( $content ) ); + $blocks = parse_blocks( $result ); + + $inner_block = $blocks[0]['innerBlocks'][0]; + $this->assertArrayNotHasKey( 'css', $inner_block['attrs']['style'] ?? array(), 'style.css should be stripped from inner block attributes.' ); + } + + /** + * Tests that content without blocks is returned unchanged. + * + * @ticket 63 + */ + public function test_returns_non_block_content_unchanged() { + $content = '

This is plain HTML content with no blocks.

'; + + $result = wp_strip_custom_css_from_blocks( $content ); + + $this->assertSame( $content, $result, 'Non-block content should be returned unchanged.' ); + } + + /** + * Tests that content without style.css attributes is returned unchanged. + * + * @ticket 63 + */ + public function test_returns_unchanged_when_no_css_attributes() { + $content = '

Hello

'; + + $result = wp_strip_custom_css_from_blocks( $content ); + + $this->assertSame( $content, $result, 'Content without style.css attributes should be returned unchanged.' ); + } + + /** + * Tests that other style properties are preserved when css is stripped. + * + * @ticket 63 + */ + public function test_preserves_other_style_properties() { + $content = '

Hello

'; + + $result = wp_unslash( wp_strip_custom_css_from_blocks( $content ) ); + $blocks = parse_blocks( $result ); + + $this->assertArrayNotHasKey( 'css', $blocks[0]['attrs']['style'], 'style.css should be stripped.' ); + $this->assertSame( '#ff0000', $blocks[0]['attrs']['style']['color']['text'], 'Other style properties should be preserved.' ); + } + + /** + * Tests that empty style object is cleaned up after stripping css. + * + * @ticket 63 + */ + public function test_cleans_up_empty_style_object() { + $content = '

Hello

'; + + $result = wp_unslash( wp_strip_custom_css_from_blocks( $content ) ); + $blocks = parse_blocks( $result ); + + $this->assertArrayNotHasKey( 'style', $blocks[0]['attrs'], 'Empty style object should be cleaned up after stripping css.' ); + } + + /** + * Tests that slashed content is handled correctly. + * + * @ticket 63 + */ + public function test_handles_slashed_content() { + $content = '

Hello

'; + $slashed = wp_slash( $content ); + + $result = wp_strip_custom_css_from_blocks( $slashed ); + $blocks = parse_blocks( wp_unslash( $result ) ); + + $this->assertArrayNotHasKey( 'css', $blocks[0]['attrs']['style'] ?? array(), 'style.css should be stripped even from slashed content.' ); + } +} From 867a13af4188bdf5bac33910741c8a2b6efe6366 Mon Sep 17 00:00:00 2001 From: Glen Davies Date: Tue, 31 Mar 2026 11:55:32 +1300 Subject: [PATCH 2/7] Fix @ticket annotations to use correct ticket number 64771 --- .../block-supports/wpStripCustomCssFromBlocks.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/phpunit/tests/block-supports/wpStripCustomCssFromBlocks.php b/tests/phpunit/tests/block-supports/wpStripCustomCssFromBlocks.php index e850428b110cc..3d31843469642 100644 --- a/tests/phpunit/tests/block-supports/wpStripCustomCssFromBlocks.php +++ b/tests/phpunit/tests/block-supports/wpStripCustomCssFromBlocks.php @@ -10,7 +10,7 @@ class Tests_Block_Supports_WpStripCustomCssFromBlocks extends WP_UnitTestCase { /** * Tests that style.css is stripped from block attributes. * - * @ticket 63 + * @ticket 64771 * * @dataProvider data_strips_css_from_blocks * @@ -45,7 +45,7 @@ public function data_strips_css_from_blocks() { /** * Tests that style.css is stripped from nested inner blocks. * - * @ticket 63 + * @ticket 64771 */ public function test_strips_css_from_inner_blocks() { $content = '

Hello

'; @@ -60,7 +60,7 @@ public function test_strips_css_from_inner_blocks() { /** * Tests that content without blocks is returned unchanged. * - * @ticket 63 + * @ticket 64771 */ public function test_returns_non_block_content_unchanged() { $content = '

This is plain HTML content with no blocks.

'; @@ -73,7 +73,7 @@ public function test_returns_non_block_content_unchanged() { /** * Tests that content without style.css attributes is returned unchanged. * - * @ticket 63 + * @ticket 64771 */ public function test_returns_unchanged_when_no_css_attributes() { $content = '

Hello

'; @@ -86,7 +86,7 @@ public function test_returns_unchanged_when_no_css_attributes() { /** * Tests that other style properties are preserved when css is stripped. * - * @ticket 63 + * @ticket 64771 */ public function test_preserves_other_style_properties() { $content = '

Hello

'; @@ -101,7 +101,7 @@ public function test_preserves_other_style_properties() { /** * Tests that empty style object is cleaned up after stripping css. * - * @ticket 63 + * @ticket 64771 */ public function test_cleans_up_empty_style_object() { $content = '

Hello

'; @@ -115,7 +115,7 @@ public function test_cleans_up_empty_style_object() { /** * Tests that slashed content is handled correctly. * - * @ticket 63 + * @ticket 64771 */ public function test_handles_slashed_content() { $content = '

Hello

'; From 29cffe9168df55e71c0fe2ec7a3fb79f0a417525 Mon Sep 17 00:00:00 2001 From: Ramon Date: Fri, 17 Apr 2026 14:52:33 +1000 Subject: [PATCH 3/7] Apply suggestions from code review Co-authored-by: Ramon --- .../tests/block-supports/wpStripCustomCssFromBlocks.php | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tests/phpunit/tests/block-supports/wpStripCustomCssFromBlocks.php b/tests/phpunit/tests/block-supports/wpStripCustomCssFromBlocks.php index 3d31843469642..f64afcb306988 100644 --- a/tests/phpunit/tests/block-supports/wpStripCustomCssFromBlocks.php +++ b/tests/phpunit/tests/block-supports/wpStripCustomCssFromBlocks.php @@ -22,6 +22,7 @@ public function test_strips_css_from_blocks( $content, $message ) { $blocks = parse_blocks( $result ); $this->assertArrayNotHasKey( 'css', $blocks[0]['attrs']['style'] ?? array(), $message ); + $this->assertArrayNotHasKey( 'style', $blocks[0]['attrs'] ?? array(), 'style key should be fully removed when css was the only property.' ); } /** @@ -35,10 +36,6 @@ public function data_strips_css_from_blocks() { 'content' => '

Hello

', 'message' => 'style.css should be stripped from block attributes.', ), - 'empty style object is cleaned up' => array( - 'content' => '

Hello

', - 'message' => 'style.css should be stripped from block attributes.', - ), ); } From 664d4d0df50815963c7f0d1b5e34c20ffdbb7ecb Mon Sep 17 00:00:00 2001 From: Ramon Date: Fri, 17 Apr 2026 14:56:55 +1000 Subject: [PATCH 4/7] Apply suggestions from code review Co-authored-by: Ramon --- .../phpunit/tests/block-supports/wpStripCustomCssFromBlocks.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/phpunit/tests/block-supports/wpStripCustomCssFromBlocks.php b/tests/phpunit/tests/block-supports/wpStripCustomCssFromBlocks.php index f64afcb306988..38cd21c0ff0ee 100644 --- a/tests/phpunit/tests/block-supports/wpStripCustomCssFromBlocks.php +++ b/tests/phpunit/tests/block-supports/wpStripCustomCssFromBlocks.php @@ -32,7 +32,7 @@ public function test_strips_css_from_blocks( $content, $message ) { */ public function data_strips_css_from_blocks() { return array( - 'single block' => array( + 'single block' => array( 'content' => '

Hello

', 'message' => 'style.css should be stripped from block attributes.', ), From b300792c6fabca9e9a0a602fb7c2e670706a7bfb Mon Sep 17 00:00:00 2001 From: Ramon Date: Fri, 17 Apr 2026 15:35:33 +1000 Subject: [PATCH 5/7] Tests: Add missing @covers tags for wp_strip_custom_css_from_blocks and enhance filter tests --- .../wpStripCustomCssFromBlocks.php | 92 ++++++++++++++++++- 1 file changed, 91 insertions(+), 1 deletion(-) diff --git a/tests/phpunit/tests/block-supports/wpStripCustomCssFromBlocks.php b/tests/phpunit/tests/block-supports/wpStripCustomCssFromBlocks.php index 38cd21c0ff0ee..ce2838064152b 100644 --- a/tests/phpunit/tests/block-supports/wpStripCustomCssFromBlocks.php +++ b/tests/phpunit/tests/block-supports/wpStripCustomCssFromBlocks.php @@ -3,7 +3,6 @@ /** * @group block-supports * - * @covers ::wp_strip_custom_css_from_blocks */ class Tests_Block_Supports_WpStripCustomCssFromBlocks extends WP_UnitTestCase { @@ -12,6 +11,7 @@ class Tests_Block_Supports_WpStripCustomCssFromBlocks extends WP_UnitTestCase { * * @ticket 64771 * + * @covers ::wp_strip_custom_css_from_blocks * @dataProvider data_strips_css_from_blocks * * @param string $content Post content containing blocks. @@ -42,6 +42,7 @@ public function data_strips_css_from_blocks() { /** * Tests that style.css is stripped from nested inner blocks. * + * @covers ::wp_strip_custom_css_from_blocks * @ticket 64771 */ public function test_strips_css_from_inner_blocks() { @@ -57,6 +58,7 @@ public function test_strips_css_from_inner_blocks() { /** * Tests that content without blocks is returned unchanged. * + * @covers ::wp_strip_custom_css_from_blocks * @ticket 64771 */ public function test_returns_non_block_content_unchanged() { @@ -70,6 +72,7 @@ public function test_returns_non_block_content_unchanged() { /** * Tests that content without style.css attributes is returned unchanged. * + * @covers ::wp_strip_custom_css_from_blocks * @ticket 64771 */ public function test_returns_unchanged_when_no_css_attributes() { @@ -83,6 +86,7 @@ public function test_returns_unchanged_when_no_css_attributes() { /** * Tests that other style properties are preserved when css is stripped. * + * @covers ::wp_strip_custom_css_from_blocks * @ticket 64771 */ public function test_preserves_other_style_properties() { @@ -98,6 +102,7 @@ public function test_preserves_other_style_properties() { /** * Tests that empty style object is cleaned up after stripping css. * + * @covers ::wp_strip_custom_css_from_blocks * @ticket 64771 */ public function test_cleans_up_empty_style_object() { @@ -112,6 +117,7 @@ public function test_cleans_up_empty_style_object() { /** * Tests that slashed content is handled correctly. * + * @covers ::wp_strip_custom_css_from_blocks * @ticket 64771 */ public function test_handles_slashed_content() { @@ -123,4 +129,88 @@ public function test_handles_slashed_content() { $this->assertArrayNotHasKey( 'css', $blocks[0]['attrs']['style'] ?? array(), 'style.css should be stripped even from slashed content.' ); } + + /** + * Tests that the content_save_pre filter is added for a user without edit_css. + * + * @ticket 64771 + * + * @covers ::wp_custom_css_kses_init + * @covers ::wp_custom_css_kses_init_filters + */ + public function test_filter_added_for_user_without_edit_css() { + $author_id = self::factory()->user->create( array( 'role' => 'author' ) ); + wp_set_current_user( $author_id ); + wp_custom_css_kses_init(); + + $this->assertSame( 8, has_filter( 'content_save_pre', 'wp_strip_custom_css_from_blocks' ), 'content_save_pre filter should be added at priority 8 for users without edit_css.' ); + $this->assertSame( 8, has_filter( 'content_filtered_save_pre', 'wp_strip_custom_css_from_blocks' ), 'content_filtered_save_pre filter should be added at priority 8 for users without edit_css.' ); + + wp_set_current_user( 0 ); + wp_custom_css_remove_filters(); + } + + /** + * Tests that the content_save_pre filter is not added for a user with edit_css. + * + * @ticket 64771 + * + * @covers ::wp_custom_css_kses_init + * @covers ::wp_custom_css_remove_filters + */ + public function test_filter_not_added_for_user_with_edit_css() { + $admin_id = self::factory()->user->create( array( 'role' => 'administrator' ) ); + wp_set_current_user( $admin_id ); + wp_custom_css_kses_init(); + + $this->assertFalse( has_filter( 'content_save_pre', 'wp_strip_custom_css_from_blocks' ), 'content_save_pre filter should not be added for users with edit_css.' ); + $this->assertFalse( has_filter( 'content_filtered_save_pre', 'wp_strip_custom_css_from_blocks' ), 'content_filtered_save_pre filter should not be added for users with edit_css.' ); + + wp_set_current_user( 0 ); + } + + /** + * Tests that switching to a user with edit_css removes the filter via the set_current_user action. + * + * wp_custom_css_kses_init() is hooked to set_current_user, so wp_set_current_user() + * alone should update the filter state without a manual call. + * + * @ticket 64771 + * + * @covers ::wp_custom_css_kses_init + */ + public function test_set_current_user_action_triggers_reinit() { + $admin_id = self::factory()->user->create( array( 'role' => 'administrator' ) ); + $author_id = self::factory()->user->create( array( 'role' => 'author' ) ); + + wp_set_current_user( $author_id ); + $this->assertNotFalse( has_filter( 'content_save_pre', 'wp_strip_custom_css_from_blocks' ), 'Filter should be active for user without edit_css.' ); + + wp_set_current_user( $admin_id ); + $this->assertFalse( has_filter( 'content_save_pre', 'wp_strip_custom_css_from_blocks' ), 'Filter should be removed after switching to a user with edit_css.' ); + + wp_set_current_user( 0 ); + } + + /** + * Tests that the filter is enabled during import regardless of user capability. + * + * @ticket 64771 + * + * @covers ::wp_custom_css_force_filtered_html_on_import_filter + */ + public function test_force_filtered_html_on_import_enables_filter_for_privileged_user() { + $admin_id = self::factory()->user->create( array( 'role' => 'administrator' ) ); + wp_set_current_user( $admin_id ); + wp_custom_css_kses_init(); + + $this->assertFalse( has_filter( 'content_save_pre', 'wp_strip_custom_css_from_blocks' ), 'Filter should not be active for admin before import.' ); + + apply_filters( 'force_filtered_html_on_import', true ); + + $this->assertNotFalse( has_filter( 'content_save_pre', 'wp_strip_custom_css_from_blocks' ), 'Filter should be enabled during import regardless of user capability.' ); + + wp_set_current_user( 0 ); + wp_custom_css_remove_filters(); + } } From cf6859c8019f9de0940547e1fed45c92e37e126f Mon Sep 17 00:00:00 2001 From: Ramon Date: Fri, 17 Apr 2026 15:58:09 +1000 Subject: [PATCH 6/7] Added checks for multisite environments in the tests --- .../wpStripCustomCssFromBlocks.php | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/phpunit/tests/block-supports/wpStripCustomCssFromBlocks.php b/tests/phpunit/tests/block-supports/wpStripCustomCssFromBlocks.php index ce2838064152b..1b6076ee185a4 100644 --- a/tests/phpunit/tests/block-supports/wpStripCustomCssFromBlocks.php +++ b/tests/phpunit/tests/block-supports/wpStripCustomCssFromBlocks.php @@ -160,13 +160,20 @@ public function test_filter_added_for_user_without_edit_css() { */ public function test_filter_not_added_for_user_with_edit_css() { $admin_id = self::factory()->user->create( array( 'role' => 'administrator' ) ); + if ( is_multisite() ) { + grant_super_admin( $admin_id ); + } wp_set_current_user( $admin_id ); wp_custom_css_kses_init(); $this->assertFalse( has_filter( 'content_save_pre', 'wp_strip_custom_css_from_blocks' ), 'content_save_pre filter should not be added for users with edit_css.' ); $this->assertFalse( has_filter( 'content_filtered_save_pre', 'wp_strip_custom_css_from_blocks' ), 'content_filtered_save_pre filter should not be added for users with edit_css.' ); + if ( is_multisite() ) { + revoke_super_admin( $admin_id ); + } wp_set_current_user( 0 ); + wp_custom_css_remove_filters(); } /** @@ -182,14 +189,23 @@ public function test_filter_not_added_for_user_with_edit_css() { public function test_set_current_user_action_triggers_reinit() { $admin_id = self::factory()->user->create( array( 'role' => 'administrator' ) ); $author_id = self::factory()->user->create( array( 'role' => 'author' ) ); + if ( is_multisite() ) { + grant_super_admin( $admin_id ); + } + // Switching to a user without edit_css should add the filter via the set_current_user action. wp_set_current_user( $author_id ); $this->assertNotFalse( has_filter( 'content_save_pre', 'wp_strip_custom_css_from_blocks' ), 'Filter should be active for user without edit_css.' ); + // Switching to a user with edit_css should remove the filter via the set_current_user action. wp_set_current_user( $admin_id ); $this->assertFalse( has_filter( 'content_save_pre', 'wp_strip_custom_css_from_blocks' ), 'Filter should be removed after switching to a user with edit_css.' ); + if ( is_multisite() ) { + revoke_super_admin( $admin_id ); + } wp_set_current_user( 0 ); + wp_custom_css_remove_filters(); } /** @@ -201,6 +217,9 @@ public function test_set_current_user_action_triggers_reinit() { */ public function test_force_filtered_html_on_import_enables_filter_for_privileged_user() { $admin_id = self::factory()->user->create( array( 'role' => 'administrator' ) ); + if ( is_multisite() ) { + grant_super_admin( $admin_id ); + } wp_set_current_user( $admin_id ); wp_custom_css_kses_init(); @@ -210,6 +229,9 @@ public function test_force_filtered_html_on_import_enables_filter_for_privileged $this->assertNotFalse( has_filter( 'content_save_pre', 'wp_strip_custom_css_from_blocks' ), 'Filter should be enabled during import regardless of user capability.' ); + if ( is_multisite() ) { + revoke_super_admin( $admin_id ); + } wp_set_current_user( 0 ); wp_custom_css_remove_filters(); } From 8e3ebe65a711685b73da18161d339bd5aaa72572 Mon Sep 17 00:00:00 2001 From: Ramon Date: Sun, 19 Apr 2026 14:55:14 +1000 Subject: [PATCH 7/7] Updated documentation to use proper reference syntax for `WP_Block_Parser::next_token()`. Improved logic in `wp_strip_custom_css_from_blocks` to access token data directly from the `$next_token` array. Adjusted filter priorities in `wp_custom_css_kses_init_filters` and `wp_custom_css_force_filtered_html_on_import_filter` to ensure correct execution order during content processing. --- src/wp-includes/block-supports/custom-css.php | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/wp-includes/block-supports/custom-css.php b/src/wp-includes/block-supports/custom-css.php index b5d977113c863..b931acf47bc02 100644 --- a/src/wp-includes/block-supports/custom-css.php +++ b/src/wp-includes/block-supports/custom-css.php @@ -127,7 +127,7 @@ function wp_register_custom_css_support( $block_type ) { /** * Strips `style.css` attributes from all blocks in post content. * - * Uses WP_Block_Parser::next_token() to scan block tokens and surgically + * Uses {@see WP_Block_Parser::next_token()} to scan block tokens and surgically * replace only the attribute JSON that changed — no parse_blocks() + * serialize_blocks() round-trip needed. * @@ -152,12 +152,13 @@ function wp_strip_custom_css_from_blocks( $content ) { while ( $parser->offset < $end ) { $next_token = $parser->next_token(); - list( $token_type, , $attrs, $start_offset, $token_length ) = $next_token; - if ( 'no-more-tokens' === $token_type ) { + if ( 'no-more-tokens' === $next_token[0] ) { break; } + list( $token_type, , $attrs, $start_offset, $token_length ) = $next_token; + $parser->offset = $start_offset + $token_length; if ( 'block-opener' !== $token_type && 'void-block' !== $token_type ) { @@ -214,6 +215,7 @@ function wp_strip_custom_css_from_blocks( $content ) { /** * Adds the filters to strip custom CSS from block content on save. + * Priority of 8 to run before wp_filter_global_styles_post (priority 9) and wp_filter_post_kses (priority 10). * * @since 7.0.0 * @access private @@ -225,6 +227,7 @@ function wp_custom_css_kses_init_filters() { /** * Removes the filters that strip custom CSS from block content on save. + * Priority of 8 to run before wp_filter_global_styles_post (priority 9) and wp_filter_post_kses (priority 10). * * @since 7.0.0 * @access private @@ -250,7 +253,8 @@ function wp_custom_css_kses_init() { /** * Initializes custom CSS content filters when imported data should be filtered. * - * This filter is the last being executed on force_filtered_html_on_import. + * Runs at priority 999 on {@see 'force_filtered_html_on_import'} to ensure it + * fires after general KSES initialization, independently of user capabilities. * If the input of the filter is true it means we are in an import situation and should * enable the custom CSS filters, independently of the user capabilities. * @@ -267,6 +271,7 @@ function wp_custom_css_force_filtered_html_on_import_filter( $arg ) { return $arg; } +// Run before wp_filter_global_styles_post (priority 9) and wp_filter_post_kses (priority 10). add_action( 'init', 'wp_custom_css_kses_init', 20 ); add_action( 'set_current_user', 'wp_custom_css_kses_init' ); add_filter( 'force_filtered_html_on_import', 'wp_custom_css_force_filtered_html_on_import_filter', 999 );