From 82f224c4f4931f9eeafd85df94db63c1a30abbe6 Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Wed, 20 Aug 2025 12:02:18 +0200 Subject: [PATCH 1/4] Block Bindings: Support image caption attribute --- src/wp-includes/class-wp-block.php | 2 +- tests/phpunit/tests/block-bindings/render.php | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/wp-includes/class-wp-block.php b/src/wp-includes/class-wp-block.php index e2b75f66431f8..30647b5acc76a 100644 --- a/src/wp-includes/class-wp-block.php +++ b/src/wp-includes/class-wp-block.php @@ -109,7 +109,7 @@ class WP_Block { private const BLOCK_BINDINGS_SUPPORTED_ATTRIBUTES = array( 'core/paragraph' => array( 'content' ), 'core/heading' => array( 'content' ), - 'core/image' => array( 'id', 'url', 'title', 'alt' ), + 'core/image' => array( 'id', 'url', 'title', 'alt', 'caption' ), 'core/button' => array( 'url', 'text', 'linkTarget', 'rel' ), 'core/post-date' => array( 'datetime' ), ); diff --git a/tests/phpunit/tests/block-bindings/render.php b/tests/phpunit/tests/block-bindings/render.php index 60b970cf7943e..178c5c17bab83 100644 --- a/tests/phpunit/tests/block-bindings/render.php +++ b/tests/phpunit/tests/block-bindings/render.php @@ -83,6 +83,16 @@ public function data_update_block_with_value_from_source() { , '
test source value
', ), + 'image block' => array( + 'caption', + << +
Breakfast at a café in Wrocław.
+ +HTML + , + '
test source value
' + ) ); } From 0f7ea0abdfc9ee78a9d9def482337d02afc18d39 Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Tue, 2 Sep 2025 11:05:35 +0200 Subject: [PATCH 2/4] Block Bindings Processor: Add remove_node() method --- src/wp-includes/class-wp-block.php | 31 +++++++++++++++++++ .../wp-block-get-block-bindings-processor.php | 22 +++++++++++++ 2 files changed, 53 insertions(+) diff --git a/src/wp-includes/class-wp-block.php b/src/wp-includes/class-wp-block.php index 30647b5acc76a..e9350987a8198 100644 --- a/src/wp-includes/class-wp-block.php +++ b/src/wp-includes/class-wp-block.php @@ -502,6 +502,37 @@ public function replace_rich_text( $rich_text ) { return true; } + + public function remove_node() { + 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 `_`. + $tag_opener = $this->bookmarks['__wp_block_bindings_tag_opener']; + $start = $tag_opener->start; + $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' ); + $tag_closer = $this->bookmarks['__wp_block_bindings_tag_closer']; + $end = $tag_closer->start + $tag_closer->length; + $this->release_bookmark( '_wp_block_bindings_tag_closer' ); + + $this->lexical_updates[] = new WP_HTML_Text_Replacement( + $start, + $end - $start, + '' + ); + + return true; + } }; return $internal_processor_class::create_fragment( $block_content ); 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 b199e10b0e92f..c0bbdde2ee42e 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 @@ -101,4 +101,26 @@ public function test_replace_rich_text_and_seek() { $processor->get_updated_html() ); } + + public function test_remove_node() { + $figure_opener = '
'; + $img = ''; + $figure_closer = '
'; + $processor = self::$get_block_bindings_processor_method->invoke( + null, + $figure_opener . + $img . + '
Breakfast at a café in Berlin
' . + $figure_closer + ); + + $processor->next_tag( array( 'tag_name' => 'figcaption' ) ); + + $this->assertTrue( $processor->remove_node() ); + + $this->assertEquals( + $figure_opener . $img . $figure_closer, + $processor->get_updated_html() + ); + } } From 4e6a8b77b5c31be8a792048017ccebd3e847fd89 Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Wed, 20 Aug 2025 12:37:46 +0200 Subject: [PATCH 3/4] Block Bindings: Remove empty nodes if rich-text attribute is empty --- src/wp-includes/class-wp-block.php | 13 +++++- tests/phpunit/tests/block-bindings/render.php | 44 +++++++++++++++++++ 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/src/wp-includes/class-wp-block.php b/src/wp-includes/class-wp-block.php index e9350987a8198..67e3d257bb671 100644 --- a/src/wp-includes/class-wp-block.php +++ b/src/wp-includes/class-wp-block.php @@ -434,7 +434,18 @@ 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 ) ); + /* + * If the value returned from the Block Bindings source is empty + * for a block attribute that whose selector is `rich-text` or `html`, + * we remove the HTML node denoted by its selector. For example, this + * means removing an Image block's `
` node if there's no + * caption supplied. + */ + if ( empty( $source_value ) ) { + $block_reader->remove_node(); + } else { + $block_reader->replace_rich_text( wp_kses_post( $source_value ) ); + } return $block_reader->get_updated_html(); } else { $block_reader->seek( 'iterate-selectors' ); diff --git a/tests/phpunit/tests/block-bindings/render.php b/tests/phpunit/tests/block-bindings/render.php index 178c5c17bab83..bca33d73dc1e3 100644 --- a/tests/phpunit/tests/block-bindings/render.php +++ b/tests/phpunit/tests/block-bindings/render.php @@ -143,6 +143,50 @@ public function test_update_block_with_value_from_source( $bound_attribute, $blo ); } + public function test_remove_containing_tag_if_rich_text_attribute_source_value_is_empty() { + $get_value_callback = function () { + return ''; + }; + + register_block_bindings_source( + self::SOURCE_NAME, + array( + 'label' => self::SOURCE_LABEL, + 'get_value_callback' => $get_value_callback, + ) + ); + + $block_content = << +
Breakfast at a café in Wrocław.
+ +HTML; + $parsed_blocks = parse_blocks( $block_content ); + + $parsed_blocks[0]['attrs']['metadata'] = array( + 'bindings' => array( + 'caption' => array( + 'source' => self::SOURCE_NAME, + ), + ), + ); + + $block = new WP_Block( $parsed_blocks[0] ); + $result = $block->render(); + + $this->assertSame( + '', + $block->attributes[ 'caption' ], + "The 'caption' attribute should be updated with the empty string value returned by the source." + ); + $this->assertSame( + '
', + trim( $result ), + 'The
and its rich text content should be removed.' + ); + } + + /** * Test if the block_bindings_supported_attributes_{$block_type} filter is applied correctly. * From e2560f118f6e29449c92fcf152ce191daa998ed0 Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Tue, 2 Sep 2025 11:16:14 +0200 Subject: [PATCH 4/4] WPCS --- tests/phpunit/tests/block-bindings/render.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/phpunit/tests/block-bindings/render.php b/tests/phpunit/tests/block-bindings/render.php index bca33d73dc1e3..79483086c8d22 100644 --- a/tests/phpunit/tests/block-bindings/render.php +++ b/tests/phpunit/tests/block-bindings/render.php @@ -83,7 +83,7 @@ public function data_update_block_with_value_from_source() { , '', ), - 'image block' => array( + 'image block' => array( 'caption', << @@ -91,8 +91,8 @@ public function data_update_block_with_value_from_source() { HTML , - '
test source value
' - ) + '
test source value
', + ), ); } @@ -176,7 +176,7 @@ public function test_remove_containing_tag_if_rich_text_attribute_source_value_i $this->assertSame( '', - $block->attributes[ 'caption' ], + $block->attributes['caption'], "The 'caption' attribute should be updated with the empty string value returned by the source." ); $this->assertSame(