diff --git a/backport-changelog/6.9/9469.md b/backport-changelog/6.9/9469.md
index 26fa84c5dd18fe..5880ac5d4d013a 100644
--- a/backport-changelog/6.9/9469.md
+++ b/backport-changelog/6.9/9469.md
@@ -1,3 +1,4 @@
https://github.com/WordPress/wordpress-develop/pull/9469
* https://github.com/WordPress/gutenberg/pull/71389
+* https://github.com/WordPress/gutenberg/pull/71691
diff --git a/lib/compat/wordpress-6.9/block-bindings.php b/lib/compat/wordpress-6.9/block-bindings.php
index 403c3b1f3db24d..245b962757f4bf 100644
--- a/lib/compat/wordpress-6.9/block-bindings.php
+++ b/lib/compat/wordpress-6.9/block-bindings.php
@@ -36,26 +36,39 @@ function gutenberg_block_bindings_render_block( $block_content, $block, $instanc
return $block_content;
}
- $attributes = $instance->parsed_block['attrs'];
// Process the block bindings and get attributes updated with the values from the sources.
$computed_attributes = gutenberg_process_block_bindings( $instance );
if ( empty( $computed_attributes ) ) {
return $block_content;
}
- // Merge the computed attributes with the original attributes.
- $instance->attributes = array_merge( $attributes, $computed_attributes );
+ /*
+ * Merge the computed attributes with the original attributes.
+ *
+ * Note that this is not a recursive merge, meaning that nested attributes --
+ * such as block bindings metadata -- will be completely replaced.
+ * This is desirable. At this point, Core has already processed any block
+ * bindings that it supports. What remains to be processed are only the attributes
+ * for which support was added later (through the `block_bindings_supported_attributes`
+ * filter). To do so, we'll run `$instance->render()` once more
+ * so the block can update its content based on those attributes.
+ */
+ $instance->attributes = array_merge( $instance->attributes, $computed_attributes );
+
+ /*
+ * If we're dealing with the Button block, we remove the bindings metadata
+ * in order to avoid having it reprocessed, which would lead to Core
+ * capitalizing the wrapper tag (e.g.
).
+ */
+ if ( 'core/button' === $instance->name ) {
+ unset( $instance->parsed_block['attrs']['metadata']['bindings'] );
+ }
/**
- * This filter is called from WP_Block::render(), after the block content has
- * already been rendered. However, dynamic blocks expect their render() method
- * to receive block attributes to have their bound values. This means that we have
- * to re-render the block here.
- * To do so, we'll set a flag that this filter checks when invoked to avoid infinite
- * recursion. Furthermore, we can unset all of the block's bindings, as we know that
- * they have been processed by the time we reach this point.
+ * This filter (`gutenberg_block_bindings_render_block`) is called from `WP_Block::render()`.
+ * To avoid infinite recursion, we set a flag that this filter checks when invoked which tells
+ * it to exit early.
*/
- unset( $instance->parsed_block['attrs']['metadata']['bindings'] );
$inside_block_bindings_render = true;
$block_content = $instance->render();
$inside_block_bindings_render = false;
diff --git a/phpunit/block-bindings-test.php b/phpunit/block-bindings-test.php
index ff0d24aa190188..f868bba9537e42 100644
--- a/phpunit/block-bindings-test.php
+++ b/phpunit/block-bindings-test.php
@@ -45,6 +45,14 @@ function ( $supported_attributes ) {
return $supported_attributes;
}
);
+
+ add_filter(
+ 'block_bindings_supported_attributes_core/image',
+ function ( $supported_attributes ) {
+ $supported_attributes[] = 'caption';
+ return $supported_attributes;
+ }
+ );
}
/**
@@ -259,11 +267,19 @@ public function test_passing_uses_context_to_source() {
* Tests if the block content is updated with the value returned by the source
* for the Image block in the placeholder state.
*
+ * Furthermore tests if the caption attribute, for which support is added via the
+ * `block_bindings_supported_attributes_core/image` filter, is correctly processed.
+ *
* @covers ::register_block_bindings_source
*/
public function test_update_block_with_value_from_source_image_placeholder() {
- $get_value_callback = function () {
- return 'https://example.com/image.jpg';
+ $get_value_callback = function ( $source_args, $block_instance, $attribute_name ) {
+ if ( 'url' === $attribute_name ) {
+ return 'https://example.com/image.jpg';
+ }
+ if ( 'caption' === $attribute_name ) {
+ return 'Example Image';
+ }
};
register_block_bindings_source(
@@ -275,8 +291,8 @@ public function test_update_block_with_value_from_source_image_placeholder() {
);
$block_content = <<
-
+
+
HTML;
$parsed_blocks = parse_blocks( $block_content );
@@ -289,7 +305,12 @@ public function test_update_block_with_value_from_source_image_placeholder() {
"The 'url' attribute should be updated with the value returned by the source."
);
$this->assertSame(
- '',
+ 'Example Image',
+ $block->attributes['caption'],
+ "The 'caption' attribute should be updated with the value returned by the source."
+ );
+ $this->assertSame(
+ 'Example Image',
trim( $result ),
'The block content should be updated with the value returned by the source.'
);