From 7f1f6a0766e2ed4c6b0901160f76b255285dd85c Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Wed, 13 Aug 2025 15:13:13 +0200 Subject: [PATCH 01/30] Introduce data provider to allow extending test coverage --- tests/phpunit/tests/block-bindings/render.php | 42 ++++++++++++++----- 1 file changed, 31 insertions(+), 11 deletions(-) diff --git a/tests/phpunit/tests/block-bindings/render.php b/tests/phpunit/tests/block-bindings/render.php index 007aeace1f2bf..fdae23e3076b3 100644 --- a/tests/phpunit/tests/block-bindings/render.php +++ b/tests/phpunit/tests/block-bindings/render.php @@ -61,14 +61,30 @@ public static function wpTearDownAfterClass() { unregister_block_type( 'test/block' ); } + public function data_update_block_with_value_from_source() { + return array( + 'paragraph block' => array( + 'content', + << +

This should not appear

+ +HTML, + '

test source value

' + ) + ); + } + /** * Test if the block content is updated with the value returned by the source. * * @ticket 60282 * * @covers ::register_block_bindings_source + * + * @dataProvider data_update_block_with_value_from_source */ - public function test_update_block_with_value_from_source() { + public function test_update_block_with_value_from_source( $bound_attribute, $block_content, $expected_result ) { $get_value_callback = function () { return 'test source value'; }; @@ -81,22 +97,26 @@ public function test_update_block_with_value_from_source() { ) ); - $block_content = << -

This should not appear

- -HTML; $parsed_blocks = parse_blocks( $block_content ); - $block = new WP_Block( $parsed_blocks[0] ); - $result = $block->render(); + + $parsed_blocks[0]['attrs']['metadata'] = array( + 'bindings' => array( + $bound_attribute => array( + 'source' => self::SOURCE_NAME, + ), + ) + ); + + $block = new WP_Block( $parsed_blocks[0] ); + $result = $block->render(); $this->assertSame( 'test source value', - $block->attributes['content'], - "The 'content' attribute should be updated with the value returned by the source." + $block->attributes[ $bound_attribute ], + "The '{$bound_attribute}' attribute should be updated with the value returned by the source." ); $this->assertSame( - '

test source value

', + $expected_result, trim( $result ), 'The block content should be updated with the value returned by the source.' ); From 054756df5d5b1028a81ab7aa31499e59935ef13c Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Wed, 13 Aug 2025 15:27:59 +0200 Subject: [PATCH 02/30] Add test coverage for Button block's text attribute --- src/wp-includes/class-wp-block.php | 2 +- tests/phpunit/tests/block-bindings/render.php | 11 ++++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/wp-includes/class-wp-block.php b/src/wp-includes/class-wp-block.php index e93ab55b75adb..1080c555c629b 100644 --- a/src/wp-includes/class-wp-block.php +++ b/src/wp-includes/class-wp-block.php @@ -429,7 +429,7 @@ private function replace_html( string $block_content, string $attribute_name, $s // Store the parent tag and its attributes to be able to restore them later in the button. // The button block has a wrapper while the paragraph and heading blocks don't. if ( 'core/button' === $this->name ) { - $button_wrapper = $block_reader->get_tag(); + $button_wrapper = strtolower( $block_reader->get_tag() ); $button_wrapper_attribute_names = $block_reader->get_attribute_names_with_prefix( '' ); $button_wrapper_attrs = array(); foreach ( $button_wrapper_attribute_names as $name ) { diff --git a/tests/phpunit/tests/block-bindings/render.php b/tests/phpunit/tests/block-bindings/render.php index fdae23e3076b3..41d92fd42c93c 100644 --- a/tests/phpunit/tests/block-bindings/render.php +++ b/tests/phpunit/tests/block-bindings/render.php @@ -71,7 +71,16 @@ public function data_update_block_with_value_from_source() { HTML, '

test source value

' - ) + ), + 'button block' => array( + 'text', + << + + +HTML, + '' + ), ); } From b328deee4d00e21af89c150d5011d2a4bb3c0024 Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Wed, 13 Aug 2025 13:24:53 +0200 Subject: [PATCH 03/30] Block Bindings: Simplify replace_html() method --- src/wp-includes/class-wp-block.php | 44 ++++-------------------------- 1 file changed, 6 insertions(+), 38 deletions(-) diff --git a/src/wp-includes/class-wp-block.php b/src/wp-includes/class-wp-block.php index 1080c555c629b..e50940d564dac 100644 --- a/src/wp-includes/class-wp-block.php +++ b/src/wp-includes/class-wp-block.php @@ -416,7 +416,7 @@ private function replace_html( string $block_content, string $attribute_name, $s switch ( $block_type->attributes[ $attribute_name ]['source'] ) { case 'html': case 'rich-text': - $block_reader = new WP_HTML_Tag_Processor( $block_content ); + $block_reader = WP_HTML_Processor::create_fragment( $block_content ); // TODO: Support for CSS selectors whenever they are ready in the HTML API. // In the meantime, support comma-separated selectors by exploding them into an array. @@ -425,18 +425,6 @@ private function replace_html( string $block_content, string $attribute_name, $s $block_reader->next_tag(); $block_reader->set_bookmark( 'iterate-selectors' ); - // TODO: This shouldn't be needed when the `set_inner_html` function is ready. - // Store the parent tag and its attributes to be able to restore them later in the button. - // The button block has a wrapper while the paragraph and heading blocks don't. - if ( 'core/button' === $this->name ) { - $button_wrapper = strtolower( $block_reader->get_tag() ); - $button_wrapper_attribute_names = $block_reader->get_attribute_names_with_prefix( '' ); - $button_wrapper_attrs = array(); - foreach ( $button_wrapper_attribute_names as $name ) { - $button_wrapper_attrs[ $name ] = $block_reader->get_attribute( $name ); - } - } - foreach ( $selectors as $selector ) { // If the parent tag, or any of its children, matches the selector, replace the HTML. if ( strcasecmp( $block_reader->get_tag(), $selector ) === 0 || $block_reader->next_tag( @@ -446,32 +434,12 @@ private function replace_html( string $block_content, string $attribute_name, $s ) ) { $block_reader->release_bookmark( 'iterate-selectors' ); - // TODO: Use `set_inner_html` method whenever it's ready in the HTML API. - // Until then, it is hardcoded for the paragraph, heading, and button blocks. - // Store the tag and its attributes to be able to restore them later. - $selector_attribute_names = $block_reader->get_attribute_names_with_prefix( '' ); - $selector_attrs = array(); - foreach ( $selector_attribute_names as $name ) { - $selector_attrs[ $name ] = $block_reader->get_attribute( $name ); - } - $selector_markup = "<$selector>" . wp_kses_post( $source_value ) . ""; - $amended_content = new WP_HTML_Tag_Processor( $selector_markup ); - $amended_content->next_tag(); - foreach ( $selector_attrs as $attribute_key => $attribute_value ) { - $amended_content->set_attribute( $attribute_key, $attribute_value ); - } - if ( 'core/paragraph' === $this->name || 'core/heading' === $this->name ) { - return $amended_content->get_updated_html(); - } - if ( 'core/button' === $this->name ) { - $button_markup = "<$button_wrapper>{$amended_content->get_updated_html()}"; - $amended_button = new WP_HTML_Tag_Processor( $button_markup ); - $amended_button->next_tag(); - foreach ( $button_wrapper_attrs as $attribute_key => $attribute_value ) { - $amended_button->set_attribute( $attribute_key, $attribute_value ); - } - return $amended_button->get_updated_html(); + // TODO: Use `set_inner_html` method whenever it's ready in the HTML API.) + $block_reader->next_token(); + if ( '#text' === $block_reader->get_token_type() ) { + $block_reader->set_modifiable_text( $source_value ); } + return $block_reader->get_updated_html(); } else { $block_reader->seek( 'iterate-selectors' ); } From 21e80e2ea99adc64b69a7ffefcd03cce3219e210 Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Mon, 18 Aug 2025 16:16:57 +0200 Subject: [PATCH 04/30] Add WP_Block_Bindings_Processor class --- .../class-wp-block-bindings-processor.php | 34 +++++++++++++++++++ src/wp-settings.php | 1 + 2 files changed, 35 insertions(+) create mode 100644 src/wp-includes/class-wp-block-bindings-processor.php diff --git a/src/wp-includes/class-wp-block-bindings-processor.php b/src/wp-includes/class-wp-block-bindings-processor.php new file mode 100644 index 0000000000000..c86ed6c5aa6b8 --- /dev/null +++ b/src/wp-includes/class-wp-block-bindings-processor.php @@ -0,0 +1,34 @@ +output . substr( $this->html, $this->end_of_flushed ); + } + + public function replace_rich_text( $rich_text ) { + if ( $this->is_tag_closer() ) { + return false; + } + + $tag_name = $this->get_tag(); + $depth = $this->get_current_depth(); + + $this->set_bookmark( '_wp_block_bindings_tag_opener' ); + // The bookmark names are prefixed with `_` so the key below has an extra `_`. + $bm = $this->bookmarks['__wp_block_bindings_tag_opener']; + $this->output .= substr( $this->html, $this->end_of_flushed, $bm->start + $bm->length ); + $this->output .= $rich_text; + $this->release_bookmark( '_wp_block_bindings_tag_opener' ); + + while ( $this->next_token() && $this->get_current_depth() >= $depth ) { + } + + $this->set_bookmark( '_wp_block_bindings_tag_closer' ); + $bm = $this->bookmarks['__wp_block_bindings_tag_closer']; + $this->end_of_flushed = $bm->start; + $this->release_bookmark( '_wp_block_bindings_tag_closer' ); + } +} diff --git a/src/wp-settings.php b/src/wp-settings.php index 3892b8cd33f91..6bfa853526ae0 100644 --- a/src/wp-settings.php +++ b/src/wp-settings.php @@ -346,6 +346,7 @@ require ABSPATH . WPINC . '/sitemaps/providers/class-wp-sitemaps-posts.php'; require ABSPATH . WPINC . '/sitemaps/providers/class-wp-sitemaps-taxonomies.php'; require ABSPATH . WPINC . '/sitemaps/providers/class-wp-sitemaps-users.php'; +require ABSPATH . WPINC . '/class-wp-block-bindings-processor.php'; require ABSPATH . WPINC . '/class-wp-block-bindings-source.php'; require ABSPATH . WPINC . '/class-wp-block-bindings-registry.php'; require ABSPATH . WPINC . '/class-wp-block-editor-context.php'; From 1c70bc3682c2134633a2f4d7a4f75c5523f59f7b Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Mon, 18 Aug 2025 11:33:25 +0200 Subject: [PATCH 05/30] Use WP_Block_Bindings_Processor for block bindings --- src/wp-includes/class-wp-block.php | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/wp-includes/class-wp-block.php b/src/wp-includes/class-wp-block.php index e50940d564dac..572f13fd7654d 100644 --- a/src/wp-includes/class-wp-block.php +++ b/src/wp-includes/class-wp-block.php @@ -416,7 +416,7 @@ private function replace_html( string $block_content, string $attribute_name, $s switch ( $block_type->attributes[ $attribute_name ]['source'] ) { case 'html': case 'rich-text': - $block_reader = WP_HTML_Processor::create_fragment( $block_content ); + $block_reader = WP_Block_Bindings_Processor::create_fragment( $block_content ); // TODO: Support for CSS selectors whenever they are ready in the HTML API. // In the meantime, support comma-separated selectors by exploding them into an array. @@ -432,14 +432,10 @@ private function replace_html( string $block_content, string $attribute_name, $s 'tag_name' => $selector, ) ) ) { + // TODO: Use `WP_HTML_Processor::set_inner_html` method once it's available. $block_reader->release_bookmark( 'iterate-selectors' ); - - // TODO: Use `set_inner_html` method whenever it's ready in the HTML API.) - $block_reader->next_token(); - if ( '#text' === $block_reader->get_token_type() ) { - $block_reader->set_modifiable_text( $source_value ); - } - return $block_reader->get_updated_html(); + $block_reader->replace_rich_text( $source_value ); + return $block_reader->build(); } else { $block_reader->seek( 'iterate-selectors' ); } From 8f767594157c680591e11037a1c3c2b04c987864 Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Mon, 18 Aug 2025 16:21:25 +0200 Subject: [PATCH 06/30] Add kses back :/ --- src/wp-includes/class-wp-block.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wp-includes/class-wp-block.php b/src/wp-includes/class-wp-block.php index 572f13fd7654d..78f67ce560453 100644 --- a/src/wp-includes/class-wp-block.php +++ b/src/wp-includes/class-wp-block.php @@ -434,7 +434,7 @@ private function replace_html( string $block_content, string $attribute_name, $s ) ) { // TODO: Use `WP_HTML_Processor::set_inner_html` method once it's available. $block_reader->release_bookmark( 'iterate-selectors' ); - $block_reader->replace_rich_text( $source_value ); + $block_reader->replace_rich_text( wp_kses_post( $source_value ) ); return $block_reader->build(); } else { $block_reader->seek( 'iterate-selectors' ); From b7b7ca543b8915ccec687568efebda459f7912d7 Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Mon, 18 Aug 2025 16:22:47 +0200 Subject: [PATCH 07/30] WPCS --- tests/phpunit/tests/block-bindings/render.php | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/tests/phpunit/tests/block-bindings/render.php b/tests/phpunit/tests/block-bindings/render.php index 41d92fd42c93c..60b970cf7943e 100644 --- a/tests/phpunit/tests/block-bindings/render.php +++ b/tests/phpunit/tests/block-bindings/render.php @@ -69,17 +69,19 @@ public function data_update_block_with_value_from_source() {

This should not appear

-HTML, - '

test source value

' +HTML + , + '

test source value

', ), - 'button block' => array( + 'button block' => array( 'text', << -HTML, - '' +HTML + , + '', ), ); } @@ -113,7 +115,7 @@ public function test_update_block_with_value_from_source( $bound_attribute, $blo $bound_attribute => array( 'source' => self::SOURCE_NAME, ), - ) + ), ); $block = new WP_Block( $parsed_blocks[0] ); From bb7c9060811767226ea1da14caae96158ff8b90f Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Mon, 18 Aug 2025 16:24:49 +0200 Subject: [PATCH 08/30] Remove obsolete var --- src/wp-includes/class-wp-block-bindings-processor.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/wp-includes/class-wp-block-bindings-processor.php b/src/wp-includes/class-wp-block-bindings-processor.php index c86ed6c5aa6b8..7c979cd948ecf 100644 --- a/src/wp-includes/class-wp-block-bindings-processor.php +++ b/src/wp-includes/class-wp-block-bindings-processor.php @@ -13,8 +13,7 @@ public function replace_rich_text( $rich_text ) { return false; } - $tag_name = $this->get_tag(); - $depth = $this->get_current_depth(); + $depth = $this->get_current_depth(); $this->set_bookmark( '_wp_block_bindings_tag_opener' ); // The bookmark names are prefixed with `_` so the key below has an extra `_`. @@ -23,6 +22,7 @@ public function replace_rich_text( $rich_text ) { $this->output .= $rich_text; $this->release_bookmark( '_wp_block_bindings_tag_opener' ); + // Find matching tag closer. while ( $this->next_token() && $this->get_current_depth() >= $depth ) { } From 103a5c4ab6b3fa71206390f5451ac24da1fa694e Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Mon, 18 Aug 2025 16:29:16 +0200 Subject: [PATCH 09/30] Indentation --- src/wp-includes/class-wp-block-bindings-processor.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wp-includes/class-wp-block-bindings-processor.php b/src/wp-includes/class-wp-block-bindings-processor.php index 7c979cd948ecf..3309e2daf2407 100644 --- a/src/wp-includes/class-wp-block-bindings-processor.php +++ b/src/wp-includes/class-wp-block-bindings-processor.php @@ -22,7 +22,7 @@ public function replace_rich_text( $rich_text ) { $this->output .= $rich_text; $this->release_bookmark( '_wp_block_bindings_tag_opener' ); - // Find matching tag closer. + // Find matching tag closer. while ( $this->next_token() && $this->get_current_depth() >= $depth ) { } From 3f2e32b9a605cb2c02b67fb81f9c5153b9e3cd7e Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Mon, 18 Aug 2025 17:10:01 +0200 Subject: [PATCH 10/30] Return true upon success --- src/wp-includes/class-wp-block-bindings-processor.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/wp-includes/class-wp-block-bindings-processor.php b/src/wp-includes/class-wp-block-bindings-processor.php index 3309e2daf2407..a20c0a557de6e 100644 --- a/src/wp-includes/class-wp-block-bindings-processor.php +++ b/src/wp-includes/class-wp-block-bindings-processor.php @@ -30,5 +30,7 @@ public function replace_rich_text( $rich_text ) { $bm = $this->bookmarks['__wp_block_bindings_tag_closer']; $this->end_of_flushed = $bm->start; $this->release_bookmark( '_wp_block_bindings_tag_closer' ); + + return true; } } From 85b635419cffd7dbff1d65b74d533fd99106f751 Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Mon, 18 Aug 2025 17:15:47 +0200 Subject: [PATCH 11/30] Add basic PHPDoc --- .../class-wp-block-bindings-processor.php | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/wp-includes/class-wp-block-bindings-processor.php b/src/wp-includes/class-wp-block-bindings-processor.php index a20c0a557de6e..915c72953eeef 100644 --- a/src/wp-includes/class-wp-block-bindings-processor.php +++ b/src/wp-includes/class-wp-block-bindings-processor.php @@ -1,5 +1,12 @@ output . substr( $this->html, $this->end_of_flushed ); } + /** + * Replace the rich text content between a tag opener and matching closer. + * + * When stopped on a tag opener, replace the content enclosed by it and its + * matching closer with the provided rich text. + * + * @param string $rich_text The rich text to replace the original content with. + * @return bool True on success. + */ public function replace_rich_text( $rich_text ) { if ( $this->is_tag_closer() ) { return false; From b67890eaa967c3545214beb3de536e334f2aa78d Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Tue, 19 Aug 2025 13:58:26 +0200 Subject: [PATCH 12/30] Add more PHPDoc --- src/wp-includes/class-wp-block-bindings-processor.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/wp-includes/class-wp-block-bindings-processor.php b/src/wp-includes/class-wp-block-bindings-processor.php index 915c72953eeef..c11bd47f37bdb 100644 --- a/src/wp-includes/class-wp-block-bindings-processor.php +++ b/src/wp-includes/class-wp-block-bindings-processor.php @@ -6,6 +6,12 @@ * This class can be used to perform the sort of structural * changes to an HTML document that are required by * Block Bindings. + * + * @access private + * + * @package WordPress + * @subpackage Block Bindings + * @since 6.9.0 */ class WP_Block_Bindings_Processor extends WP_HTML_Processor { private $output = ''; From 6bc4fd5aabba2d49b775f6bb861246939fb85e10 Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Tue, 19 Aug 2025 14:21:48 +0200 Subject: [PATCH 13/30] Add basic test coverage --- .../wpBlockBindingsProcessor.php | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 tests/phpunit/tests/block-bindings/wpBlockBindingsProcessor.php diff --git a/tests/phpunit/tests/block-bindings/wpBlockBindingsProcessor.php b/tests/phpunit/tests/block-bindings/wpBlockBindingsProcessor.php new file mode 100644 index 0000000000000..218413007db8e --- /dev/null +++ b/tests/phpunit/tests/block-bindings/wpBlockBindingsProcessor.php @@ -0,0 +1,29 @@ +'; + $figure_closer = ''; + $processor = WP_Block_Bindings_Processor::create_fragment( + $figure_opener . + '
Breakfast at a café in Berlin
' . + $figure_closer + ); + $processor->next_tag( array( 'tag_name' => 'figcaption' ) ); + + $this->assertTrue( $processor->replace_rich_text( 'New image caption' ) ); + $this->assertEquals( + $figure_opener . '
New image caption
' . $figure_closer, + $processor->build() + ); + } +} From d943cd8084f47b0053b207e355a9d1a166e5d92b Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Tue, 19 Aug 2025 14:56:04 +0200 Subject: [PATCH 14/30] Allow setting attributes --- .../class-wp-block-bindings-processor.php | 4 ++-- .../block-bindings/wpBlockBindingsProcessor.php | 17 +++++++++++++---- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/wp-includes/class-wp-block-bindings-processor.php b/src/wp-includes/class-wp-block-bindings-processor.php index c11bd47f37bdb..8c3a06c64e8ea 100644 --- a/src/wp-includes/class-wp-block-bindings-processor.php +++ b/src/wp-includes/class-wp-block-bindings-processor.php @@ -18,7 +18,7 @@ class WP_Block_Bindings_Processor extends WP_HTML_Processor { private $end_of_flushed = 0; public function build() { - return $this->output . substr( $this->html, $this->end_of_flushed ); + return $this->output . substr( $this->get_updated_html(), $this->end_of_flushed ); } /** @@ -40,7 +40,7 @@ public function replace_rich_text( $rich_text ) { $this->set_bookmark( '_wp_block_bindings_tag_opener' ); // The bookmark names are prefixed with `_` so the key below has an extra `_`. $bm = $this->bookmarks['__wp_block_bindings_tag_opener']; - $this->output .= substr( $this->html, $this->end_of_flushed, $bm->start + $bm->length ); + $this->output .= substr( $this->get_updated_html(), $this->end_of_flushed, $bm->start + $bm->length ); $this->output .= $rich_text; $this->release_bookmark( '_wp_block_bindings_tag_opener' ); diff --git a/tests/phpunit/tests/block-bindings/wpBlockBindingsProcessor.php b/tests/phpunit/tests/block-bindings/wpBlockBindingsProcessor.php index 218413007db8e..470e9d12387a0 100644 --- a/tests/phpunit/tests/block-bindings/wpBlockBindingsProcessor.php +++ b/tests/phpunit/tests/block-bindings/wpBlockBindingsProcessor.php @@ -10,19 +10,28 @@ * @group block-bindings */ class Tests_Blocks_wpBlockBindingsProcessor extends WP_UnitTestCase { - public function test_replace_rich_text() { - $figure_opener = '
'; + public function test_set_attribute_and_replace_rich_text() { + $figure_opener = '
'; + $img = ''; $figure_closer = '
'; $processor = WP_Block_Bindings_Processor::create_fragment( $figure_opener . + $img . '
Breakfast at a café in Berlin
' . $figure_closer ); + + $processor->next_tag( array( 'tag_name' => 'figure' ) ); + $processor->add_class( 'size-large' ); + $processor->next_tag( array( 'tag_name' => 'figcaption' ) ); - $this->assertTrue( $processor->replace_rich_text( 'New image caption' ) ); + $this->assertTrue( $processor->replace_rich_text( 'New image caption' ) ); $this->assertEquals( - $figure_opener . '
New image caption
' . $figure_closer, + '
' . + $img . + '
New image caption
' . + $figure_closer, $processor->build() ); } From 527c5d8c41b5701d505052f02e491788e6f01973 Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Tue, 19 Aug 2025 16:56:18 +0200 Subject: [PATCH 15/30] Increase test coverage --- .../wpBlockBindingsProcessor.php | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tests/phpunit/tests/block-bindings/wpBlockBindingsProcessor.php b/tests/phpunit/tests/block-bindings/wpBlockBindingsProcessor.php index 470e9d12387a0..c3467556c36dd 100644 --- a/tests/phpunit/tests/block-bindings/wpBlockBindingsProcessor.php +++ b/tests/phpunit/tests/block-bindings/wpBlockBindingsProcessor.php @@ -10,6 +10,27 @@ * @group block-bindings */ class Tests_Blocks_wpBlockBindingsProcessor extends WP_UnitTestCase { + /** + * @ticket 63840 + */ + public function test_replace_rich_text() { + $button_wrapper_opener = ''; + $processor = WP_Block_Bindings_Processor::create_fragment( + $button_wrapper_opener . 'This should not appear' . $button_wrapper_closer + ); + $processor->next_tag( array( 'tag_name' => 'a' ) ); + + $this->assertTrue( $processor->replace_rich_text( 'The hardest button to button' ) ); + $this->assertEquals( + $button_wrapper_opener . 'The hardest button to button' . $button_wrapper_closer, + $processor->build() + ); + } + + /** + * @ticket 63840 + */ public function test_set_attribute_and_replace_rich_text() { $figure_opener = '
'; $img = ''; From 86e836dd67ce2e0bc1fb6f9a656a3daaccb2635c Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Wed, 20 Aug 2025 11:13:26 +0200 Subject: [PATCH 16/30] Increase test coverage --- .../wpBlockBindingsProcessor.php | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/tests/phpunit/tests/block-bindings/wpBlockBindingsProcessor.php b/tests/phpunit/tests/block-bindings/wpBlockBindingsProcessor.php index c3467556c36dd..5a8cf11095847 100644 --- a/tests/phpunit/tests/block-bindings/wpBlockBindingsProcessor.php +++ b/tests/phpunit/tests/block-bindings/wpBlockBindingsProcessor.php @@ -56,4 +56,36 @@ public function test_set_attribute_and_replace_rich_text() { $processor->build() ); } + + /** + * @ticket 63840 + */ + public function test_replace_rich_text_and_seek() { + $figure_opener = '
'; + $img = ''; + $figure_closer = '
'; + $processor = WP_Block_Bindings_Processor::create_fragment( + $figure_opener . + $img . + '
Breakfast at a café in Berlin
' . + $figure_closer + ); + + $processor->next_tag( array( 'tag_name' => 'img' ) ); + $processor->set_bookmark( 'image' ); + + $processor->next_tag( array( 'tag_name' => 'figcaption' ) ); + + $this->assertTrue( $processor->replace_rich_text( 'New image caption' ) ); + + $processor->seek( 'image' ); + + $this->assertEquals( + $figure_opener . + $img . + '
New image caption
' . + $figure_closer, + $processor->build() + ); + } } From dad7380f996d528dca24e1401405642728c2374a Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Wed, 20 Aug 2025 11:13:40 +0200 Subject: [PATCH 17/30] Add more commentary and warnings --- .../class-wp-block-bindings-processor.php | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/wp-includes/class-wp-block-bindings-processor.php b/src/wp-includes/class-wp-block-bindings-processor.php index 8c3a06c64e8ea..f833b9ed07562 100644 --- a/src/wp-includes/class-wp-block-bindings-processor.php +++ b/src/wp-includes/class-wp-block-bindings-processor.php @@ -5,7 +5,19 @@ * * This class can be used to perform the sort of structural * changes to an HTML document that are required by - * Block Bindings. + * Block Bindings. Namely, proper nesting structure of HTML is + * maintained, but HTML updates could still leak out of the + * containing parent node. For example, this allows inserting + * an A element inside an open A element, which would close + * the containing A element. + + * Modifications may be requested for a document _once_ after + * matching a token. Due to the way the modifications are + * applied, it's not possible to replace the rich text content + * for a node more than once. Furthermore, if a `replace_rich_text()` + * operation is followed by a `seek()` to a position before the + * updated rich text content, any modification at that earlier + * position will lead to broken output. * * @access private * From 88af5ffd00f539bd5aa1c44bf2b21447835a96c5 Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Thu, 21 Aug 2025 19:32:21 +0200 Subject: [PATCH 18/30] Do not explose block bindings processor class --- src/wp-includes/class-wp-block.php | 49 +++++++++++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/src/wp-includes/class-wp-block.php b/src/wp-includes/class-wp-block.php index 78f67ce560453..e00de22ddb583 100644 --- a/src/wp-includes/class-wp-block.php +++ b/src/wp-includes/class-wp-block.php @@ -416,7 +416,7 @@ private function replace_html( string $block_content, string $attribute_name, $s switch ( $block_type->attributes[ $attribute_name ]['source'] ) { case 'html': case 'rich-text': - $block_reader = WP_Block_Bindings_Processor::create_fragment( $block_content ); + $block_reader = self::get_block_bindings_processor($block_content); // TODO: Support for CSS selectors whenever they are ready in the HTML API. // In the meantime, support comma-separated selectors by exploding them into an array. @@ -461,6 +461,53 @@ private function replace_html( string $block_content, string $attribute_name, $s } } + private static function get_block_bindings_processor( string $block_content ) { + $internal_processor_class = new class ('', WP_HTML_Processor::CONSTRUCTOR_UNLOCK_CODE) extends WP_HTML_Processor { + private $output = ''; + private $end_of_flushed = 0; + + public function build() { + return $this->output . substr( $this->get_updated_html(), $this->end_of_flushed ); + } + + /** + * Replace the rich text content between a tag opener and matching closer. + * + * When stopped on a tag opener, replace the content enclosed by it and its + * matching closer with the provided rich text. + * + * @param string $rich_text The rich text to replace the original content with. + * @return bool True on success. + */ + public function replace_rich_text( $rich_text ) { + if ( $this->is_tag_closer() ) { + return false; + } + + $depth = $this->get_current_depth(); + + $this->set_bookmark( '_wp_block_bindings_tag_opener' ); + // The bookmark names are prefixed with `_` so the key below has an extra `_`. + $bm = $this->bookmarks['__wp_block_bindings_tag_opener']; + $this->output .= substr( $this->get_updated_html(), $this->end_of_flushed, $bm->start + $bm->length ); + $this->output .= $rich_text; + $this->release_bookmark( '_wp_block_bindings_tag_opener' ); + + // Find matching tag closer. + while ( $this->next_token() && $this->get_current_depth() >= $depth ) { + } + + $this->set_bookmark( '_wp_block_bindings_tag_closer' ); + $bm = $this->bookmarks['__wp_block_bindings_tag_closer']; + $this->end_of_flushed = $bm->start; + $this->release_bookmark( '_wp_block_bindings_tag_closer' ); + + return true; + } + }; + + return $internal_processor_class::create_fragment( $block_content ); + } /** * Generates the render output for the block. From a56f978cb89be8fc07518e659a471ccdfb6afd1c Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Thu, 21 Aug 2025 19:32:33 +0200 Subject: [PATCH 19/30] Remove block bindings processor class --- .../class-wp-block-bindings-processor.php | 70 ------------------- src/wp-settings.php | 1 - 2 files changed, 71 deletions(-) delete mode 100644 src/wp-includes/class-wp-block-bindings-processor.php diff --git a/src/wp-includes/class-wp-block-bindings-processor.php b/src/wp-includes/class-wp-block-bindings-processor.php deleted file mode 100644 index f833b9ed07562..0000000000000 --- a/src/wp-includes/class-wp-block-bindings-processor.php +++ /dev/null @@ -1,70 +0,0 @@ -output . substr( $this->get_updated_html(), $this->end_of_flushed ); - } - - /** - * Replace the rich text content between a tag opener and matching closer. - * - * When stopped on a tag opener, replace the content enclosed by it and its - * matching closer with the provided rich text. - * - * @param string $rich_text The rich text to replace the original content with. - * @return bool True on success. - */ - public function replace_rich_text( $rich_text ) { - if ( $this->is_tag_closer() ) { - return false; - } - - $depth = $this->get_current_depth(); - - $this->set_bookmark( '_wp_block_bindings_tag_opener' ); - // The bookmark names are prefixed with `_` so the key below has an extra `_`. - $bm = $this->bookmarks['__wp_block_bindings_tag_opener']; - $this->output .= substr( $this->get_updated_html(), $this->end_of_flushed, $bm->start + $bm->length ); - $this->output .= $rich_text; - $this->release_bookmark( '_wp_block_bindings_tag_opener' ); - - // Find matching tag closer. - while ( $this->next_token() && $this->get_current_depth() >= $depth ) { - } - - $this->set_bookmark( '_wp_block_bindings_tag_closer' ); - $bm = $this->bookmarks['__wp_block_bindings_tag_closer']; - $this->end_of_flushed = $bm->start; - $this->release_bookmark( '_wp_block_bindings_tag_closer' ); - - return true; - } -} diff --git a/src/wp-settings.php b/src/wp-settings.php index 6bfa853526ae0..3892b8cd33f91 100644 --- a/src/wp-settings.php +++ b/src/wp-settings.php @@ -346,7 +346,6 @@ require ABSPATH . WPINC . '/sitemaps/providers/class-wp-sitemaps-posts.php'; require ABSPATH . WPINC . '/sitemaps/providers/class-wp-sitemaps-taxonomies.php'; require ABSPATH . WPINC . '/sitemaps/providers/class-wp-sitemaps-users.php'; -require ABSPATH . WPINC . '/class-wp-block-bindings-processor.php'; require ABSPATH . WPINC . '/class-wp-block-bindings-source.php'; require ABSPATH . WPINC . '/class-wp-block-bindings-registry.php'; require ABSPATH . WPINC . '/class-wp-block-editor-context.php'; From 7c3fc45844b8ca66596f87bd97b78d099faf078e Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Thu, 21 Aug 2025 19:35:36 +0200 Subject: [PATCH 20/30] wpcs --- src/wp-includes/class-wp-block.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/wp-includes/class-wp-block.php b/src/wp-includes/class-wp-block.php index e00de22ddb583..8fa06aa5d8682 100644 --- a/src/wp-includes/class-wp-block.php +++ b/src/wp-includes/class-wp-block.php @@ -416,7 +416,7 @@ private function replace_html( string $block_content, string $attribute_name, $s switch ( $block_type->attributes[ $attribute_name ]['source'] ) { case 'html': case 'rich-text': - $block_reader = self::get_block_bindings_processor($block_content); + $block_reader = self::get_block_bindings_processor( $block_content ); // TODO: Support for CSS selectors whenever they are ready in the HTML API. // In the meantime, support comma-separated selectors by exploding them into an array. @@ -462,7 +462,7 @@ private function replace_html( string $block_content, string $attribute_name, $s } private static function get_block_bindings_processor( string $block_content ) { - $internal_processor_class = new class ('', WP_HTML_Processor::CONSTRUCTOR_UNLOCK_CODE) extends WP_HTML_Processor { + $internal_processor_class = new class('', WP_HTML_Processor::CONSTRUCTOR_UNLOCK_CODE) extends WP_HTML_Processor { private $output = ''; private $end_of_flushed = 0; From 66fef38310d7c3076e9460c937a08079696d6883 Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Thu, 21 Aug 2025 19:36:46 +0200 Subject: [PATCH 21/30] Make the hidden class instance static --- src/wp-includes/class-wp-block.php | 77 ++++++++++++++++-------------- 1 file changed, 40 insertions(+), 37 deletions(-) diff --git a/src/wp-includes/class-wp-block.php b/src/wp-includes/class-wp-block.php index 8fa06aa5d8682..24976ab0ed823 100644 --- a/src/wp-includes/class-wp-block.php +++ b/src/wp-includes/class-wp-block.php @@ -462,49 +462,52 @@ private function replace_html( string $block_content, string $attribute_name, $s } private static function get_block_bindings_processor( string $block_content ) { - $internal_processor_class = new class('', WP_HTML_Processor::CONSTRUCTOR_UNLOCK_CODE) extends WP_HTML_Processor { - private $output = ''; - private $end_of_flushed = 0; - - public function build() { - return $this->output . substr( $this->get_updated_html(), $this->end_of_flushed ); - } - - /** - * Replace the rich text content between a tag opener and matching closer. - * - * When stopped on a tag opener, replace the content enclosed by it and its - * matching closer with the provided rich text. - * - * @param string $rich_text The rich text to replace the original content with. - * @return bool True on success. - */ - public function replace_rich_text( $rich_text ) { - if ( $this->is_tag_closer() ) { - return false; + static $internal_processor_class = null; + if ( null === $internal_processor_class ) { + $internal_processor_class = new class('', WP_HTML_Processor::CONSTRUCTOR_UNLOCK_CODE) extends WP_HTML_Processor { + private $output = ''; + private $end_of_flushed = 0; + + public function build() { + return $this->output . substr( $this->get_updated_html(), $this->end_of_flushed ); } - $depth = $this->get_current_depth(); + /** + * Replace the rich text content between a tag opener and matching closer. + * + * When stopped on a tag opener, replace the content enclosed by it and its + * matching closer with the provided rich text. + * + * @param string $rich_text The rich text to replace the original content with. + * @return bool True on success. + */ + public function replace_rich_text( $rich_text ) { + if ( $this->is_tag_closer() ) { + return false; + } - $this->set_bookmark( '_wp_block_bindings_tag_opener' ); - // The bookmark names are prefixed with `_` so the key below has an extra `_`. - $bm = $this->bookmarks['__wp_block_bindings_tag_opener']; - $this->output .= substr( $this->get_updated_html(), $this->end_of_flushed, $bm->start + $bm->length ); - $this->output .= $rich_text; - $this->release_bookmark( '_wp_block_bindings_tag_opener' ); + $depth = $this->get_current_depth(); - // Find matching tag closer. - while ( $this->next_token() && $this->get_current_depth() >= $depth ) { - } + $this->set_bookmark( '_wp_block_bindings_tag_opener' ); + // The bookmark names are prefixed with `_` so the key below has an extra `_`. + $bm = $this->bookmarks['__wp_block_bindings_tag_opener']; + $this->output .= substr( $this->get_updated_html(), $this->end_of_flushed, $bm->start + $bm->length ); + $this->output .= $rich_text; + $this->release_bookmark( '_wp_block_bindings_tag_opener' ); - $this->set_bookmark( '_wp_block_bindings_tag_closer' ); - $bm = $this->bookmarks['__wp_block_bindings_tag_closer']; - $this->end_of_flushed = $bm->start; - $this->release_bookmark( '_wp_block_bindings_tag_closer' ); + // Find matching tag closer. + while ( $this->next_token() && $this->get_current_depth() >= $depth ) { + } - return true; - } - }; + $this->set_bookmark( '_wp_block_bindings_tag_closer' ); + $bm = $this->bookmarks['__wp_block_bindings_tag_closer']; + $this->end_of_flushed = $bm->start; + $this->release_bookmark( '_wp_block_bindings_tag_closer' ); + + return true; + } + }; + } return $internal_processor_class::create_fragment( $block_content ); } From 24a0b0d3114c4767e2b0bd790b5926743b355f01 Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Mon, 25 Aug 2025 14:38:50 +0200 Subject: [PATCH 22/30] Use Reflection to make tests work again --- .../wpBlockBindingsProcessor.php | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/tests/phpunit/tests/block-bindings/wpBlockBindingsProcessor.php b/tests/phpunit/tests/block-bindings/wpBlockBindingsProcessor.php index 5a8cf11095847..046fa32f2cd52 100644 --- a/tests/phpunit/tests/block-bindings/wpBlockBindingsProcessor.php +++ b/tests/phpunit/tests/block-bindings/wpBlockBindingsProcessor.php @@ -10,13 +10,23 @@ * @group block-bindings */ class Tests_Blocks_wpBlockBindingsProcessor extends WP_UnitTestCase { + + private static $get_block_bindings_processor_method; + + public static function wpSetupBeforeClass() { + self::$get_block_bindings_processor_method = new ReflectionMethod( 'WP_Block', 'get_block_bindings_processor' ); + self::$get_block_bindings_processor_method->setAccessible( true ); + } + /** * @ticket 63840 */ public function test_replace_rich_text() { $button_wrapper_opener = ''; - $processor = WP_Block_Bindings_Processor::create_fragment( + + $processor = self::$get_block_bindings_processor_method->invoke( + null, $button_wrapper_opener . 'This should not appear' . $button_wrapper_closer ); $processor->next_tag( array( 'tag_name' => 'a' ) ); @@ -35,7 +45,8 @@ public function test_set_attribute_and_replace_rich_text() { $figure_opener = '
'; $img = ''; $figure_closer = '
'; - $processor = WP_Block_Bindings_Processor::create_fragment( + $processor = self::$get_block_bindings_processor_method->invoke( + null, $figure_opener . $img . '
Breakfast at a café in Berlin
' . @@ -64,7 +75,8 @@ public function test_replace_rich_text_and_seek() { $figure_opener = '
'; $img = ''; $figure_closer = '
'; - $processor = WP_Block_Bindings_Processor::create_fragment( + $processor = self::$get_block_bindings_processor_method->invoke( + null, $figure_opener . $img . '
Breakfast at a café in Berlin
' . From 2e7df737d60b1e879560ffb8c60e66d304d6b7bd Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Mon, 25 Aug 2025 14:43:55 +0200 Subject: [PATCH 23/30] Block Bindings Processor: Tweak test to break with current implementation --- .../phpunit/tests/block-bindings/wpBlockBindingsProcessor.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/phpunit/tests/block-bindings/wpBlockBindingsProcessor.php b/tests/phpunit/tests/block-bindings/wpBlockBindingsProcessor.php index 046fa32f2cd52..8e8f368cca84c 100644 --- a/tests/phpunit/tests/block-bindings/wpBlockBindingsProcessor.php +++ b/tests/phpunit/tests/block-bindings/wpBlockBindingsProcessor.php @@ -91,10 +91,11 @@ public function test_replace_rich_text_and_seek() { $this->assertTrue( $processor->replace_rich_text( 'New image caption' ) ); $processor->seek( 'image' ); + $processor->add_class( 'extra-img-class' ); $this->assertEquals( $figure_opener . - $img . + '' . '
New image caption
' . $figure_closer, $processor->build() From e2f0a381504a4fc213f2cd1630fadd9c6e8d71e7 Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Mon, 25 Aug 2025 14:51:17 +0200 Subject: [PATCH 24/30] Block Bindings Processor: Base implementation on WP_HTML_Text_Replacement to fix broken test --- src/wp-includes/class-wp-block.php | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/wp-includes/class-wp-block.php b/src/wp-includes/class-wp-block.php index 24976ab0ed823..572380c98a18c 100644 --- a/src/wp-includes/class-wp-block.php +++ b/src/wp-includes/class-wp-block.php @@ -465,11 +465,9 @@ private static function get_block_bindings_processor( string $block_content ) { static $internal_processor_class = null; if ( null === $internal_processor_class ) { $internal_processor_class = new class('', WP_HTML_Processor::CONSTRUCTOR_UNLOCK_CODE) extends WP_HTML_Processor { - private $output = ''; - private $end_of_flushed = 0; public function build() { - return $this->output . substr( $this->get_updated_html(), $this->end_of_flushed ); + return $this->get_updated_html(); } /** @@ -490,9 +488,8 @@ public function replace_rich_text( $rich_text ) { $this->set_bookmark( '_wp_block_bindings_tag_opener' ); // The bookmark names are prefixed with `_` so the key below has an extra `_`. - $bm = $this->bookmarks['__wp_block_bindings_tag_opener']; - $this->output .= substr( $this->get_updated_html(), $this->end_of_flushed, $bm->start + $bm->length ); - $this->output .= $rich_text; + $bm = $this->bookmarks['__wp_block_bindings_tag_opener']; + $start = $bm->start + $bm->length; $this->release_bookmark( '_wp_block_bindings_tag_opener' ); // Find matching tag closer. @@ -500,10 +497,16 @@ public function replace_rich_text( $rich_text ) { } $this->set_bookmark( '_wp_block_bindings_tag_closer' ); - $bm = $this->bookmarks['__wp_block_bindings_tag_closer']; - $this->end_of_flushed = $bm->start; + $bm = $this->bookmarks['__wp_block_bindings_tag_closer']; + $end = $bm->start; $this->release_bookmark( '_wp_block_bindings_tag_closer' ); + $this->lexical_updates[] = new WP_HTML_Text_Replacement( + $start, + $end - $start, + $rich_text + ); + return true; } }; From b0450506716e0d20f64ee8e0702e8f0018e6d2ed Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Mon, 25 Aug 2025 14:57:52 +0200 Subject: [PATCH 25/30] Remove now-obsolete build() alias method --- src/wp-includes/class-wp-block.php | 7 +------ .../tests/block-bindings/wpBlockBindingsProcessor.php | 6 +++--- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/src/wp-includes/class-wp-block.php b/src/wp-includes/class-wp-block.php index 572380c98a18c..cd772401e6575 100644 --- a/src/wp-includes/class-wp-block.php +++ b/src/wp-includes/class-wp-block.php @@ -435,7 +435,7 @@ private function replace_html( string $block_content, string $attribute_name, $s // TODO: Use `WP_HTML_Processor::set_inner_html` method once it's available. $block_reader->release_bookmark( 'iterate-selectors' ); $block_reader->replace_rich_text( wp_kses_post( $source_value ) ); - return $block_reader->build(); + return $block_reader->get_updated_html(); } else { $block_reader->seek( 'iterate-selectors' ); } @@ -465,11 +465,6 @@ private static function get_block_bindings_processor( string $block_content ) { static $internal_processor_class = null; if ( null === $internal_processor_class ) { $internal_processor_class = new class('', WP_HTML_Processor::CONSTRUCTOR_UNLOCK_CODE) extends WP_HTML_Processor { - - public function build() { - return $this->get_updated_html(); - } - /** * Replace the rich text content between a tag opener and matching closer. * diff --git a/tests/phpunit/tests/block-bindings/wpBlockBindingsProcessor.php b/tests/phpunit/tests/block-bindings/wpBlockBindingsProcessor.php index 8e8f368cca84c..394224b6e09e5 100644 --- a/tests/phpunit/tests/block-bindings/wpBlockBindingsProcessor.php +++ b/tests/phpunit/tests/block-bindings/wpBlockBindingsProcessor.php @@ -34,7 +34,7 @@ public function test_replace_rich_text() { $this->assertTrue( $processor->replace_rich_text( 'The hardest button to button' ) ); $this->assertEquals( $button_wrapper_opener . 'The hardest button to button' . $button_wrapper_closer, - $processor->build() + $processor->get_updated_html() ); } @@ -64,7 +64,7 @@ public function test_set_attribute_and_replace_rich_text() { $img . '
New image caption
' . $figure_closer, - $processor->build() + $processor->get_updated_html() ); } @@ -98,7 +98,7 @@ public function test_replace_rich_text_and_seek() { '' . '
New image caption
' . $figure_closer, - $processor->build() + $processor->get_updated_html() ); } } From e739c6ccba2eac35dd5b2bc1d5b0baa7d485ced6 Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Mon, 25 Aug 2025 15:14:49 +0200 Subject: [PATCH 26/30] Rename test file --- ...rocessor.php => wp-block-get-block-bindings-processor.php} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename tests/phpunit/tests/block-bindings/{wpBlockBindingsProcessor.php => wp-block-get-block-bindings-processor.php} (96%) diff --git a/tests/phpunit/tests/block-bindings/wpBlockBindingsProcessor.php b/tests/phpunit/tests/block-bindings/wp-block-get-block-bindings-processor.php similarity index 96% rename from tests/phpunit/tests/block-bindings/wpBlockBindingsProcessor.php rename to tests/phpunit/tests/block-bindings/wp-block-get-block-bindings-processor.php index 394224b6e09e5..e617774140797 100644 --- a/tests/phpunit/tests/block-bindings/wpBlockBindingsProcessor.php +++ b/tests/phpunit/tests/block-bindings/wp-block-get-block-bindings-processor.php @@ -1,6 +1,6 @@ Date: Mon, 25 Aug 2025 15:17:13 +0200 Subject: [PATCH 27/30] Correct @since PHPDoc --- .../block-bindings/wp-block-get-block-bindings-processor.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/phpunit/tests/block-bindings/wp-block-get-block-bindings-processor.php b/tests/phpunit/tests/block-bindings/wp-block-get-block-bindings-processor.php index e617774140797..b199e10b0e92f 100644 --- a/tests/phpunit/tests/block-bindings/wp-block-get-block-bindings-processor.php +++ b/tests/phpunit/tests/block-bindings/wp-block-get-block-bindings-processor.php @@ -4,7 +4,7 @@ * * @package WordPress * @subpackage Blocks - * @since 6.5.0 + * @since 6.9.0 * * @group blocks * @group block-bindings From 4dbb0e5024f32f2ed7cc3774e6c992736cd60f9d Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Tue, 26 Aug 2025 16:54:48 +0200 Subject: [PATCH 28/30] More descriptive variable names --- src/wp-includes/class-wp-block.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/wp-includes/class-wp-block.php b/src/wp-includes/class-wp-block.php index cd772401e6575..e2104b1e06305 100644 --- a/src/wp-includes/class-wp-block.php +++ b/src/wp-includes/class-wp-block.php @@ -483,8 +483,8 @@ public function replace_rich_text( $rich_text ) { $this->set_bookmark( '_wp_block_bindings_tag_opener' ); // The bookmark names are prefixed with `_` so the key below has an extra `_`. - $bm = $this->bookmarks['__wp_block_bindings_tag_opener']; - $start = $bm->start + $bm->length; + $tag_opener = $this->bookmarks['__wp_block_bindings_tag_opener']; + $start = $tag_opener->start + $tag_opener->length; $this->release_bookmark( '_wp_block_bindings_tag_opener' ); // Find matching tag closer. @@ -492,8 +492,8 @@ public function replace_rich_text( $rich_text ) { } $this->set_bookmark( '_wp_block_bindings_tag_closer' ); - $bm = $this->bookmarks['__wp_block_bindings_tag_closer']; - $end = $bm->start; + $tag_closer = $this->bookmarks['__wp_block_bindings_tag_closer']; + $end = $tag_closer->start; $this->release_bookmark( '_wp_block_bindings_tag_closer' ); $this->lexical_updates[] = new WP_HTML_Text_Replacement( From a18e435b2af10a73aa5c7e86eb11dfbc2ea9396c Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Tue, 26 Aug 2025 20:21:08 +0200 Subject: [PATCH 29/30] Remove static var --- src/wp-includes/class-wp-block.php | 73 ++++++++++++++---------------- 1 file changed, 35 insertions(+), 38 deletions(-) diff --git a/src/wp-includes/class-wp-block.php b/src/wp-includes/class-wp-block.php index e2104b1e06305..8972a99832d32 100644 --- a/src/wp-includes/class-wp-block.php +++ b/src/wp-includes/class-wp-block.php @@ -462,50 +462,47 @@ private function replace_html( string $block_content, string $attribute_name, $s } private static function get_block_bindings_processor( string $block_content ) { - static $internal_processor_class = null; - if ( null === $internal_processor_class ) { - $internal_processor_class = new class('', WP_HTML_Processor::CONSTRUCTOR_UNLOCK_CODE) extends WP_HTML_Processor { - /** - * Replace the rich text content between a tag opener and matching closer. - * - * When stopped on a tag opener, replace the content enclosed by it and its - * matching closer with the provided rich text. - * - * @param string $rich_text The rich text to replace the original content with. - * @return bool True on success. - */ - public function replace_rich_text( $rich_text ) { - if ( $this->is_tag_closer() ) { - return false; - } + $internal_processor_class = new class('', WP_HTML_Processor::CONSTRUCTOR_UNLOCK_CODE) extends WP_HTML_Processor { + /** + * Replace the rich text content between a tag opener and matching closer. + * + * When stopped on a tag opener, replace the content enclosed by it and its + * matching closer with the provided rich text. + * + * @param string $rich_text The rich text to replace the original content with. + * @return bool True on success. + */ + public function replace_rich_text( $rich_text ) { + if ( $this->is_tag_closer() ) { + return false; + } - $depth = $this->get_current_depth(); + $depth = $this->get_current_depth(); - $this->set_bookmark( '_wp_block_bindings_tag_opener' ); - // The bookmark names are prefixed with `_` so the key below has an extra `_`. - $tag_opener = $this->bookmarks['__wp_block_bindings_tag_opener']; - $start = $tag_opener->start + $tag_opener->length; - $this->release_bookmark( '_wp_block_bindings_tag_opener' ); + $this->set_bookmark( '_wp_block_bindings_tag_opener' ); + // The bookmark names are prefixed with `_` so the key below has an extra `_`. + $tag_opener = $this->bookmarks['__wp_block_bindings_tag_opener']; + $start = $tag_opener->start + $tag_opener->length; + $this->release_bookmark( '_wp_block_bindings_tag_opener' ); - // Find matching tag closer. - while ( $this->next_token() && $this->get_current_depth() >= $depth ) { - } + // Find matching tag closer. + while ( $this->next_token() && $this->get_current_depth() >= $depth ) { + } - $this->set_bookmark( '_wp_block_bindings_tag_closer' ); - $tag_closer = $this->bookmarks['__wp_block_bindings_tag_closer']; - $end = $tag_closer->start; - $this->release_bookmark( '_wp_block_bindings_tag_closer' ); + $this->set_bookmark( '_wp_block_bindings_tag_closer' ); + $tag_closer = $this->bookmarks['__wp_block_bindings_tag_closer']; + $end = $tag_closer->start; + $this->release_bookmark( '_wp_block_bindings_tag_closer' ); - $this->lexical_updates[] = new WP_HTML_Text_Replacement( - $start, - $end - $start, - $rich_text - ); + $this->lexical_updates[] = new WP_HTML_Text_Replacement( + $start, + $end - $start, + $rich_text + ); - return true; - } - }; - } + return true; + } + }; return $internal_processor_class::create_fragment( $block_content ); } From 3410f40dc72eec46f18695bb2da177572ef632c1 Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Tue, 26 Aug 2025 20:29:55 +0200 Subject: [PATCH 30/30] Make sure we're not stopped on atomic/void/self-closing element --- src/wp-includes/class-wp-block.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wp-includes/class-wp-block.php b/src/wp-includes/class-wp-block.php index 8972a99832d32..e2b75f66431f8 100644 --- a/src/wp-includes/class-wp-block.php +++ b/src/wp-includes/class-wp-block.php @@ -473,7 +473,7 @@ private static function get_block_bindings_processor( string $block_content ) { * @return bool True on success. */ public function replace_rich_text( $rich_text ) { - if ( $this->is_tag_closer() ) { + if ( $this->is_tag_closer() || ! $this->expects_closer() ) { return false; }