From ec404de6602510735d165e29b7880ef654d6d069 Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Tue, 16 Sep 2025 10:35:25 +0200 Subject: [PATCH 1/5] Block Bindings: Fix back-compat layer --- lib/compat/wordpress-6.9/block-bindings.php | 26 ++++++++++++--------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/lib/compat/wordpress-6.9/block-bindings.php b/lib/compat/wordpress-6.9/block-bindings.php index 403c3b1f3db24d..ca8ab25ea63c06 100644 --- a/lib/compat/wordpress-6.9/block-bindings.php +++ b/lib/compat/wordpress-6.9/block-bindings.php @@ -36,26 +36,30 @@ 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 ); /** - * 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; From 5a3ded24f8a2ea14f075094f4e6ec5a186189bab Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Tue, 16 Sep 2025 10:55:02 +0200 Subject: [PATCH 2/5] Add backport changelog --- backport-changelog/6.9/9469.md | 1 + 1 file changed, 1 insertion(+) 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 From 47f0c05c9288f9ac005da7e367b4747ef83e0234 Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Tue, 16 Sep 2025 14:41:37 +0200 Subject: [PATCH 3/5] Add test coverage for the bug that this PR is fixing --- phpunit/block-bindings-test.php | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) 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.' ); From 3363b028c0693b531828a6762b1e30cc4dd1c90d Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Tue, 16 Sep 2025 16:47:23 +0200 Subject: [PATCH 4/5] Fix Button block regression --- lib/compat/wordpress-6.9/block-bindings.php | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/compat/wordpress-6.9/block-bindings.php b/lib/compat/wordpress-6.9/block-bindings.php index ca8ab25ea63c06..ec56f6ae6b6e39 100644 --- a/lib/compat/wordpress-6.9/block-bindings.php +++ b/lib/compat/wordpress-6.9/block-bindings.php @@ -42,7 +42,16 @@ function gutenberg_block_bindings_render_block( $block_content, $block, $instanc return $block_content; } - /** + /* + * 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'] ); + } + + /* * Merge the computed attributes with the original attributes. * * Note that this is not a recursive merge, meaning that nested attributes -- From 6a488fe0caf293c7b43452077e7035fd1eb87609 Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Tue, 16 Sep 2025 20:29:08 +0200 Subject: [PATCH 5/5] Move down to make order a bit easier to grasp --- lib/compat/wordpress-6.9/block-bindings.php | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/compat/wordpress-6.9/block-bindings.php b/lib/compat/wordpress-6.9/block-bindings.php index ec56f6ae6b6e39..245b962757f4bf 100644 --- a/lib/compat/wordpress-6.9/block-bindings.php +++ b/lib/compat/wordpress-6.9/block-bindings.php @@ -42,15 +42,6 @@ function gutenberg_block_bindings_render_block( $block_content, $block, $instanc return $block_content; } - /* - * 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'] ); - } - /* * Merge the computed attributes with the original attributes. * @@ -64,6 +55,15 @@ function gutenberg_block_bindings_render_block( $block_content, $block, $instanc */ $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 (`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