From 8720e12c78cb174ffd5047f917d9bba8a1a1b14f Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Wed, 17 Jan 2024 16:35:20 +0000 Subject: [PATCH 01/72] Add the Block Bindings API --- .../block-bindings/block-bindings.php | 64 +++++++ .../class-wp-block-bindings.php | 156 ++++++++++++++++++ .../block-bindings/sources/pattern.php | 20 +++ .../block-bindings/sources/post-meta.php | 24 +++ src/wp-includes/blocks.php | 76 +++++++++ src/wp-settings.php | 4 + 6 files changed, 344 insertions(+) create mode 100644 src/wp-includes/block-bindings/block-bindings.php create mode 100644 src/wp-includes/block-bindings/class-wp-block-bindings.php create mode 100644 src/wp-includes/block-bindings/sources/pattern.php create mode 100644 src/wp-includes/block-bindings/sources/post-meta.php diff --git a/src/wp-includes/block-bindings/block-bindings.php b/src/wp-includes/block-bindings/block-bindings.php new file mode 100644 index 0000000000000..83fb5b8aaae66 --- /dev/null +++ b/src/wp-includes/block-bindings/block-bindings.php @@ -0,0 +1,64 @@ +register_source( $source_name, $label, $apply ); +} + + +/** + * Retrieves the list of registered block sources. + * + * @return array The list of registered block sources. + */ +function wp_block_bindings_get_sources() { + return wp_block_bindings()->get_sources(); +} + + +/** + * Replaces the HTML content of a block based on the provided source value. + * + * @param string $block_content Block Content. + * @param string $block_name The name of the block to process. + * @param string $block_attr The attribute of the block we want to process. + * @param string $source_value The value used to replace the HTML. + * @return string The modified block content. + */ +function wp_block_bindings_replace_html( $block_content, $block_name, $block_attr, $source_value ) { + return wp_block_bindings()->replace_html( $block_content, $block_name, $block_attr, $source_value ); +} diff --git a/src/wp-includes/block-bindings/class-wp-block-bindings.php b/src/wp-includes/block-bindings/class-wp-block-bindings.php new file mode 100644 index 0000000000000..4f61618d8adba --- /dev/null +++ b/src/wp-includes/block-bindings/class-wp-block-bindings.php @@ -0,0 +1,156 @@ +sources[ $source_name ] = array( + 'label' => $label, + 'apply' => $apply, + ); + } + + /** + * Depending on the block attributes, replace the proper HTML based on the value returned by the source. + * + * @param string $block_content Block Content. + * @param string $block_name The name of the block to process. + * @param string $block_attr The attribute of the block we want to process. + * @param string $source_value The value used to replace the HTML. + */ + public function replace_html( $block_content, $block_name, $block_attr, $source_value ) { + $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block_name ); + if ( null === $block_type ) { + return; + } + + // Depending on the attribute source, the processing will be different. + switch ( $block_type->attributes[ $block_attr ]['source'] ) { + case 'html': + case 'rich-text': + $block_reader = new WP_HTML_Tag_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. + $selectors = explode( ',', $block_type->attributes[ $block_attr ]['selector'] ); + // Add a bookmark to the first tag to be able to iterate over the selectors. + $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' === $block_name ) { + $button_wrapper = $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 ), $selector ) === 0 || $block_reader->next_tag( + array( + 'tag_name' => $selector, + ) + ) ) { + $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>" . esc_html( $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' === $block_name || 'core/heading' === $block_name ) { + return $amended_content->get_updated_html(); + } + if ( 'core/button' === $block_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(); + } + } else { + $block_reader->seek( 'iterate-selectors' ); + } + } + $block_reader->release_bookmark( 'iterate-selectors' ); + return $block_content; + + case 'attribute': + $amended_content = new WP_HTML_Tag_Processor( $block_content ); + if ( ! $amended_content->next_tag( + array( + // TODO: build the query from CSS selector. + 'tag_name' => $block_type->attributes[ $block_attr ]['selector'], + ) + ) ) { + return $block_content; + } + $amended_content->set_attribute( $block_type->attributes[ $block_attr ]['attribute'], esc_attr( $source_value ) ); + return $amended_content->get_updated_html(); + break; + + default: + return $block_content; + break; + } + return; + } + + /** + * Retrieves the list of registered block sources. + * + * @return array The array of registered sources. + */ + public function get_sources() { + return $this->sources; + } +} diff --git a/src/wp-includes/block-bindings/sources/pattern.php b/src/wp-includes/block-bindings/sources/pattern.php new file mode 100644 index 0000000000000..be6ec9bd87d2b --- /dev/null +++ b/src/wp-includes/block-bindings/sources/pattern.php @@ -0,0 +1,20 @@ +attributes, array( 'metadata', 'id' ), false ) ) { + return null; + } + $block_id = $block_instance->attributes['metadata']['id']; + return _wp_array_get( $block_instance->context, array( 'pattern/overrides', $block_id, $attribute_name ), null ); +} + +wp_block_bindings_register_source( + 'pattern_attributes', + __( 'Pattern Attributes' ), + 'pattern_source_callback' +); diff --git a/src/wp-includes/block-bindings/sources/post-meta.php b/src/wp-includes/block-bindings/sources/post-meta.php new file mode 100644 index 0000000000000..d27fc30b62a1e --- /dev/null +++ b/src/wp-includes/block-bindings/sources/post-meta.php @@ -0,0 +1,24 @@ +context['postId'] but it wasn't available in the image block. + $post_id = get_the_ID(); + } + + return get_post_meta( $post_id, $source_attrs['value'], true ); +} + +wp_block_bindings_register_source( + 'post_meta', + __( 'Post Meta' ), + 'post_meta_source_callback' +); diff --git a/src/wp-includes/blocks.php b/src/wp-includes/blocks.php index 431e2b015332c..75e450a9f0859 100644 --- a/src/wp-includes/blocks.php +++ b/src/wp-includes/blocks.php @@ -2083,3 +2083,79 @@ function _wp_footnotes_force_filtered_html_on_import_filter( $arg ) { } return $arg; } + + +/** + * Process the block bindings attribute. + * + * @param string $block_content Block Content. + * @param array $block Block attributes. + * @param WP_Block $block_instance The block instance. + */ +function gutenberg_process_block_bindings( $block_content, $block, $block_instance ) { + + // Allowed blocks that support block bindings. + // TODO: Look for a mechanism to opt-in for this. Maybe adding a property to block attributes? + $allowed_blocks = array( + 'core/paragraph' => array( 'content' ), + 'core/heading' => array( 'content' ), + 'core/image' => array( 'url', 'title', 'alt' ), + 'core/button' => array( 'url', 'text' ), + ); + + // If the block doesn't have the bindings property or isn't one of the allowed block types, return. + if ( ! isset( $block['attrs']['metadata']['bindings'] ) || ! isset( $allowed_blocks[ $block_instance->name ] ) ) { + return $block_content; + } + + // Assuming the following format for the bindings property of the "metadata" attribute: + // + // "bindings": { + // "title": { + // "source": { + // "name": "post_meta", + // "attributes": { "value": "text_custom_field" } + // } + // }, + // "url": { + // "source": { + // "name": "post_meta", + // "attributes": { "value": "text_custom_field" } + // } + // } + // } + // + + $block_bindings_sources = wp_block_bindings_get_sources(); + $modified_block_content = $block_content; + foreach ( $block['attrs']['metadata']['bindings'] as $binding_attribute => $binding_source ) { + + // If the attribute is not in the list, process next attribute. + if ( ! in_array( $binding_attribute, $allowed_blocks[ $block_instance->name ], true ) ) { + continue; + } + // If no source is provided, or that source is not registered, process next attribute. + if ( ! isset( $binding_source['source'] ) || ! isset( $binding_source['source']['name'] ) || ! isset( $block_bindings_sources[ $binding_source['source']['name'] ] ) ) { + continue; + } + + $source_callback = $block_bindings_sources[ $binding_source['source']['name'] ]['apply']; + // Get the value based on the source. + if ( ! isset( $binding_source['source']['attributes'] ) ) { + $source_args = array(); + } else { + $source_args = $binding_source['source']['attributes']; + } + $source_value = $source_callback( $source_args, $block_instance, $binding_attribute ); + // If the value is null, process next attribute. + if ( is_null( $source_value ) ) { + continue; + } + + // Process the HTML based on the block and the attribute. + $modified_block_content = wp_block_bindings_replace_html( $modified_block_content, $block_instance->name, $binding_attribute, $source_value ); + } + return $modified_block_content; +} + +add_filter( 'render_block', 'gutenberg_process_block_bindings', 20, 3 ); diff --git a/src/wp-settings.php b/src/wp-settings.php index c8835db31a005..b258bfffbbd5a 100644 --- a/src/wp-settings.php +++ b/src/wp-settings.php @@ -367,6 +367,10 @@ require ABSPATH . WPINC . '/fonts.php'; require ABSPATH . WPINC . '/class-wp-script-modules.php'; require ABSPATH . WPINC . '/script-modules.php'; +require ABSPATH . WPINC . '/block-bindings/block-bindings.php'; +require ABSPATH . WPINC . '/block-bindings/class-wp-block-bindings.php'; +require ABSPATH . WPINC . '/block-bindings/sources/post-meta.php'; +require ABSPATH . WPINC . '/block-bindings/sources/pattern.php'; $GLOBALS['wp_embed'] = new WP_Embed(); From 8891169e84b20a3623bf67162da2d947d0fc6fb2 Mon Sep 17 00:00:00 2001 From: Ricardo Artemio Morales Date: Wed, 17 Jan 2024 13:43:23 -0500 Subject: [PATCH 02/72] Add tests --- .../includes/class-wp-block-bindings-test.php | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 tests/phpunit/includes/class-wp-block-bindings-test.php diff --git a/tests/phpunit/includes/class-wp-block-bindings-test.php b/tests/phpunit/includes/class-wp-block-bindings-test.php new file mode 100644 index 0000000000000..dfd5e368a1a5f --- /dev/null +++ b/tests/phpunit/includes/class-wp-block-bindings-test.php @@ -0,0 +1,87 @@ +register_source( $source_name, $label, $apply ); + + $sources = $wp_block_bindings->get_sources(); + $this->assertArrayHasKey( $source_name, $sources ); + $this->assertEquals( $label, $sources[ $source_name ]['label'] ); + $this->assertEquals( $apply, $sources[ $source_name ]['apply'] ); + } + + /** + * Test replace_html method for content. + */ + public function test_replace_html_for_paragraph_content() { + $wp_block_bindings = new WP_Block_Bindings(); + + $block_content = '

Hello World

'; + $block_name = 'core/paragraph'; + $block_attr = 'content'; + $source_value = 'Updated Content'; + + $result = $wp_block_bindings->replace_html( $block_content, $block_name, $block_attr, $source_value ); + + // Check if the block content was updated correctly. + $this->assertStringContainsString( $source_value, $result ); + } + + /** + * Test replace_html method for attributes. + */ + public function test_replace_html_for_attribute() { + $wp_block_bindings = new WP_Block_Bindings(); + $block_content = '
Hello World
'; + $block_name = 'core/button'; + $block_attr = 'url'; + $source_value = 'Updated URL'; + + $result = $wp_block_bindings->replace_html( $block_content, $block_name, $block_attr, $source_value ); + $this->assertStringContainsString( $source_value, $result ); + } + + // Test case for scenarios where block type is not registered. + public function test_replace_html_with_unregistered_block() { + $wp_block_bindings = new WP_Block_Bindings(); + + $block_content = '

Hello World

'; + $block_name = 'NONEXISTENT'; + $block_attr = 'content'; + $source_value = 'Updated Content'; + + $result = $wp_block_bindings->replace_html( $block_content, $block_name, $block_attr, $source_value ); + + $this->assertEquals( $block_content, $result ); + } + + // Test case for scenarios where block is registered but attribute does not exist on block type. + public function test_replace_html_with_registered_block_but_unsupported_source_type() { + $wp_block_bindings = new WP_Block_Bindings(); + + $block_content = '
Hello World
'; + $block_name = 'core/paragraph'; + $block_attr = 'NONEXISTENT'; + $source_value = 'Updated Content'; + + $result = $wp_block_bindings->replace_html( $block_content, $block_name, $block_attr, $source_value ); + + $this->assertEquals( $block_content, $result ); + } +} From eac5b22fc82a923b962807018c0dc3e226455071 Mon Sep 17 00:00:00 2001 From: Ricardo Artemio Morales Date: Wed, 17 Jan 2024 14:22:03 -0500 Subject: [PATCH 03/72] Move tests to different directory --- .../class-wp-block-bindings-test.php => tests/block-bindings.php} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/phpunit/{includes/class-wp-block-bindings-test.php => tests/block-bindings.php} (100%) diff --git a/tests/phpunit/includes/class-wp-block-bindings-test.php b/tests/phpunit/tests/block-bindings.php similarity index 100% rename from tests/phpunit/includes/class-wp-block-bindings-test.php rename to tests/phpunit/tests/block-bindings.php From 39e52ed5d12eed3e78117bed305feddaf0239291 Mon Sep 17 00:00:00 2001 From: Ricardo Artemio Morales Date: Wed, 17 Jan 2024 14:31:31 -0500 Subject: [PATCH 04/72] Fix bug wherein block content was erased --- src/wp-includes/block-bindings/class-wp-block-bindings.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/wp-includes/block-bindings/class-wp-block-bindings.php b/src/wp-includes/block-bindings/class-wp-block-bindings.php index 4f61618d8adba..f43b288c153dc 100644 --- a/src/wp-includes/block-bindings/class-wp-block-bindings.php +++ b/src/wp-includes/block-bindings/class-wp-block-bindings.php @@ -53,8 +53,8 @@ public function register_source( $source_name, $label, $apply ) { */ public function replace_html( $block_content, $block_name, $block_attr, $source_value ) { $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block_name ); - if ( null === $block_type ) { - return; + if ( null === $block_type || ! isset( $block_type->attributes[ $block_attr ] ) ) { + return $block_content; } // Depending on the attribute source, the processing will be different. From 82e70ff4f857578ef64e8fc4f14d0d78966498f5 Mon Sep 17 00:00:00 2001 From: Ricardo Artemio Morales Date: Wed, 17 Jan 2024 14:34:51 -0500 Subject: [PATCH 05/72] Update comment format for consistency --- tests/phpunit/tests/block-bindings.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/phpunit/tests/block-bindings.php b/tests/phpunit/tests/block-bindings.php index dfd5e368a1a5f..1767dbbfe5c4b 100644 --- a/tests/phpunit/tests/block-bindings.php +++ b/tests/phpunit/tests/block-bindings.php @@ -57,7 +57,9 @@ public function test_replace_html_for_attribute() { $this->assertStringContainsString( $source_value, $result ); } - // Test case for scenarios where block type is not registered. + /** + * Test case for scenarios where block type is not registered. + */ public function test_replace_html_with_unregistered_block() { $wp_block_bindings = new WP_Block_Bindings(); @@ -71,7 +73,9 @@ public function test_replace_html_with_unregistered_block() { $this->assertEquals( $block_content, $result ); } - // Test case for scenarios where block is registered but attribute does not exist on block type. + /** + * Test case for scenarios where block is registered but attribute does not exist on block type. + */ public function test_replace_html_with_registered_block_but_unsupported_source_type() { $wp_block_bindings = new WP_Block_Bindings(); From 6486471b56d5c1b08706bb09d600347e5719448b Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Wed, 17 Jan 2024 19:54:39 +0000 Subject: [PATCH 06/72] Move process_block_bindings_function to class-wp-block --- src/wp-includes/blocks.php | 4 +--- src/wp-includes/class-wp-block.php | 2 ++ 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/wp-includes/blocks.php b/src/wp-includes/blocks.php index 75e450a9f0859..c22d2a1ffe7df 100644 --- a/src/wp-includes/blocks.php +++ b/src/wp-includes/blocks.php @@ -2092,7 +2092,7 @@ function _wp_footnotes_force_filtered_html_on_import_filter( $arg ) { * @param array $block Block attributes. * @param WP_Block $block_instance The block instance. */ -function gutenberg_process_block_bindings( $block_content, $block, $block_instance ) { +function _process_block_bindings( $block_content, $block, $block_instance ) { // Allowed blocks that support block bindings. // TODO: Look for a mechanism to opt-in for this. Maybe adding a property to block attributes? @@ -2157,5 +2157,3 @@ function gutenberg_process_block_bindings( $block_content, $block, $block_instan } return $modified_block_content; } - -add_filter( 'render_block', 'gutenberg_process_block_bindings', 20, 3 ); diff --git a/src/wp-includes/class-wp-block.php b/src/wp-includes/class-wp-block.php index 65d3af6a12f1a..95ba2bf0ddd2c 100644 --- a/src/wp-includes/class-wp-block.php +++ b/src/wp-includes/class-wp-block.php @@ -280,6 +280,8 @@ public function render( $options = array() ) { } } + $block_content = _process_block_bindings( $block_content, $this->parsed_block, $this ); + /** * Filters the content of a single block. * From 048330e6b491836ff335a0308c9cff88dc7dfb45 Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Wed, 17 Jan 2024 19:59:18 +0000 Subject: [PATCH 07/72] WordPress capitalization dangit --- tests/phpunit/tests/block-bindings.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/phpunit/tests/block-bindings.php b/tests/phpunit/tests/block-bindings.php index 1767dbbfe5c4b..32167b681087b 100644 --- a/tests/phpunit/tests/block-bindings.php +++ b/tests/phpunit/tests/block-bindings.php @@ -3,7 +3,7 @@ * Unit tests covering WP_Block_Bindings functionality. * * @since 6.5.0 - * @package wordpress + * @package WordPress */ class WP_Block_Bindings_Test extends WP_UnitTestCase { From 4a4b8257bf5ded6d1faf4ae7695b0ebe83a21bab Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Thu, 18 Jan 2024 14:05:35 +0000 Subject: [PATCH 08/72] PHPDoc formatting --- .../block-bindings/sources/pattern.php | 2 +- .../block-bindings/sources/post-meta.php | 2 +- src/wp-includes/blocks.php | 27 +++++-------------- 3 files changed, 8 insertions(+), 23 deletions(-) diff --git a/src/wp-includes/block-bindings/sources/pattern.php b/src/wp-includes/block-bindings/sources/pattern.php index be6ec9bd87d2b..92f7189c6e4d4 100644 --- a/src/wp-includes/block-bindings/sources/pattern.php +++ b/src/wp-includes/block-bindings/sources/pattern.php @@ -1,6 +1,6 @@ $binding_source ) { From 9a662e4b4c115557d8e9f643286a75d729040e0f Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Thu, 18 Jan 2024 15:14:54 +0000 Subject: [PATCH 09/72] Add a better PHPDoc --- src/wp-includes/blocks.php | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/src/wp-includes/blocks.php b/src/wp-includes/blocks.php index 2dde8f8dcd131..be624a7ac9556 100644 --- a/src/wp-includes/blocks.php +++ b/src/wp-includes/blocks.php @@ -2088,6 +2088,42 @@ function _wp_footnotes_force_filtered_html_on_import_filter( $arg ) { /** * Processes the block bindings in block's attributes. * + * A block might contain bindings in its attributes. Bindings are mappings + * between an attribute of the block and a source. A "source" is a function + * registered with `wp_block_bindings_register_source()` that defines how to + * retrieve a value from outside the block, e.g. from post meta. + * + * This function will process those bindings and replace the HTML with the value of the binding. + * The value is retrieved from the source of the binding. + * + * ### Example + * + * The "bindings" property for an Image block might look like this: + * + * ```json + * { + * "metadata": { + * "bindings": { + * "title": { + * "source": { + * "name": "post_meta", + * "attributes": { "value": "text_custom_field" } + * } + * }, + * "url": { + * "source": { + * "name": "post_meta", + * "attributes": { "value": "url_custom_field" } + * } + * } + * } + * } + * } + * ``` + * + * The above example will replace the `title` and `url` attributes of the Image + * block with the values of the `text_custom_field` and `url_custom_field` post meta. + * * @access private * @since 6.5.0 * From c92b6ac006fc84477e58b94181c51be453c9c332 Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Thu, 18 Jan 2024 18:50:26 +0000 Subject: [PATCH 10/72] Add a new unit test covering the _process_block_bindings() function --- tests/phpunit/tests/block-bindings.php | 47 ++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/tests/phpunit/tests/block-bindings.php b/tests/phpunit/tests/block-bindings.php index 32167b681087b..5f7b40a1b1e1e 100644 --- a/tests/phpunit/tests/block-bindings.php +++ b/tests/phpunit/tests/block-bindings.php @@ -88,4 +88,51 @@ public function test_replace_html_with_registered_block_but_unsupported_source_t $this->assertEquals( $block_content, $result ); } + + /** + * Test case for the _process_block_bindings function using a custom binding source. + * + * @covers _process_block_bindings + */ + public function test_post_meta_bindings_source() { + $wp_block_bindings = wp_block_bindings(); + + // Register a custom binding source. + $source_name = 'test_source'; + $label = 'Test Source'; + $apply = function () { + return 'test source value'; + }; + $wp_block_bindings->register_source( $source_name, $label, $apply ); + + $block_content = '

This content will be overriden

'; + + // Parsed block representing a paragraph block. + $block = array( + 'blockName' => 'core/paragraph', + 'attrs' => array( + 'metadata' => array( + 'bindings' => array( + 'content' => array( + 'source' => array( + 'name' => 'test_source', + 'attributes' => array( + 'value' => 'does not matter, it is not used by the test source', + ), + ), + ), + ), + ), + ), + ); + + // Block instance representing a paragraph block. + $block_instance = new WP_Block( $block ); + + $content = _process_block_bindings( $block_content, $block, $block_instance ); + + $result = '

test source value

'; + + $this->assertEquals( $result, $content, 'The block content should be updated with the value returned by the custom binding source.' ); + } } From abb56fb742b41a0dc79c575513810edb34d4f57d Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Thu, 18 Jan 2024 18:57:22 +0000 Subject: [PATCH 11/72] More fixes for formatting, capitalization and PHPDocs --- .../block-bindings/block-bindings.php | 2 +- .../block-bindings/class-wp-block-bindings.php | 2 +- .../block-bindings/sources/pattern.php | 7 ++++--- .../block-bindings/sources/post-meta.php | 6 +++--- tests/phpunit/tests/block-bindings.php | 18 ++++++++++++++++-- 5 files changed, 25 insertions(+), 10 deletions(-) diff --git a/src/wp-includes/block-bindings/block-bindings.php b/src/wp-includes/block-bindings/block-bindings.php index 83fb5b8aaae66..4855900cbb8c9 100644 --- a/src/wp-includes/block-bindings/block-bindings.php +++ b/src/wp-includes/block-bindings/block-bindings.php @@ -53,7 +53,7 @@ function wp_block_bindings_get_sources() { /** * Replaces the HTML content of a block based on the provided source value. * - * @param string $block_content Block Content. + * @param string $block_content Block content. * @param string $block_name The name of the block to process. * @param string $block_attr The attribute of the block we want to process. * @param string $source_value The value used to replace the HTML. diff --git a/src/wp-includes/block-bindings/class-wp-block-bindings.php b/src/wp-includes/block-bindings/class-wp-block-bindings.php index f43b288c153dc..a9a9a31957bad 100644 --- a/src/wp-includes/block-bindings/class-wp-block-bindings.php +++ b/src/wp-includes/block-bindings/class-wp-block-bindings.php @@ -46,7 +46,7 @@ public function register_source( $source_name, $label, $apply ) { /** * Depending on the block attributes, replace the proper HTML based on the value returned by the source. * - * @param string $block_content Block Content. + * @param string $block_content Block content. * @param string $block_name The name of the block to process. * @param string $block_attr The attribute of the block we want to process. * @param string $source_value The value used to replace the HTML. diff --git a/src/wp-includes/block-bindings/sources/pattern.php b/src/wp-includes/block-bindings/sources/pattern.php index 92f7189c6e4d4..c032baa06fc6d 100644 --- a/src/wp-includes/block-bindings/sources/pattern.php +++ b/src/wp-includes/block-bindings/sources/pattern.php @@ -1,6 +1,7 @@ attributes, array( 'metadata', 'id' ), false ) ) { return null; } - $block_id = $block_instance->attributes['metadata']['id']; - return _wp_array_get( $block_instance->context, array( 'pattern/overrides', $block_id, $attribute_name ), null ); + $block_id = $block_instance->attributes['metadata']['id']; + return _wp_array_get( $block_instance->context, array( 'pattern/overrides', $block_id, $attribute_name ), null ); } wp_block_bindings_register_source( diff --git a/src/wp-includes/block-bindings/sources/post-meta.php b/src/wp-includes/block-bindings/sources/post-meta.php index c241f3198d4a0..f03f00afde055 100644 --- a/src/wp-includes/block-bindings/sources/post-meta.php +++ b/src/wp-includes/block-bindings/sources/post-meta.php @@ -6,15 +6,15 @@ * @package WordPress */ function post_meta_source_callback( $source_attrs ) { - // Use the postId attribute if available + // Use the postId attribute if available if ( isset( $source_attrs['postId'] ) ) { $post_id = $source_attrs['postId']; } else { - // I tried using $block_instance->context['postId'] but it wasn't available in the image block. + // $block_instance->context['postId'] is not available in the Image block. $post_id = get_the_ID(); } - return get_post_meta( $post_id, $source_attrs['value'], true ); + return get_post_meta( $post_id, $source_attrs['value'], true ); } wp_block_bindings_register_source( diff --git a/tests/phpunit/tests/block-bindings.php b/tests/phpunit/tests/block-bindings.php index 5f7b40a1b1e1e..5a97545ca77ea 100644 --- a/tests/phpunit/tests/block-bindings.php +++ b/tests/phpunit/tests/block-bindings.php @@ -2,14 +2,19 @@ /** * Unit tests covering WP_Block_Bindings functionality. * + * @group block-bindings + * + * @covers WP_Block_Bindings + * * @since 6.5.0 * @package WordPress */ - class WP_Block_Bindings_Test extends WP_UnitTestCase { /** * Test register_source method. + * + * @covers WP_Block_Bindings::register_source */ public function test_register_source() { $wp_block_bindings = new WP_Block_Bindings(); @@ -28,6 +33,8 @@ public function test_register_source() { /** * Test replace_html method for content. + * + * @covers WP_Block_Bindings::replace_html */ public function test_replace_html_for_paragraph_content() { $wp_block_bindings = new WP_Block_Bindings(); @@ -45,6 +52,8 @@ public function test_replace_html_for_paragraph_content() { /** * Test replace_html method for attributes. + * + * @covers WP_Block_Bindings::replace_html */ public function test_replace_html_for_attribute() { $wp_block_bindings = new WP_Block_Bindings(); @@ -59,6 +68,8 @@ public function test_replace_html_for_attribute() { /** * Test case for scenarios where block type is not registered. + * + * @covers WP_Block_Bindings::replace_html */ public function test_replace_html_with_unregistered_block() { $wp_block_bindings = new WP_Block_Bindings(); @@ -74,7 +85,10 @@ public function test_replace_html_with_unregistered_block() { } /** - * Test case for scenarios where block is registered but attribute does not exist on block type. + * Test case for scenarios where block is registered but attribute does not + * exist on block type. + * + * @covers WP_Block_Bindings::replace_html */ public function test_replace_html_with_registered_block_but_unsupported_source_type() { $wp_block_bindings = new WP_Block_Bindings(); From e8b11957bd66892e248f6fac078083a0190a47a8 Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Thu, 18 Jan 2024 18:58:25 +0000 Subject: [PATCH 12/72] Add @since to block-bindings.php --- src/wp-includes/block-bindings/block-bindings.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/wp-includes/block-bindings/block-bindings.php b/src/wp-includes/block-bindings/block-bindings.php index 4855900cbb8c9..25a1d89d660b3 100644 --- a/src/wp-includes/block-bindings/block-bindings.php +++ b/src/wp-includes/block-bindings/block-bindings.php @@ -11,6 +11,8 @@ /** * Retrieves the singleton instance of WP_Block_Bindings. * + * @since 6.5.0 + * * @return WP_Block_Bindings The WP_Block_Bindings instance. */ function wp_block_bindings() { @@ -25,6 +27,8 @@ function wp_block_bindings() { /** * Registers a new source for block bindings. * + * @since 6.5.0 + * * @param string $source_name The name of the source. * @param string $label The label of the source. * @param callable $apply The callback executed when the source is processed during block rendering. The callable should have the following signature: @@ -43,6 +47,8 @@ function wp_block_bindings_register_source( $source_name, $label, $apply ) { /** * Retrieves the list of registered block sources. * + * @since 6.5.0 + * * @return array The list of registered block sources. */ function wp_block_bindings_get_sources() { @@ -53,6 +59,8 @@ function wp_block_bindings_get_sources() { /** * Replaces the HTML content of a block based on the provided source value. * + * @since 6.5.0 + * * @param string $block_content Block content. * @param string $block_name The name of the block to process. * @param string $block_attr The attribute of the block we want to process. From cc6c2c2eab8c594d7fa91a3d46b155102b2ddb9d Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Thu, 18 Jan 2024 19:01:52 +0000 Subject: [PATCH 13/72] Add version and since tags to class and methods --- .../block-bindings/class-wp-block-bindings.php | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/wp-includes/block-bindings/class-wp-block-bindings.php b/src/wp-includes/block-bindings/class-wp-block-bindings.php index a9a9a31957bad..4716c38afc5a0 100644 --- a/src/wp-includes/block-bindings/class-wp-block-bindings.php +++ b/src/wp-includes/block-bindings/class-wp-block-bindings.php @@ -18,6 +18,8 @@ class WP_Block_Bindings { /** * Holds the registered block bindings sources, keyed by source identifier. * + * @since 6.5.0 + * * @var array */ private $sources = array(); @@ -25,6 +27,8 @@ class WP_Block_Bindings { /** * Function to register a new source. * + * @since 6.5.0 + * * @param string $source_name The name of the source. * @param string $label The label of the source. * @param callable $apply The callback executed when the source is processed during block rendering. The callable should have the following signature: @@ -44,7 +48,9 @@ public function register_source( $source_name, $label, $apply ) { } /** - * Depending on the block attributes, replace the proper HTML based on the value returned by the source. + * Depending on the block attributes, replace the HTML based on the value returned by the source. + * + * @since 6.5.0 * * @param string $block_content Block content. * @param string $block_name The name of the block to process. @@ -148,6 +154,8 @@ public function replace_html( $block_content, $block_name, $block_attr, $source_ /** * Retrieves the list of registered block sources. * + * @since 6.5.0 + * * @return array The array of registered sources. */ public function get_sources() { From 206b6b780589f3dc3e63c0e68dc6fa3a8fd28bd8 Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Thu, 18 Jan 2024 19:17:35 +0000 Subject: [PATCH 14/72] Add a comment on the block class --- src/wp-includes/class-wp-block.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/wp-includes/class-wp-block.php b/src/wp-includes/class-wp-block.php index 95ba2bf0ddd2c..89e80c8cfc0e7 100644 --- a/src/wp-includes/class-wp-block.php +++ b/src/wp-includes/class-wp-block.php @@ -280,6 +280,8 @@ public function render( $options = array() ) { } } + // Process the block bindings for this block, if any are registered. This + // will replace the block content with the value from a registered binding source. $block_content = _process_block_bindings( $block_content, $this->parsed_block, $this ); /** From dc8377a701323f6c285fe988e28c093617edbef8 Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Thu, 18 Jan 2024 19:20:19 +0000 Subject: [PATCH 15/72] Update assertions in WP_Block_Bindings_Test class --- tests/phpunit/tests/block-bindings.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/phpunit/tests/block-bindings.php b/tests/phpunit/tests/block-bindings.php index 5a97545ca77ea..28015c2da0b1a 100644 --- a/tests/phpunit/tests/block-bindings.php +++ b/tests/phpunit/tests/block-bindings.php @@ -27,8 +27,8 @@ public function test_register_source() { $sources = $wp_block_bindings->get_sources(); $this->assertArrayHasKey( $source_name, $sources ); - $this->assertEquals( $label, $sources[ $source_name ]['label'] ); - $this->assertEquals( $apply, $sources[ $source_name ]['apply'] ); + $this->assertEquals( $label, $sources[ $source_name ]['label'], 'The label should match the one in the registered source' ); + $this->assertEquals( $apply, $sources[ $source_name ]['apply'], 'The apply callback should match the one in the registered source' ); } /** @@ -47,7 +47,7 @@ public function test_replace_html_for_paragraph_content() { $result = $wp_block_bindings->replace_html( $block_content, $block_name, $block_attr, $source_value ); // Check if the block content was updated correctly. - $this->assertStringContainsString( $source_value, $result ); + $this->assertStringContainsString( $source_value, $result, 'The block content should be updated with the value returned by the source.' ); } /** @@ -63,7 +63,7 @@ public function test_replace_html_for_attribute() { $source_value = 'Updated URL'; $result = $wp_block_bindings->replace_html( $block_content, $block_name, $block_attr, $source_value ); - $this->assertStringContainsString( $source_value, $result ); + $this->assertStringContainsString( $source_value, $result, 'The block content should be updated with the value returned by the source.' ); } /** @@ -81,7 +81,7 @@ public function test_replace_html_with_unregistered_block() { $result = $wp_block_bindings->replace_html( $block_content, $block_name, $block_attr, $source_value ); - $this->assertEquals( $block_content, $result ); + $this->assertEquals( $block_content, $result, 'The block content should not be updated if the block type is not registered.' ); } /** @@ -100,7 +100,7 @@ public function test_replace_html_with_registered_block_but_unsupported_source_t $result = $wp_block_bindings->replace_html( $block_content, $block_name, $block_attr, $source_value ); - $this->assertEquals( $block_content, $result ); + $this->assertEquals( $block_content, $result, 'The block content should not be updated if the block type does not support the attribute.' ); } /** From 751a459d42cdb0834ded5f94c1ebcaaf80013a73 Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Thu, 18 Jan 2024 19:45:07 +0000 Subject: [PATCH 16/72] Update block binding registration function comment --- src/wp-includes/block-bindings/class-wp-block-bindings.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/wp-includes/block-bindings/class-wp-block-bindings.php b/src/wp-includes/block-bindings/class-wp-block-bindings.php index 4716c38afc5a0..2deaeee419003 100644 --- a/src/wp-includes/block-bindings/class-wp-block-bindings.php +++ b/src/wp-includes/block-bindings/class-wp-block-bindings.php @@ -25,7 +25,12 @@ class WP_Block_Bindings { private $sources = array(); /** - * Function to register a new source. + * Function to register a new block binding source. + * + * Sources are used to override block's original attributes with a value + * coming from the source. Once a source is registered, it can be used by a + * block by setting its `metadata.bindings` attribute to a value that refers + * to the source. * * @since 6.5.0 * From 423bb66359ecceba8a641996922684ce6cd1cedc Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Tue, 23 Jan 2024 15:50:06 +0000 Subject: [PATCH 17/72] Change the signature of `wp_block_bindings_register_source` & WP_Block_Bindings::register_source to accept an array of [label, apply] --- .../block-bindings/block-bindings.php | 24 ++++++++++------ .../class-wp-block-bindings.php | 28 ++++++++++--------- .../block-bindings/sources/pattern.php | 6 ++-- .../block-bindings/sources/post-meta.php | 6 ++-- tests/phpunit/tests/block-bindings.php | 4 +-- 5 files changed, 40 insertions(+), 28 deletions(-) diff --git a/src/wp-includes/block-bindings/block-bindings.php b/src/wp-includes/block-bindings/block-bindings.php index 25a1d89d660b3..92b18dd8f0993 100644 --- a/src/wp-includes/block-bindings/block-bindings.php +++ b/src/wp-includes/block-bindings/block-bindings.php @@ -30,17 +30,23 @@ function wp_block_bindings() { * @since 6.5.0 * * @param string $source_name The name of the source. - * @param string $label The label of the source. - * @param callable $apply The callback executed when the source is processed during block rendering. The callable should have the following signature: - * function (object $source_attrs, object $block_instance, string $attribute_name): string - * - object $source_attrs: Object containing source ID used to look up the override value, i.e. {"value": "{ID}"}. - * - object $block_instance: The block instance. - * - string $attribute_name: The name of an attribute used to retrieve an override value from the block context. - * The callable should return a string that will be used to override the block's original value. + * @param array $source_args The array of arguments that are used to register a source. The array has two elements: + * 1. string $label The label of the source. + * 2. callback $apply A callback + * executed when the source is processed during + * block rendering. The callback should have the + * following signature: + * + * `function (object $source_attrs, object $block_instance, string $attribute_name): string` + * - @param object $source_attrs: Object containing source ID used to look up the override value, i.e. {"value": "{ID}"}. + * - @param object $block_instance: The block instance. + * - @param string $attribute_name: The name of an attribute used to retrieve an override value from the block context. + * The callback should return a string that will be used to override the block's original value. + * * @return void */ -function wp_block_bindings_register_source( $source_name, $label, $apply ) { - wp_block_bindings()->register_source( $source_name, $label, $apply ); +function wp_block_bindings_register_source( $source_name, $source_args ) { + wp_block_bindings()->register_source( $source_name, $source_args ); } diff --git a/src/wp-includes/block-bindings/class-wp-block-bindings.php b/src/wp-includes/block-bindings/class-wp-block-bindings.php index 2deaeee419003..537140282d502 100644 --- a/src/wp-includes/block-bindings/class-wp-block-bindings.php +++ b/src/wp-includes/block-bindings/class-wp-block-bindings.php @@ -34,22 +34,24 @@ class WP_Block_Bindings { * * @since 6.5.0 * - * @param string $source_name The name of the source. - * @param string $label The label of the source. - * @param callable $apply The callback executed when the source is processed during block rendering. The callable should have the following signature: - * function (object $source_attrs, object $block_instance, string $attribute_name): string - * - object $source_attrs: Object containing source ID used to look up the override value, i.e. {"value": "{ID}"}. - * - object $block_instance: The block instance. - * - string $attribute_name: The name of an attribute used to retrieve an override value from the block context. - * The callable should return a string that will be used to override the block's original value. + * @param string $source_name The name of the source. + * @param array $source_args The array of arguments that are used to register a source. The array has two elements: + * 1. string $label The label of the source. + * 2. callback $apply A callback + * executed when the source is processed during + * block rendering. The callback should have the + * following signature: + * + * `function (object $source_attrs, object $block_instance, string $attribute_name): string` + * - @param object $source_attrs: Object containing source ID used to look up the override value, i.e. {"value": "{ID}"}. + * - @param object $block_instance: The block instance. + * - @param string $attribute_name: The name of an attribute used to retrieve an override value from the block context. + * The callback should return a string that will be used to override the block's original value. * * @return void */ - public function register_source( $source_name, $label, $apply ) { - $this->sources[ $source_name ] = array( - 'label' => $label, - 'apply' => $apply, - ); + public function register_source( $source_name, array $source_args ) { + $this->sources[ $source_name ] = $source_args; } /** diff --git a/src/wp-includes/block-bindings/sources/pattern.php b/src/wp-includes/block-bindings/sources/pattern.php index c032baa06fc6d..c9c6c52190540 100644 --- a/src/wp-includes/block-bindings/sources/pattern.php +++ b/src/wp-includes/block-bindings/sources/pattern.php @@ -16,6 +16,8 @@ function pattern_source_callback( $source_attrs, $block_instance, $attribute_nam wp_block_bindings_register_source( 'pattern_attributes', - __( 'Pattern Attributes' ), - 'pattern_source_callback' + array( + __( 'Pattern Attributes' ), + 'pattern_source_callback', + ) ); diff --git a/src/wp-includes/block-bindings/sources/post-meta.php b/src/wp-includes/block-bindings/sources/post-meta.php index f03f00afde055..f4302d2e52e41 100644 --- a/src/wp-includes/block-bindings/sources/post-meta.php +++ b/src/wp-includes/block-bindings/sources/post-meta.php @@ -19,6 +19,8 @@ function post_meta_source_callback( $source_attrs ) { wp_block_bindings_register_source( 'post_meta', - __( 'Post Meta' ), - 'post_meta_source_callback' + array( + __( 'Post Meta' ), + 'post_meta_source_callback', + ), ); diff --git a/tests/phpunit/tests/block-bindings.php b/tests/phpunit/tests/block-bindings.php index 28015c2da0b1a..1f59b4b96fa91 100644 --- a/tests/phpunit/tests/block-bindings.php +++ b/tests/phpunit/tests/block-bindings.php @@ -23,7 +23,7 @@ public function test_register_source() { $label = 'Test Source'; $apply = function () { }; - $wp_block_bindings->register_source( $source_name, $label, $apply ); + $wp_block_bindings->register_source( $source_name, array( $label, $apply ) ); $sources = $wp_block_bindings->get_sources(); $this->assertArrayHasKey( $source_name, $sources ); @@ -117,7 +117,7 @@ public function test_post_meta_bindings_source() { $apply = function () { return 'test source value'; }; - $wp_block_bindings->register_source( $source_name, $label, $apply ); + $wp_block_bindings->register_source( $source_name, array( $label, $apply ) ); $block_content = '

This content will be overriden

'; From d1b0d2e82fc94ba694463fd8af408a965479a3cd Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Tue, 23 Jan 2024 16:00:33 +0000 Subject: [PATCH 18/72] Add type hints for `register_source()` & `replace_html()` --- src/wp-includes/block-bindings/block-bindings.php | 4 ++-- src/wp-includes/block-bindings/class-wp-block-bindings.php | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/wp-includes/block-bindings/block-bindings.php b/src/wp-includes/block-bindings/block-bindings.php index 92b18dd8f0993..d009b71676e31 100644 --- a/src/wp-includes/block-bindings/block-bindings.php +++ b/src/wp-includes/block-bindings/block-bindings.php @@ -45,7 +45,7 @@ function wp_block_bindings() { * * @return void */ -function wp_block_bindings_register_source( $source_name, $source_args ) { +function wp_block_bindings_register_source( string $source_name, array $source_args ) { wp_block_bindings()->register_source( $source_name, $source_args ); } @@ -73,6 +73,6 @@ function wp_block_bindings_get_sources() { * @param string $source_value The value used to replace the HTML. * @return string The modified block content. */ -function wp_block_bindings_replace_html( $block_content, $block_name, $block_attr, $source_value ) { +function wp_block_bindings_replace_html( string $block_content, string $block_name, string $block_attr, string $source_value ) { return wp_block_bindings()->replace_html( $block_content, $block_name, $block_attr, $source_value ); } diff --git a/src/wp-includes/block-bindings/class-wp-block-bindings.php b/src/wp-includes/block-bindings/class-wp-block-bindings.php index 537140282d502..909c162968c7c 100644 --- a/src/wp-includes/block-bindings/class-wp-block-bindings.php +++ b/src/wp-includes/block-bindings/class-wp-block-bindings.php @@ -50,7 +50,7 @@ class WP_Block_Bindings { * * @return void */ - public function register_source( $source_name, array $source_args ) { + public function register_source( string $source_name, array $source_args ) { $this->sources[ $source_name ] = $source_args; } @@ -64,7 +64,7 @@ public function register_source( $source_name, array $source_args ) { * @param string $block_attr The attribute of the block we want to process. * @param string $source_value The value used to replace the HTML. */ - public function replace_html( $block_content, $block_name, $block_attr, $source_value ) { + public function replace_html( string $block_content, string $block_name, string $block_attr, string $source_value ) { $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block_name ); if ( null === $block_type || ! isset( $block_type->attributes[ $block_attr ] ) ) { return $block_content; From 9f2274d354b42ebed7d510edee596d9f1085d22d Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Tue, 23 Jan 2024 16:13:16 +0000 Subject: [PATCH 19/72] Add the missing array keys --- src/wp-includes/block-bindings/sources/pattern.php | 4 ++-- src/wp-includes/block-bindings/sources/post-meta.php | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/wp-includes/block-bindings/sources/pattern.php b/src/wp-includes/block-bindings/sources/pattern.php index c9c6c52190540..c32306e52a64f 100644 --- a/src/wp-includes/block-bindings/sources/pattern.php +++ b/src/wp-includes/block-bindings/sources/pattern.php @@ -17,7 +17,7 @@ function pattern_source_callback( $source_attrs, $block_instance, $attribute_nam wp_block_bindings_register_source( 'pattern_attributes', array( - __( 'Pattern Attributes' ), - 'pattern_source_callback', + 'label' => __( 'Pattern Attributes' ), + 'apply' => 'pattern_source_callback', ) ); diff --git a/src/wp-includes/block-bindings/sources/post-meta.php b/src/wp-includes/block-bindings/sources/post-meta.php index f4302d2e52e41..6ae7a0d9cb81a 100644 --- a/src/wp-includes/block-bindings/sources/post-meta.php +++ b/src/wp-includes/block-bindings/sources/post-meta.php @@ -20,7 +20,7 @@ function post_meta_source_callback( $source_attrs ) { wp_block_bindings_register_source( 'post_meta', array( - __( 'Post Meta' ), - 'post_meta_source_callback', + 'label' => __( 'Post Meta' ), + 'apply' => 'post_meta_source_callback', ), ); From 02f0ea353dd1143a5de9f317175fcc87e299c4b4 Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Tue, 23 Jan 2024 17:50:06 +0000 Subject: [PATCH 20/72] Apply https://github.com/WordPress/gutenberg/pull/58055/ --- src/wp-includes/block-bindings/class-wp-block-bindings.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wp-includes/block-bindings/class-wp-block-bindings.php b/src/wp-includes/block-bindings/class-wp-block-bindings.php index 909c162968c7c..614a45495a4b8 100644 --- a/src/wp-includes/block-bindings/class-wp-block-bindings.php +++ b/src/wp-includes/block-bindings/class-wp-block-bindings.php @@ -112,7 +112,7 @@ public function replace_html( string $block_content, string $block_name, string foreach ( $selector_attribute_names as $name ) { $selector_attrs[ $name ] = $block_reader->get_attribute( $name ); } - $selector_markup = "<$selector>" . esc_html( $source_value ) . ""; + $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 ) { From 9303e02f1c755256fd5011250cd754ed6bf9ccd4 Mon Sep 17 00:00:00 2001 From: Ricardo Artemio Morales Date: Wed, 24 Jan 2024 23:46:59 -0500 Subject: [PATCH 21/72] Make processing of bindings private to the class --- .../block-bindings/block-bindings.php | 17 +-- .../class-wp-block-bindings.php | 112 +++++++++++++++++- src/wp-includes/blocks.php | 95 --------------- src/wp-includes/class-wp-block.php | 2 +- tests/phpunit/tests/block-bindings.php | 2 +- 5 files changed, 122 insertions(+), 106 deletions(-) diff --git a/src/wp-includes/block-bindings/block-bindings.php b/src/wp-includes/block-bindings/block-bindings.php index d009b71676e31..99e06aea7a4f5 100644 --- a/src/wp-includes/block-bindings/block-bindings.php +++ b/src/wp-includes/block-bindings/block-bindings.php @@ -63,16 +63,17 @@ function wp_block_bindings_get_sources() { /** - * Replaces the HTML content of a block based on the provided source value. +* Wrapper for the WP_Block_Bindings process method, which is used + * process mappings between an attribute of a block and a source. + * Please see the WP_Block_Bindings::process method for more details. * + * @access public * @since 6.5.0 * - * @param string $block_content Block content. - * @param string $block_name The name of the block to process. - * @param string $block_attr The attribute of the block we want to process. - * @param string $source_value The value used to replace the HTML. - * @return string The modified block content. + * @param string $block_content Block content. + * @param array $block The full block, including name and attributes. + * @param WP_Block $block_instance The block instance. */ -function wp_block_bindings_replace_html( string $block_content, string $block_name, string $block_attr, string $source_value ) { - return wp_block_bindings()->replace_html( $block_content, $block_name, $block_attr, $source_value ); +function wp_block_bindings_process_bindings( $block_content, $block, $block_instance ) { + return wp_block_bindings()->process_bindings( $block_content, $block, $block_instance ); } diff --git a/src/wp-includes/block-bindings/class-wp-block-bindings.php b/src/wp-includes/block-bindings/class-wp-block-bindings.php index 614a45495a4b8..09edb5a02fc4a 100644 --- a/src/wp-includes/block-bindings/class-wp-block-bindings.php +++ b/src/wp-includes/block-bindings/class-wp-block-bindings.php @@ -54,6 +54,99 @@ public function register_source( string $source_name, array $source_args ) { $this->sources[ $source_name ] = $source_args; } + /** + * Processes the block bindings in block's attributes. + * + * A block might contain bindings in its attributes. Bindings are mappings + * between an attribute of the block and a source. A "source" is a function + * registered with `wp_block_bindings_register_source()` that defines how to + * retrieve a value from outside the block, e.g. from post meta. + * + * This function will process those bindings and replace the HTML with the value of the binding. + * The value is retrieved from the source of the binding. + * + * ### Example + * + * The "bindings" property for an Image block might look like this: + * + * ```json + * { + * "metadata": { + * "bindings": { + * "title": { + * "source": { + * "name": "post_meta", + * "attributes": { "value": "text_custom_field" } + * } + * }, + * "url": { + * "source": { + * "name": "post_meta", + * "attributes": { "value": "url_custom_field" } + * } + * } + * } + * } + * } + * ``` + * + * The above example will replace the `title` and `url` attributes of the Image + * block with the values of the `text_custom_field` and `url_custom_field` post meta. + * + * @access private + * @since 6.5.0 + * + * @param string $block_content Block content. + * @param array $block The full block, including name and attributes. + * @param WP_Block $block_instance The block instance. + */ + private function process( $block_content, $block, $block_instance ) { + + // Allowed blocks that support block bindings. + // TODO: Look for a mechanism to opt-in for this. Maybe adding a property to block attributes? + $allowed_blocks = array( + 'core/paragraph' => array( 'content' ), + 'core/heading' => array( 'content' ), + 'core/image' => array( 'url', 'title', 'alt' ), + 'core/button' => array( 'url', 'text' ), + ); + + // If the block doesn't have the bindings property or isn't one of the allowed block types, return. + if ( ! isset( $block['attrs']['metadata']['bindings'] ) || ! isset( $allowed_blocks[ $block_instance->name ] ) ) { + return $block_content; + } + + $modified_block_content = $block_content; + foreach ( $block['attrs']['metadata']['bindings'] as $binding_attribute => $binding_source ) { + + // If the attribute is not in the list, process next attribute. + if ( ! in_array( $binding_attribute, $allowed_blocks[ $block_instance->name ], true ) ) { + continue; + } + // If no source is provided, or that source is not registered, process next attribute. + if ( ! isset( $binding_source['source'] ) || ! isset( $binding_source['source']['name'] ) || ! isset( $this->sources[ $binding_source['source']['name'] ] ) ) { + continue; + } + + $source_callback = $this->sources[ $binding_source['source']['name'] ]['apply']; + // Get the value based on the source. + if ( ! isset( $binding_source['source']['attributes'] ) ) { + $source_args = array(); + } else { + $source_args = $binding_source['source']['attributes']; + } + $source_value = $source_callback( $source_args, $block_instance, $binding_attribute ); + // If the value is null, process next attribute. + if ( is_null( $source_value ) ) { + continue; + } + + // Process the HTML based on the block and the attribute. + $modified_block_content = $this->replace_html( $modified_block_content, $block_instance->name, $binding_attribute, $source_value ); + } + return $modified_block_content; + } + /** * Depending on the block attributes, replace the HTML based on the value returned by the source. * @@ -64,7 +157,7 @@ public function register_source( string $source_name, array $source_args ) { * @param string $block_attr The attribute of the block we want to process. * @param string $source_value The value used to replace the HTML. */ - public function replace_html( string $block_content, string $block_name, string $block_attr, string $source_value ) { + private function replace_html( string $block_content, string $block_name, string $block_attr, string $source_value ) { $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block_name ); if ( null === $block_type || ! isset( $block_type->attributes[ $block_attr ] ) ) { return $block_content; @@ -168,4 +261,21 @@ public function replace_html( string $block_content, string $block_name, string public function get_sources() { return $this->sources; } + + /** + * Wrapper for the WP_Block_Bindings process method, which is used + * process mappings between an attribute of a block and a source. + * Please see the WP_Block_Bindings::process method for more details. + * + * @access public + * @since 6.5.0 + * + * @param string $block_content Block content. + * @param array $block The full block, including name and attributes. + * @param WP_Block $block_instance The block instance. + */ + public function process_bindings( $block_content, $block, $block_instance ) { + return $this->process( $block_content, $block, $block_instance ); + } + } diff --git a/src/wp-includes/blocks.php b/src/wp-includes/blocks.php index be624a7ac9556..431e2b015332c 100644 --- a/src/wp-includes/blocks.php +++ b/src/wp-includes/blocks.php @@ -2083,98 +2083,3 @@ function _wp_footnotes_force_filtered_html_on_import_filter( $arg ) { } return $arg; } - - -/** - * Processes the block bindings in block's attributes. - * - * A block might contain bindings in its attributes. Bindings are mappings - * between an attribute of the block and a source. A "source" is a function - * registered with `wp_block_bindings_register_source()` that defines how to - * retrieve a value from outside the block, e.g. from post meta. - * - * This function will process those bindings and replace the HTML with the value of the binding. - * The value is retrieved from the source of the binding. - * - * ### Example - * - * The "bindings" property for an Image block might look like this: - * - * ```json - * { - * "metadata": { - * "bindings": { - * "title": { - * "source": { - * "name": "post_meta", - * "attributes": { "value": "text_custom_field" } - * } - * }, - * "url": { - * "source": { - * "name": "post_meta", - * "attributes": { "value": "url_custom_field" } - * } - * } - * } - * } - * } - * ``` - * - * The above example will replace the `title` and `url` attributes of the Image - * block with the values of the `text_custom_field` and `url_custom_field` post meta. - * - * @access private - * @since 6.5.0 - * - * @param string $block_content Block content. - * @param array $block The full block, including name and attributes. - * @param WP_Block $block_instance The block instance. - */ -function _process_block_bindings( $block_content, $block, $block_instance ) { - - // Allowed blocks that support block bindings. - // TODO: Look for a mechanism to opt-in for this. Maybe adding a property to block attributes? - $allowed_blocks = array( - 'core/paragraph' => array( 'content' ), - 'core/heading' => array( 'content' ), - 'core/image' => array( 'url', 'title', 'alt' ), - 'core/button' => array( 'url', 'text' ), - ); - - // If the block doesn't have the bindings property or isn't one of the allowed block types, return. - if ( ! isset( $block['attrs']['metadata']['bindings'] ) || ! isset( $allowed_blocks[ $block_instance->name ] ) ) { - return $block_content; - } - - $block_bindings_sources = wp_block_bindings_get_sources(); - $modified_block_content = $block_content; - foreach ( $block['attrs']['metadata']['bindings'] as $binding_attribute => $binding_source ) { - - // If the attribute is not in the list, process next attribute. - if ( ! in_array( $binding_attribute, $allowed_blocks[ $block_instance->name ], true ) ) { - continue; - } - // If no source is provided, or that source is not registered, process next attribute. - if ( ! isset( $binding_source['source'] ) || ! isset( $binding_source['source']['name'] ) || ! isset( $block_bindings_sources[ $binding_source['source']['name'] ] ) ) { - continue; - } - - $source_callback = $block_bindings_sources[ $binding_source['source']['name'] ]['apply']; - // Get the value based on the source. - if ( ! isset( $binding_source['source']['attributes'] ) ) { - $source_args = array(); - } else { - $source_args = $binding_source['source']['attributes']; - } - $source_value = $source_callback( $source_args, $block_instance, $binding_attribute ); - // If the value is null, process next attribute. - if ( is_null( $source_value ) ) { - continue; - } - - // Process the HTML based on the block and the attribute. - $modified_block_content = wp_block_bindings_replace_html( $modified_block_content, $block_instance->name, $binding_attribute, $source_value ); - } - return $modified_block_content; -} diff --git a/src/wp-includes/class-wp-block.php b/src/wp-includes/class-wp-block.php index 89e80c8cfc0e7..9e60ab3d98ef3 100644 --- a/src/wp-includes/class-wp-block.php +++ b/src/wp-includes/class-wp-block.php @@ -282,7 +282,7 @@ public function render( $options = array() ) { // Process the block bindings for this block, if any are registered. This // will replace the block content with the value from a registered binding source. - $block_content = _process_block_bindings( $block_content, $this->parsed_block, $this ); + $block_content = wp_block_bindings_process_bindings( $block_content, $this->parsed_block, $this ); /** * Filters the content of a single block. diff --git a/tests/phpunit/tests/block-bindings.php b/tests/phpunit/tests/block-bindings.php index 1f59b4b96fa91..28587b88ed4d9 100644 --- a/tests/phpunit/tests/block-bindings.php +++ b/tests/phpunit/tests/block-bindings.php @@ -143,7 +143,7 @@ public function test_post_meta_bindings_source() { // Block instance representing a paragraph block. $block_instance = new WP_Block( $block ); - $content = _process_block_bindings( $block_content, $block, $block_instance ); + $content = $wp_block_bindings->process_bindings( $block_content, $block, $block_instance ); $result = '

test source value

'; From 30f4ed76f41be76c0cb929e883176ad98ec11312 Mon Sep 17 00:00:00 2001 From: Ricardo Artemio Morales Date: Thu, 25 Jan 2024 00:02:30 -0500 Subject: [PATCH 22/72] Update class name and method names --- src/wp-includes/block-bindings/block-bindings.php | 10 +++++----- .../block-bindings/class-wp-block-bindings.php | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/wp-includes/block-bindings/block-bindings.php b/src/wp-includes/block-bindings/block-bindings.php index 99e06aea7a4f5..e30acb01766b1 100644 --- a/src/wp-includes/block-bindings/block-bindings.php +++ b/src/wp-includes/block-bindings/block-bindings.php @@ -18,7 +18,7 @@ function wp_block_bindings() { static $instance = null; if ( is_null( $instance ) ) { - $instance = new WP_Block_Bindings(); + $instance = new WP_Block_Bindings_Registry(); } return $instance; } @@ -45,8 +45,8 @@ function wp_block_bindings() { * * @return void */ -function wp_block_bindings_register_source( string $source_name, array $source_args ) { - wp_block_bindings()->register_source( $source_name, $source_args ); +function wp_block_bindings_register_source( $source_name, array $source_args ) { + wp_block_bindings()->register_block_bindings_source( $source_name, $source_args ); } @@ -57,8 +57,8 @@ function wp_block_bindings_register_source( string $source_name, array $source_a * * @return array The list of registered block sources. */ -function wp_block_bindings_get_sources() { - return wp_block_bindings()->get_sources(); +function wp_block_bindings_get_all_registered() { + return wp_block_bindings()->get_all_registered(); } diff --git a/src/wp-includes/block-bindings/class-wp-block-bindings.php b/src/wp-includes/block-bindings/class-wp-block-bindings.php index 09edb5a02fc4a..fe56167ac03e7 100644 --- a/src/wp-includes/block-bindings/class-wp-block-bindings.php +++ b/src/wp-includes/block-bindings/class-wp-block-bindings.php @@ -13,7 +13,7 @@ * * @since 6.5.0 */ -class WP_Block_Bindings { +class WP_Block_Bindings_Registry { /** * Holds the registered block bindings sources, keyed by source identifier. @@ -50,7 +50,7 @@ class WP_Block_Bindings { * * @return void */ - public function register_source( string $source_name, array $source_args ) { + public function register_block_bindings_source( $source_name, array $source_args ) { $this->sources[ $source_name ] = $source_args; } @@ -59,7 +59,7 @@ public function register_source( string $source_name, array $source_args ) { * * A block might contain bindings in its attributes. Bindings are mappings * between an attribute of the block and a source. A "source" is a function - * registered with `wp_block_bindings_register_source()` that defines how to + * registered with `$this->register_block_bindings_source()` that defines how to * retrieve a value from outside the block, e.g. from post meta. * * This function will process those bindings and replace the HTML with the value of the binding. @@ -258,7 +258,7 @@ private function replace_html( string $block_content, string $block_name, string * * @return array The array of registered sources. */ - public function get_sources() { + public function get_all_registered() { return $this->sources; } From 940fb823ca6f9a2710cc7ad9fcc565a68d023661 Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Thu, 25 Jan 2024 13:44:44 +0000 Subject: [PATCH 23/72] Remove the `wp_block_bindings()` function and create a static get_instance() method instead --- .../block-bindings/block-bindings.php | 21 +++------------- .../class-wp-block-bindings.php | 24 +++++++++++++++++++ 2 files changed, 27 insertions(+), 18 deletions(-) diff --git a/src/wp-includes/block-bindings/block-bindings.php b/src/wp-includes/block-bindings/block-bindings.php index e30acb01766b1..842b567c3b366 100644 --- a/src/wp-includes/block-bindings/block-bindings.php +++ b/src/wp-includes/block-bindings/block-bindings.php @@ -8,21 +8,6 @@ * @package WordPress */ -/** - * Retrieves the singleton instance of WP_Block_Bindings. - * - * @since 6.5.0 - * - * @return WP_Block_Bindings The WP_Block_Bindings instance. - */ -function wp_block_bindings() { - static $instance = null; - if ( is_null( $instance ) ) { - $instance = new WP_Block_Bindings_Registry(); - } - return $instance; -} - /** * Registers a new source for block bindings. @@ -46,7 +31,7 @@ function wp_block_bindings() { * @return void */ function wp_block_bindings_register_source( $source_name, array $source_args ) { - wp_block_bindings()->register_block_bindings_source( $source_name, $source_args ); + WP_Block_Bindings_Registry::get_instance()->register_block_bindings_source( $source_name, $source_args ); } @@ -58,7 +43,7 @@ function wp_block_bindings_register_source( $source_name, array $source_args ) { * @return array The list of registered block sources. */ function wp_block_bindings_get_all_registered() { - return wp_block_bindings()->get_all_registered(); + return WP_Block_Bindings_Registry::get_instance()->get_all_registered(); } @@ -75,5 +60,5 @@ function wp_block_bindings_get_all_registered() { * @param WP_Block $block_instance The block instance. */ function wp_block_bindings_process_bindings( $block_content, $block, $block_instance ) { - return wp_block_bindings()->process_bindings( $block_content, $block, $block_instance ); + return WP_Block_Bindings_Registry::get_instance()->process_bindings( $block_content, $block, $block_instance ); } diff --git a/src/wp-includes/block-bindings/class-wp-block-bindings.php b/src/wp-includes/block-bindings/class-wp-block-bindings.php index fe56167ac03e7..bf4e740f3d687 100644 --- a/src/wp-includes/block-bindings/class-wp-block-bindings.php +++ b/src/wp-includes/block-bindings/class-wp-block-bindings.php @@ -24,6 +24,14 @@ class WP_Block_Bindings_Registry { */ private $sources = array(); + /** + * Container for the main instance of the class. + * + * @since 6.5.0 + * @var WP_Block_Bindings_Registry|null + */ + private static $instance = null; + /** * Function to register a new block binding source. * @@ -278,4 +286,20 @@ public function process_bindings( $block_content, $block, $block_instance ) { return $this->process( $block_content, $block, $block_instance ); } + /** + * Utility method to retrieve the main instance of the class. + * + * The instance will be created if it does not exist yet. + * + * @since 6.5.0 + * + * @return WP_Block_Bindings_Registry The WP_Block_Bindings_Registry instance. + */ + public static function get_instance() { + if ( null === self::$instance ) { + self::$instance = new self(); + } + + return self::$instance; + } } From dbda027873ac09114fbe2735c29985d1498b23a5 Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Thu, 25 Jan 2024 15:24:18 +0000 Subject: [PATCH 24/72] Rename `register_block_bindings_source()` to `register()` --- src/wp-includes/block-bindings/block-bindings.php | 2 +- src/wp-includes/block-bindings/class-wp-block-bindings.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/wp-includes/block-bindings/block-bindings.php b/src/wp-includes/block-bindings/block-bindings.php index 842b567c3b366..139c6637d3285 100644 --- a/src/wp-includes/block-bindings/block-bindings.php +++ b/src/wp-includes/block-bindings/block-bindings.php @@ -31,7 +31,7 @@ * @return void */ function wp_block_bindings_register_source( $source_name, array $source_args ) { - WP_Block_Bindings_Registry::get_instance()->register_block_bindings_source( $source_name, $source_args ); + WP_Block_Bindings_Registry::get_instance()->register( $source_name, $source_args ); } diff --git a/src/wp-includes/block-bindings/class-wp-block-bindings.php b/src/wp-includes/block-bindings/class-wp-block-bindings.php index bf4e740f3d687..77d4e747ad7e1 100644 --- a/src/wp-includes/block-bindings/class-wp-block-bindings.php +++ b/src/wp-includes/block-bindings/class-wp-block-bindings.php @@ -58,7 +58,7 @@ class WP_Block_Bindings_Registry { * * @return void */ - public function register_block_bindings_source( $source_name, array $source_args ) { + public function register( $source_name, array $source_args ) { $this->sources[ $source_name ] = $source_args; } @@ -67,7 +67,7 @@ public function register_block_bindings_source( $source_name, array $source_args * * A block might contain bindings in its attributes. Bindings are mappings * between an attribute of the block and a source. A "source" is a function - * registered with `$this->register_block_bindings_source()` that defines how to + * registered with `$this->register()` that defines how to * retrieve a value from outside the block, e.g. from post meta. * * This function will process those bindings and replace the HTML with the value of the binding. From 34d9dd7621b6a8bbc220a5023391d67c304b04ba Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Thu, 25 Jan 2024 16:15:38 +0000 Subject: [PATCH 25/72] Move the process_block_bindings & replace_html methods to the WP_block class --- .../block-bindings/block-bindings.php | 17 -- .../class-wp-block-bindings.php | 213 ------------------ src/wp-includes/class-wp-block.php | 200 +++++++++++++++- 3 files changed, 199 insertions(+), 231 deletions(-) diff --git a/src/wp-includes/block-bindings/block-bindings.php b/src/wp-includes/block-bindings/block-bindings.php index 139c6637d3285..56453b097dd12 100644 --- a/src/wp-includes/block-bindings/block-bindings.php +++ b/src/wp-includes/block-bindings/block-bindings.php @@ -45,20 +45,3 @@ function wp_block_bindings_register_source( $source_name, array $source_args ) { function wp_block_bindings_get_all_registered() { return WP_Block_Bindings_Registry::get_instance()->get_all_registered(); } - - -/** -* Wrapper for the WP_Block_Bindings process method, which is used - * process mappings between an attribute of a block and a source. - * Please see the WP_Block_Bindings::process method for more details. - * - * @access public - * @since 6.5.0 - * - * @param string $block_content Block content. - * @param array $block The full block, including name and attributes. - * @param WP_Block $block_instance The block instance. - */ -function wp_block_bindings_process_bindings( $block_content, $block, $block_instance ) { - return WP_Block_Bindings_Registry::get_instance()->process_bindings( $block_content, $block, $block_instance ); -} diff --git a/src/wp-includes/block-bindings/class-wp-block-bindings.php b/src/wp-includes/block-bindings/class-wp-block-bindings.php index 77d4e747ad7e1..6630e1b0053a5 100644 --- a/src/wp-includes/block-bindings/class-wp-block-bindings.php +++ b/src/wp-includes/block-bindings/class-wp-block-bindings.php @@ -62,203 +62,6 @@ public function register( $source_name, array $source_args ) { $this->sources[ $source_name ] = $source_args; } - /** - * Processes the block bindings in block's attributes. - * - * A block might contain bindings in its attributes. Bindings are mappings - * between an attribute of the block and a source. A "source" is a function - * registered with `$this->register()` that defines how to - * retrieve a value from outside the block, e.g. from post meta. - * - * This function will process those bindings and replace the HTML with the value of the binding. - * The value is retrieved from the source of the binding. - * - * ### Example - * - * The "bindings" property for an Image block might look like this: - * - * ```json - * { - * "metadata": { - * "bindings": { - * "title": { - * "source": { - * "name": "post_meta", - * "attributes": { "value": "text_custom_field" } - * } - * }, - * "url": { - * "source": { - * "name": "post_meta", - * "attributes": { "value": "url_custom_field" } - * } - * } - * } - * } - * } - * ``` - * - * The above example will replace the `title` and `url` attributes of the Image - * block with the values of the `text_custom_field` and `url_custom_field` post meta. - * - * @access private - * @since 6.5.0 - * - * @param string $block_content Block content. - * @param array $block The full block, including name and attributes. - * @param WP_Block $block_instance The block instance. - */ - private function process( $block_content, $block, $block_instance ) { - - // Allowed blocks that support block bindings. - // TODO: Look for a mechanism to opt-in for this. Maybe adding a property to block attributes? - $allowed_blocks = array( - 'core/paragraph' => array( 'content' ), - 'core/heading' => array( 'content' ), - 'core/image' => array( 'url', 'title', 'alt' ), - 'core/button' => array( 'url', 'text' ), - ); - - // If the block doesn't have the bindings property or isn't one of the allowed block types, return. - if ( ! isset( $block['attrs']['metadata']['bindings'] ) || ! isset( $allowed_blocks[ $block_instance->name ] ) ) { - return $block_content; - } - - $modified_block_content = $block_content; - foreach ( $block['attrs']['metadata']['bindings'] as $binding_attribute => $binding_source ) { - - // If the attribute is not in the list, process next attribute. - if ( ! in_array( $binding_attribute, $allowed_blocks[ $block_instance->name ], true ) ) { - continue; - } - // If no source is provided, or that source is not registered, process next attribute. - if ( ! isset( $binding_source['source'] ) || ! isset( $binding_source['source']['name'] ) || ! isset( $this->sources[ $binding_source['source']['name'] ] ) ) { - continue; - } - - $source_callback = $this->sources[ $binding_source['source']['name'] ]['apply']; - // Get the value based on the source. - if ( ! isset( $binding_source['source']['attributes'] ) ) { - $source_args = array(); - } else { - $source_args = $binding_source['source']['attributes']; - } - $source_value = $source_callback( $source_args, $block_instance, $binding_attribute ); - // If the value is null, process next attribute. - if ( is_null( $source_value ) ) { - continue; - } - - // Process the HTML based on the block and the attribute. - $modified_block_content = $this->replace_html( $modified_block_content, $block_instance->name, $binding_attribute, $source_value ); - } - return $modified_block_content; - } - - /** - * Depending on the block attributes, replace the HTML based on the value returned by the source. - * - * @since 6.5.0 - * - * @param string $block_content Block content. - * @param string $block_name The name of the block to process. - * @param string $block_attr The attribute of the block we want to process. - * @param string $source_value The value used to replace the HTML. - */ - private function replace_html( string $block_content, string $block_name, string $block_attr, string $source_value ) { - $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block_name ); - if ( null === $block_type || ! isset( $block_type->attributes[ $block_attr ] ) ) { - return $block_content; - } - - // Depending on the attribute source, the processing will be different. - switch ( $block_type->attributes[ $block_attr ]['source'] ) { - case 'html': - case 'rich-text': - $block_reader = new WP_HTML_Tag_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. - $selectors = explode( ',', $block_type->attributes[ $block_attr ]['selector'] ); - // Add a bookmark to the first tag to be able to iterate over the selectors. - $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' === $block_name ) { - $button_wrapper = $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 ), $selector ) === 0 || $block_reader->next_tag( - array( - 'tag_name' => $selector, - ) - ) ) { - $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' === $block_name || 'core/heading' === $block_name ) { - return $amended_content->get_updated_html(); - } - if ( 'core/button' === $block_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(); - } - } else { - $block_reader->seek( 'iterate-selectors' ); - } - } - $block_reader->release_bookmark( 'iterate-selectors' ); - return $block_content; - - case 'attribute': - $amended_content = new WP_HTML_Tag_Processor( $block_content ); - if ( ! $amended_content->next_tag( - array( - // TODO: build the query from CSS selector. - 'tag_name' => $block_type->attributes[ $block_attr ]['selector'], - ) - ) ) { - return $block_content; - } - $amended_content->set_attribute( $block_type->attributes[ $block_attr ]['attribute'], esc_attr( $source_value ) ); - return $amended_content->get_updated_html(); - break; - - default: - return $block_content; - break; - } - return; - } - /** * Retrieves the list of registered block sources. * @@ -270,22 +73,6 @@ public function get_all_registered() { return $this->sources; } - /** - * Wrapper for the WP_Block_Bindings process method, which is used - * process mappings between an attribute of a block and a source. - * Please see the WP_Block_Bindings::process method for more details. - * - * @access public - * @since 6.5.0 - * - * @param string $block_content Block content. - * @param array $block The full block, including name and attributes. - * @param WP_Block $block_instance The block instance. - */ - public function process_bindings( $block_content, $block, $block_instance ) { - return $this->process( $block_content, $block, $block_instance ); - } - /** * Utility method to retrieve the main instance of the class. * diff --git a/src/wp-includes/class-wp-block.php b/src/wp-includes/class-wp-block.php index 9e60ab3d98ef3..2b7553c166619 100644 --- a/src/wp-includes/class-wp-block.php +++ b/src/wp-includes/class-wp-block.php @@ -191,6 +191,204 @@ public function __get( $name ) { return null; } + /** + * Processes the block bindings in block's attributes. + * + * A block might contain bindings in its attributes. Bindings are mappings + * between an attribute of the block and a source. A "source" is a function + * registered with `$this->register()` that defines how to + * retrieve a value from outside the block, e.g. from post meta. + * + * This function will process those bindings and replace the HTML with the value of the binding. + * The value is retrieved from the source of the binding. + * + * ### Example + * + * The "bindings" property for an Image block might look like this: + * + * ```json + * { + * "metadata": { + * "bindings": { + * "title": { + * "source": { + * "name": "post_meta", + * "attributes": { "value": "text_custom_field" } + * } + * }, + * "url": { + * "source": { + * "name": "post_meta", + * "attributes": { "value": "url_custom_field" } + * } + * } + * } + * } + * } + * ``` + * + * The above example will replace the `title` and `url` attributes of the Image + * block with the values of the `text_custom_field` and `url_custom_field` post meta. + * + * @access private + * @since 6.5.0 + * + * @param string $block_content Block content. + * @param array $block The full block, including name and attributes. + */ + private function process_block_bindings( $block_content ) { + $block = $this->parsed_block; + + // Allowed blocks that support block bindings. + // TODO: Look for a mechanism to opt-in for this. Maybe adding a property to block attributes? + $allowed_blocks = array( + 'core/paragraph' => array( 'content' ), + 'core/heading' => array( 'content' ), + 'core/image' => array( 'url', 'title', 'alt' ), + 'core/button' => array( 'url', 'text' ), + ); + + // If the block doesn't have the bindings property or isn't one of the allowed block types, return. + if ( ! isset( $block['attrs']['metadata']['bindings'] ) || ! isset( $allowed_blocks[ $this->name ] ) ) { + return $block_content; + } + + $modified_block_content = $block_content; + foreach ( $block['attrs']['metadata']['bindings'] as $binding_attribute => $binding_source ) { + + // If the attribute is not in the list, process next attribute. + if ( ! in_array( $binding_attribute, $allowed_blocks[ $this->name ], true ) ) { + continue; + } + // If no source is provided, or that source is not registered, process next attribute. + if ( ! isset( $binding_source['source'] ) || ! isset( $binding_source['source']['name'] ) || ! isset( $this->sources[ $binding_source['source']['name'] ] ) ) { + continue; + } + + $source_callback = $this->sources[ $binding_source['source']['name'] ]['apply']; + // Get the value based on the source. + if ( ! isset( $binding_source['source']['attributes'] ) ) { + $source_args = array(); + } else { + $source_args = $binding_source['source']['attributes']; + } + $source_value = $source_callback( $source_args, $this, $binding_attribute ); + // If the value is null, process next attribute. + if ( is_null( $source_value ) ) { + continue; + } + + // Process the HTML based on the block and the attribute. + $modified_block_content = $this->replace_html( $modified_block_content, $this->name, $binding_attribute, $source_value ); + } + return $modified_block_content; + } + + /** + * Depending on the block attributes, replace the HTML based on the value returned by the source. + * + * @since 6.5.0 + * + * @param string $block_content Block content. + * @param string $block_name The name of the block to process. + * @param string $block_attr The attribute of the block we want to process. + * @param string $source_value The value used to replace the HTML. + */ + private function replace_html( string $block_content, string $block_name, string $block_attr, string $source_value ) { + $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block_name ); + if ( null === $block_type || ! isset( $block_type->attributes[ $block_attr ] ) ) { + return $block_content; + } + + // Depending on the attribute source, the processing will be different. + switch ( $block_type->attributes[ $block_attr ]['source'] ) { + case 'html': + case 'rich-text': + $block_reader = new WP_HTML_Tag_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. + $selectors = explode( ',', $block_type->attributes[ $block_attr ]['selector'] ); + // Add a bookmark to the first tag to be able to iterate over the selectors. + $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' === $block_name ) { + $button_wrapper = $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 ), $selector ) === 0 || $block_reader->next_tag( + array( + 'tag_name' => $selector, + ) + ) ) { + $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' === $block_name || 'core/heading' === $block_name ) { + return $amended_content->get_updated_html(); + } + if ( 'core/button' === $block_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(); + } + } else { + $block_reader->seek( 'iterate-selectors' ); + } + } + $block_reader->release_bookmark( 'iterate-selectors' ); + return $block_content; + + case 'attribute': + $amended_content = new WP_HTML_Tag_Processor( $block_content ); + if ( ! $amended_content->next_tag( + array( + // TODO: build the query from CSS selector. + 'tag_name' => $block_type->attributes[ $block_attr ]['selector'], + ) + ) ) { + return $block_content; + } + $amended_content->set_attribute( $block_type->attributes[ $block_attr ]['attribute'], esc_attr( $source_value ) ); + return $amended_content->get_updated_html(); + break; + + default: + return $block_content; + break; + } + return; + } + + /** * Generates the render output for the block. * @@ -282,7 +480,7 @@ public function render( $options = array() ) { // Process the block bindings for this block, if any are registered. This // will replace the block content with the value from a registered binding source. - $block_content = wp_block_bindings_process_bindings( $block_content, $this->parsed_block, $this ); + $block_content = $this->process_block_bindings( $block_content ); /** * Filters the content of a single block. From 74bfae768b95404dc3fcb15c24cbbb0f488d802d Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Thu, 25 Jan 2024 16:34:06 +0000 Subject: [PATCH 26/72] Rename WP_Block_Bindings to WP_Block_Bindings_Registry in missing places --- .../class-wp-block-bindings.php | 2 +- tests/phpunit/tests/block-bindings.php | 30 +++++++++---------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/wp-includes/block-bindings/class-wp-block-bindings.php b/src/wp-includes/block-bindings/class-wp-block-bindings.php index 6630e1b0053a5..7e15e5883558d 100644 --- a/src/wp-includes/block-bindings/class-wp-block-bindings.php +++ b/src/wp-includes/block-bindings/class-wp-block-bindings.php @@ -1,6 +1,6 @@ register_source( $source_name, array( $label, $apply ) ); + $wp_block_bindings->register( $source_name, array( $label, $apply ) ); - $sources = $wp_block_bindings->get_sources(); + $sources = $wp_block_bindings->get_all_registered(); $this->assertArrayHasKey( $source_name, $sources ); $this->assertEquals( $label, $sources[ $source_name ]['label'], 'The label should match the one in the registered source' ); $this->assertEquals( $apply, $sources[ $source_name ]['apply'], 'The apply callback should match the one in the registered source' ); @@ -34,10 +34,10 @@ public function test_register_source() { /** * Test replace_html method for content. * - * @covers WP_Block_Bindings::replace_html + * @covers WP_Block_Bindings_Registry::replace_html */ public function test_replace_html_for_paragraph_content() { - $wp_block_bindings = new WP_Block_Bindings(); + $wp_block_bindings = new WP_Block_Bindings_Registry(); $block_content = '

Hello World

'; $block_name = 'core/paragraph'; @@ -53,10 +53,10 @@ public function test_replace_html_for_paragraph_content() { /** * Test replace_html method for attributes. * - * @covers WP_Block_Bindings::replace_html + * @covers WP_Block_Bindings_Registry::replace_html */ public function test_replace_html_for_attribute() { - $wp_block_bindings = new WP_Block_Bindings(); + $wp_block_bindings = new WP_Block_Bindings_Registry(); $block_content = ''; $block_name = 'core/button'; $block_attr = 'url'; @@ -69,10 +69,10 @@ public function test_replace_html_for_attribute() { /** * Test case for scenarios where block type is not registered. * - * @covers WP_Block_Bindings::replace_html + * @covers WP_Block_Bindings_Registry::replace_html */ public function test_replace_html_with_unregistered_block() { - $wp_block_bindings = new WP_Block_Bindings(); + $wp_block_bindings = new WP_Block_Bindings_Registry(); $block_content = '

Hello World

'; $block_name = 'NONEXISTENT'; @@ -88,10 +88,10 @@ public function test_replace_html_with_unregistered_block() { * Test case for scenarios where block is registered but attribute does not * exist on block type. * - * @covers WP_Block_Bindings::replace_html + * @covers WP_Block_Bindings_Registry::replace_html */ public function test_replace_html_with_registered_block_but_unsupported_source_type() { - $wp_block_bindings = new WP_Block_Bindings(); + $wp_block_bindings = new WP_Block_Bindings_Registry(); $block_content = '
Hello World
'; $block_name = 'core/paragraph'; From 013535e4d94116fe1a65edb7d5f68ce3e4d05f4c Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Thu, 25 Jan 2024 16:35:12 +0000 Subject: [PATCH 27/72] Rename class-wp-block-bindings.php to class-wp-block-bindings-registry.php --- ...wp-block-bindings.php => class-wp-block-bindings-registry.php} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/wp-includes/block-bindings/{class-wp-block-bindings.php => class-wp-block-bindings-registry.php} (100%) diff --git a/src/wp-includes/block-bindings/class-wp-block-bindings.php b/src/wp-includes/block-bindings/class-wp-block-bindings-registry.php similarity index 100% rename from src/wp-includes/block-bindings/class-wp-block-bindings.php rename to src/wp-includes/block-bindings/class-wp-block-bindings-registry.php From 89e9833fbf2b796326bc23fa7706132d4314726d Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Thu, 25 Jan 2024 19:09:11 +0000 Subject: [PATCH 28/72] Rename $source_args to $source_properties to align with other similar functions --- src/wp-includes/block-bindings/block-bindings.php | 6 +++--- .../block-bindings/class-wp-block-bindings-registry.php | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/wp-includes/block-bindings/block-bindings.php b/src/wp-includes/block-bindings/block-bindings.php index 56453b097dd12..82694cb3f1738 100644 --- a/src/wp-includes/block-bindings/block-bindings.php +++ b/src/wp-includes/block-bindings/block-bindings.php @@ -15,7 +15,7 @@ * @since 6.5.0 * * @param string $source_name The name of the source. - * @param array $source_args The array of arguments that are used to register a source. The array has two elements: + * @param array $source_properties The array of arguments that are used to register a source. The array has two elements: * 1. string $label The label of the source. * 2. callback $apply A callback * executed when the source is processed during @@ -30,8 +30,8 @@ * * @return void */ -function wp_block_bindings_register_source( $source_name, array $source_args ) { - WP_Block_Bindings_Registry::get_instance()->register( $source_name, $source_args ); +function wp_block_bindings_register_source( $source_name, array $source_properties ) { + WP_Block_Bindings_Registry::get_instance()->register( $source_name, $source_properties ); } diff --git a/src/wp-includes/block-bindings/class-wp-block-bindings-registry.php b/src/wp-includes/block-bindings/class-wp-block-bindings-registry.php index 7e15e5883558d..1b1e22957ff16 100644 --- a/src/wp-includes/block-bindings/class-wp-block-bindings-registry.php +++ b/src/wp-includes/block-bindings/class-wp-block-bindings-registry.php @@ -43,7 +43,7 @@ class WP_Block_Bindings_Registry { * @since 6.5.0 * * @param string $source_name The name of the source. - * @param array $source_args The array of arguments that are used to register a source. The array has two elements: + * @param array $source_properties The array of arguments that are used to register a source. The array has two elements: * 1. string $label The label of the source. * 2. callback $apply A callback * executed when the source is processed during @@ -58,8 +58,8 @@ class WP_Block_Bindings_Registry { * * @return void */ - public function register( $source_name, array $source_args ) { - $this->sources[ $source_name ] = $source_args; + public function register( $source_name, array $source_properties ) { + $this->sources[ $source_name ] = $source_properties; } /** From 0c931e05492e98d7a3f7c91cac6e081d3f1470ac Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Thu, 25 Jan 2024 19:11:15 +0000 Subject: [PATCH 29/72] Add `is_registered()` & `get_registered()` --- .../class-wp-block-bindings-registry.php | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/src/wp-includes/block-bindings/class-wp-block-bindings-registry.php b/src/wp-includes/block-bindings/class-wp-block-bindings-registry.php index 1b1e22957ff16..e6e91cfa10bb2 100644 --- a/src/wp-includes/block-bindings/class-wp-block-bindings-registry.php +++ b/src/wp-includes/block-bindings/class-wp-block-bindings-registry.php @@ -63,7 +63,7 @@ public function register( $source_name, array $source_properties ) { } /** - * Retrieves the list of registered block sources. + * Retrieves the list of registered block bindings sources. * * @since 6.5.0 * @@ -73,6 +73,34 @@ public function get_all_registered() { return $this->sources; } + /** + * Retrieves a registered block bindings source. + * + * @since 6.5.0 + * + * @param string $source_name The name of the source. + * @return bool True if the source is registered, false otherwise. + */ + public function get_registered( $source_name ) { + if ( ! $this->is_registered( $source_name ) ) { + return null; + } + + return $this->sources[ $source_name ]; + } + + /** + * Checks if a block source is registered. + * + * @since 6.5.0 + * + * @param string $source_name The name of the source. + * @return bool True if the source is registered, false otherwise. + */ + public function is_registered( $source_name ) { + return isset( $this->sources[ $source_name ] ); + } + /** * Utility method to retrieve the main instance of the class. * From 7b1ebe29b1dc881ef1c2f0e65f98dfb4fc901676 Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Thu, 25 Jan 2024 19:17:18 +0000 Subject: [PATCH 30/72] Register block bindings on 'init' action hook --- src/wp-includes/block-bindings/block-bindings.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/wp-includes/block-bindings/block-bindings.php b/src/wp-includes/block-bindings/block-bindings.php index 82694cb3f1738..397413261b6d8 100644 --- a/src/wp-includes/block-bindings/block-bindings.php +++ b/src/wp-includes/block-bindings/block-bindings.php @@ -31,10 +31,14 @@ * @return void */ function wp_block_bindings_register_source( $source_name, array $source_properties ) { - WP_Block_Bindings_Registry::get_instance()->register( $source_name, $source_properties ); + add_action( + 'init', + function () use ( $source_name, $source_properties ) { + WP_Block_Bindings_Registry::get_instance()->register( $source_name, $source_properties ); + } + ); } - /** * Retrieves the list of registered block sources. * From e0b388367104b6c20a30d14ca19afeb7700e121c Mon Sep 17 00:00:00 2001 From: Ricardo Artemio Morales Date: Fri, 26 Jan 2024 15:32:46 -0500 Subject: [PATCH 31/72] Fix incorrect loading of block bindings class --- src/wp-settings.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wp-settings.php b/src/wp-settings.php index 08e8a008de604..f7d1b4ce7a32b 100644 --- a/src/wp-settings.php +++ b/src/wp-settings.php @@ -375,7 +375,7 @@ require ABSPATH . WPINC . '/class-wp-script-modules.php'; require ABSPATH . WPINC . '/script-modules.php'; require ABSPATH . WPINC . '/block-bindings/block-bindings.php'; -require ABSPATH . WPINC . '/block-bindings/class-wp-block-bindings.php'; +require ABSPATH . WPINC . '/block-bindings/class-wp-block-bindings-registry.php'; require ABSPATH . WPINC . '/block-bindings/sources/post-meta.php'; require ABSPATH . WPINC . '/block-bindings/sources/pattern.php'; From 2900bf53330d355f476bce07f04f36316618b4ee Mon Sep 17 00:00:00 2001 From: Ricardo Artemio Morales Date: Fri, 26 Jan 2024 16:05:04 -0500 Subject: [PATCH 32/72] Add validation to prevent sources from being registered twice --- .../block-bindings/class-wp-block-bindings-registry.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/wp-includes/block-bindings/class-wp-block-bindings-registry.php b/src/wp-includes/block-bindings/class-wp-block-bindings-registry.php index e6e91cfa10bb2..9f72f54a06d96 100644 --- a/src/wp-includes/block-bindings/class-wp-block-bindings-registry.php +++ b/src/wp-includes/block-bindings/class-wp-block-bindings-registry.php @@ -56,10 +56,14 @@ class WP_Block_Bindings_Registry { * - @param string $attribute_name: The name of an attribute used to retrieve an override value from the block context. * The callback should return a string that will be used to override the block's original value. * - * @return void + * @return boolean Whether the registration was successful. */ public function register( $source_name, array $source_properties ) { - $this->sources[ $source_name ] = $source_properties; + if( ! isset( $this->sources[ $source_name ] ) ) { + $this->sources[ $source_name ] = $source_properties; + return true; + } + return false; } /** From 26da23e5fef2adca1aa8ebe69d8e892f5497fceb Mon Sep 17 00:00:00 2001 From: Ricardo Artemio Morales Date: Fri, 26 Jan 2024 16:06:37 -0500 Subject: [PATCH 33/72] Add 'core' namespace to built-in sources --- src/wp-includes/block-bindings/sources/pattern.php | 2 +- src/wp-includes/block-bindings/sources/post-meta.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/wp-includes/block-bindings/sources/pattern.php b/src/wp-includes/block-bindings/sources/pattern.php index c32306e52a64f..bac5fbb946500 100644 --- a/src/wp-includes/block-bindings/sources/pattern.php +++ b/src/wp-includes/block-bindings/sources/pattern.php @@ -15,7 +15,7 @@ function pattern_source_callback( $source_attrs, $block_instance, $attribute_nam } wp_block_bindings_register_source( - 'pattern_attributes', + 'core/pattern_attributes', array( 'label' => __( 'Pattern Attributes' ), 'apply' => 'pattern_source_callback', diff --git a/src/wp-includes/block-bindings/sources/post-meta.php b/src/wp-includes/block-bindings/sources/post-meta.php index 6ae7a0d9cb81a..cc7eb8928841b 100644 --- a/src/wp-includes/block-bindings/sources/post-meta.php +++ b/src/wp-includes/block-bindings/sources/post-meta.php @@ -18,7 +18,7 @@ function post_meta_source_callback( $source_attrs ) { } wp_block_bindings_register_source( - 'post_meta', + 'core/post_meta', array( 'label' => __( 'Post Meta' ), 'apply' => 'post_meta_source_callback', From 29213d82a8afe6be9306ca2b84dfbd96771c3a73 Mon Sep 17 00:00:00 2001 From: Ricardo Artemio Morales Date: Fri, 26 Jan 2024 16:55:11 -0500 Subject: [PATCH 34/72] Fix incorrect reading of sources --- src/wp-includes/class-wp-block.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/wp-includes/class-wp-block.php b/src/wp-includes/class-wp-block.php index 2b7553c166619..4a1e33f856447 100644 --- a/src/wp-includes/class-wp-block.php +++ b/src/wp-includes/class-wp-block.php @@ -253,19 +253,21 @@ private function process_block_bindings( $block_content ) { return $block_content; } + $block_bindings_sources = wp_block_bindings_get_all_registered(); $modified_block_content = $block_content; foreach ( $block['attrs']['metadata']['bindings'] as $binding_attribute => $binding_source ) { + // If the attribute is not in the list, process next attribute. if ( ! in_array( $binding_attribute, $allowed_blocks[ $this->name ], true ) ) { continue; } // If no source is provided, or that source is not registered, process next attribute. - if ( ! isset( $binding_source['source'] ) || ! isset( $binding_source['source']['name'] ) || ! isset( $this->sources[ $binding_source['source']['name'] ] ) ) { + if ( ! isset( $binding_source['source'] ) || ! isset( $binding_source['source']['name'] ) || ! isset( $block_bindings_sources[ $binding_source['source']['name'] ] ) ) { continue; } - $source_callback = $this->sources[ $binding_source['source']['name'] ]['apply']; + $source_callback = $block_bindings_sources[ $binding_source['source']['name'] ]['apply']; // Get the value based on the source. if ( ! isset( $binding_source['source']['attributes'] ) ) { $source_args = array(); From 4c3d892d4098689fb825a89bef19c7e60aed7a94 Mon Sep 17 00:00:00 2001 From: Ricardo Artemio Morales Date: Fri, 26 Jan 2024 16:57:29 -0500 Subject: [PATCH 35/72] Rename 'apply' to 'get_value_callback' --- src/wp-includes/block-bindings/block-bindings.php | 9 ++++----- .../block-bindings/class-wp-block-bindings-registry.php | 9 ++++----- src/wp-includes/block-bindings/sources/pattern.php | 2 +- src/wp-includes/block-bindings/sources/post-meta.php | 2 +- src/wp-includes/class-wp-block.php | 2 +- 5 files changed, 11 insertions(+), 13 deletions(-) diff --git a/src/wp-includes/block-bindings/block-bindings.php b/src/wp-includes/block-bindings/block-bindings.php index 397413261b6d8..cdb84785d60b9 100644 --- a/src/wp-includes/block-bindings/block-bindings.php +++ b/src/wp-includes/block-bindings/block-bindings.php @@ -16,11 +16,10 @@ * * @param string $source_name The name of the source. * @param array $source_properties The array of arguments that are used to register a source. The array has two elements: - * 1. string $label The label of the source. - * 2. callback $apply A callback - * executed when the source is processed during - * block rendering. The callback should have the - * following signature: + * 1. string $label The label of the source. + * 2. callback $get_value_callback A callback executed when + * the source is processed during block rendering. + * The callback should have the following signature: * * `function (object $source_attrs, object $block_instance, string $attribute_name): string` * - @param object $source_attrs: Object containing source ID used to look up the override value, i.e. {"value": "{ID}"}. diff --git a/src/wp-includes/block-bindings/class-wp-block-bindings-registry.php b/src/wp-includes/block-bindings/class-wp-block-bindings-registry.php index 9f72f54a06d96..6c20998a1e46b 100644 --- a/src/wp-includes/block-bindings/class-wp-block-bindings-registry.php +++ b/src/wp-includes/block-bindings/class-wp-block-bindings-registry.php @@ -44,11 +44,10 @@ class WP_Block_Bindings_Registry { * * @param string $source_name The name of the source. * @param array $source_properties The array of arguments that are used to register a source. The array has two elements: - * 1. string $label The label of the source. - * 2. callback $apply A callback - * executed when the source is processed during - * block rendering. The callback should have the - * following signature: + * 1. string $label The label of the source. + * 2. callback $get_value_callback A callback executed when + * the source is processed during block rendering. + * The callback should have the following signature: * * `function (object $source_attrs, object $block_instance, string $attribute_name): string` * - @param object $source_attrs: Object containing source ID used to look up the override value, i.e. {"value": "{ID}"}. diff --git a/src/wp-includes/block-bindings/sources/pattern.php b/src/wp-includes/block-bindings/sources/pattern.php index bac5fbb946500..9e4eafe581dd1 100644 --- a/src/wp-includes/block-bindings/sources/pattern.php +++ b/src/wp-includes/block-bindings/sources/pattern.php @@ -18,6 +18,6 @@ function pattern_source_callback( $source_attrs, $block_instance, $attribute_nam 'core/pattern_attributes', array( 'label' => __( 'Pattern Attributes' ), - 'apply' => 'pattern_source_callback', + 'get_value_callback' => 'pattern_source_callback', ) ); diff --git a/src/wp-includes/block-bindings/sources/post-meta.php b/src/wp-includes/block-bindings/sources/post-meta.php index cc7eb8928841b..811dad6b6349a 100644 --- a/src/wp-includes/block-bindings/sources/post-meta.php +++ b/src/wp-includes/block-bindings/sources/post-meta.php @@ -21,6 +21,6 @@ function post_meta_source_callback( $source_attrs ) { 'core/post_meta', array( 'label' => __( 'Post Meta' ), - 'apply' => 'post_meta_source_callback', + 'get_value_callback' => 'post_meta_source_callback', ), ); diff --git a/src/wp-includes/class-wp-block.php b/src/wp-includes/class-wp-block.php index 4a1e33f856447..9578c9dd4bbdf 100644 --- a/src/wp-includes/class-wp-block.php +++ b/src/wp-includes/class-wp-block.php @@ -267,7 +267,7 @@ private function process_block_bindings( $block_content ) { continue; } - $source_callback = $block_bindings_sources[ $binding_source['source']['name'] ]['apply']; + $source_callback = $this->sources[ $binding_source['source']['name'] ]['get_value_callback']; // Get the value based on the source. if ( ! isset( $binding_source['source']['attributes'] ) ) { $source_args = array(); From 06a39303c26c7c407ccf93c52acbfa218b6b6a77 Mon Sep 17 00:00:00 2001 From: Ricardo Artemio Morales Date: Fri, 26 Jan 2024 17:54:43 -0500 Subject: [PATCH 36/72] Fix incorrect reading of registered sources --- 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 9578c9dd4bbdf..ff834b177957b 100644 --- a/src/wp-includes/class-wp-block.php +++ b/src/wp-includes/class-wp-block.php @@ -267,7 +267,7 @@ private function process_block_bindings( $block_content ) { continue; } - $source_callback = $this->sources[ $binding_source['source']['name'] ]['get_value_callback']; + $source_callback = $block_bindings_sources[ $binding_source['source']['name'] ]['get_value_callback']; // Get the value based on the source. if ( ! isset( $binding_source['source']['attributes'] ) ) { $source_args = array(); From f53121da39de7fb94ba0e72878d5d61930016461 Mon Sep 17 00:00:00 2001 From: Ricardo Artemio Morales Date: Fri, 26 Jan 2024 17:55:05 -0500 Subject: [PATCH 37/72] Ensure unauthorized users don't read post meta --- src/wp-includes/block-bindings/sources/post-meta.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/wp-includes/block-bindings/sources/post-meta.php b/src/wp-includes/block-bindings/sources/post-meta.php index 811dad6b6349a..bd6999209f529 100644 --- a/src/wp-includes/block-bindings/sources/post-meta.php +++ b/src/wp-includes/block-bindings/sources/post-meta.php @@ -14,6 +14,13 @@ function post_meta_source_callback( $source_attrs ) { $post_id = get_the_ID(); } + // If a post isn't public, we need to prevent + // unauthorized users from accessing the post meta. + $post = get_post($post_id); + if ( ( $post && $post->post_status != 'publish' && ! current_user_can( 'read_post', $post_id ) ) || post_password_required( $post_id ) ) { + return null; + } + return get_post_meta( $post_id, $source_attrs['value'], true ); } From 5b8eb4ac95ede774fd59db2e8b41beb392602530 Mon Sep 17 00:00:00 2001 From: Ricardo Artemio Morales Date: Fri, 26 Jan 2024 18:06:19 -0500 Subject: [PATCH 38/72] Fix PHPCS errors --- .../block-bindings/class-wp-block-bindings-registry.php | 2 +- src/wp-includes/block-bindings/sources/pattern.php | 4 ++-- src/wp-includes/block-bindings/sources/post-meta.php | 8 ++++---- src/wp-includes/class-wp-block.php | 2 -- 4 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/wp-includes/block-bindings/class-wp-block-bindings-registry.php b/src/wp-includes/block-bindings/class-wp-block-bindings-registry.php index 6c20998a1e46b..74a736f1ec335 100644 --- a/src/wp-includes/block-bindings/class-wp-block-bindings-registry.php +++ b/src/wp-includes/block-bindings/class-wp-block-bindings-registry.php @@ -58,7 +58,7 @@ class WP_Block_Bindings_Registry { * @return boolean Whether the registration was successful. */ public function register( $source_name, array $source_properties ) { - if( ! isset( $this->sources[ $source_name ] ) ) { + if ( ! isset( $this->sources[ $source_name ] ) ) { $this->sources[ $source_name ] = $source_properties; return true; } diff --git a/src/wp-includes/block-bindings/sources/pattern.php b/src/wp-includes/block-bindings/sources/pattern.php index 9e4eafe581dd1..868ceb91cce3e 100644 --- a/src/wp-includes/block-bindings/sources/pattern.php +++ b/src/wp-includes/block-bindings/sources/pattern.php @@ -17,7 +17,7 @@ function pattern_source_callback( $source_attrs, $block_instance, $attribute_nam wp_block_bindings_register_source( 'core/pattern_attributes', array( - 'label' => __( 'Pattern Attributes' ), - 'get_value_callback' => 'pattern_source_callback', + 'label' => __( 'Pattern Attributes' ), + 'get_value_callback' => 'pattern_source_callback' ) ); diff --git a/src/wp-includes/block-bindings/sources/post-meta.php b/src/wp-includes/block-bindings/sources/post-meta.php index bd6999209f529..0c223abce7176 100644 --- a/src/wp-includes/block-bindings/sources/post-meta.php +++ b/src/wp-includes/block-bindings/sources/post-meta.php @@ -16,8 +16,8 @@ function post_meta_source_callback( $source_attrs ) { // If a post isn't public, we need to prevent // unauthorized users from accessing the post meta. - $post = get_post($post_id); - if ( ( $post && $post->post_status != 'publish' && ! current_user_can( 'read_post', $post_id ) ) || post_password_required( $post_id ) ) { + $post = get_post( $post_id ); + if ( ( $post && 'publish' !== $post->post_status && ! current_user_can( 'read_post', $post_id ) ) || post_password_required( $post_id ) ) { return null; } @@ -27,7 +27,7 @@ function post_meta_source_callback( $source_attrs ) { wp_block_bindings_register_source( 'core/post_meta', array( - 'label' => __( 'Post Meta' ), - 'get_value_callback' => 'post_meta_source_callback', + 'label' => __( 'Post Meta' ), + 'get_value_callback' => 'post_meta_source_callback' ), ); diff --git a/src/wp-includes/class-wp-block.php b/src/wp-includes/class-wp-block.php index ff834b177957b..0c60aed029980 100644 --- a/src/wp-includes/class-wp-block.php +++ b/src/wp-includes/class-wp-block.php @@ -256,8 +256,6 @@ private function process_block_bindings( $block_content ) { $block_bindings_sources = wp_block_bindings_get_all_registered(); $modified_block_content = $block_content; foreach ( $block['attrs']['metadata']['bindings'] as $binding_attribute => $binding_source ) { - - // If the attribute is not in the list, process next attribute. if ( ! in_array( $binding_attribute, $allowed_blocks[ $this->name ], true ) ) { continue; From 45a96f8d74f69bfdedd1a10d208e565c1fbf7017 Mon Sep 17 00:00:00 2001 From: Ricardo Artemio Morales Date: Fri, 26 Jan 2024 18:13:36 -0500 Subject: [PATCH 39/72] Rename sources using naming pattern for block types --- src/wp-includes/block-bindings/sources/pattern.php | 2 +- src/wp-includes/block-bindings/sources/post-meta.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/wp-includes/block-bindings/sources/pattern.php b/src/wp-includes/block-bindings/sources/pattern.php index 868ceb91cce3e..ed938b713a481 100644 --- a/src/wp-includes/block-bindings/sources/pattern.php +++ b/src/wp-includes/block-bindings/sources/pattern.php @@ -15,7 +15,7 @@ function pattern_source_callback( $source_attrs, $block_instance, $attribute_nam } wp_block_bindings_register_source( - 'core/pattern_attributes', + 'core/pattern-attributes', array( 'label' => __( 'Pattern Attributes' ), 'get_value_callback' => 'pattern_source_callback' diff --git a/src/wp-includes/block-bindings/sources/post-meta.php b/src/wp-includes/block-bindings/sources/post-meta.php index 0c223abce7176..9a39b9dbc4450 100644 --- a/src/wp-includes/block-bindings/sources/post-meta.php +++ b/src/wp-includes/block-bindings/sources/post-meta.php @@ -25,7 +25,7 @@ function post_meta_source_callback( $source_attrs ) { } wp_block_bindings_register_source( - 'core/post_meta', + 'core/post-meta', array( 'label' => __( 'Post Meta' ), 'get_value_callback' => 'post_meta_source_callback' From 08ddb0a0cb2aa1e27c8f80c08d3f0f7b7d560033 Mon Sep 17 00:00:00 2001 From: Ricardo Artemio Morales Date: Fri, 26 Jan 2024 18:20:58 -0500 Subject: [PATCH 40/72] Add context to translation --- src/wp-includes/block-bindings/sources/post-meta.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wp-includes/block-bindings/sources/post-meta.php b/src/wp-includes/block-bindings/sources/post-meta.php index 9a39b9dbc4450..706d5ba018416 100644 --- a/src/wp-includes/block-bindings/sources/post-meta.php +++ b/src/wp-includes/block-bindings/sources/post-meta.php @@ -27,7 +27,7 @@ function post_meta_source_callback( $source_attrs ) { wp_block_bindings_register_source( 'core/post-meta', array( - 'label' => __( 'Post Meta' ), + 'label' => _x( 'Post Meta', 'Post metadata to be read and used to substitute block content' ), 'get_value_callback' => 'post_meta_source_callback' ), ); From 3a16941ed1fc1408f0687297f2c7d9183ee3a416 Mon Sep 17 00:00:00 2001 From: Ricardo Artemio Morales Date: Fri, 26 Jan 2024 18:41:21 -0500 Subject: [PATCH 41/72] Update comments and revise to use proper array annotation --- .../block-bindings/block-bindings.php | 38 +++++++++++-------- .../class-wp-block-bindings-registry.php | 26 +++++++------ 2 files changed, 36 insertions(+), 28 deletions(-) diff --git a/src/wp-includes/block-bindings/block-bindings.php b/src/wp-includes/block-bindings/block-bindings.php index cdb84785d60b9..3310bf251a676 100644 --- a/src/wp-includes/block-bindings/block-bindings.php +++ b/src/wp-includes/block-bindings/block-bindings.php @@ -8,26 +8,32 @@ * @package WordPress */ - /** - * Registers a new source for block bindings. - * - * @since 6.5.0 + * Function to register a new block binding source. * - * @param string $source_name The name of the source. - * @param array $source_properties The array of arguments that are used to register a source. The array has two elements: - * 1. string $label The label of the source. - * 2. callback $get_value_callback A callback executed when - * the source is processed during block rendering. - * The callback should have the following signature: + * Sources are used to override block's original attributes with a value + * coming from the source. Once a source is registered, it can be used by a + * block by setting its `metadata.bindings` attribute to a value that refers + * to the source. * - * `function (object $source_attrs, object $block_instance, string $attribute_name): string` - * - @param object $source_attrs: Object containing source ID used to look up the override value, i.e. {"value": "{ID}"}. - * - @param object $block_instance: The block instance. - * - @param string $attribute_name: The name of an attribute used to retrieve an override value from the block context. - * The callback should return a string that will be used to override the block's original value. + * @since 6.5.0 * - * @return void + * @param string $source_name The name of the source. + * @param array $source_properties { + * The array of arguments that are used to register a source. We use an array so that we can easily extend + * the API to pass additional arguments in the future. For now, it should be comprised of two elements: + * + * @type string $label The label of the source. + * @type callback $get_value_callback A callback executed when the source is processed during block rendering. + * The callback should have the following signature: + * + * `function (object $source_attrs, object $block_instance, string $attribute_name): string` + * - @param object $source_attrs: Object containing source ID used to look up the override value, i.e. {"value": "{ID}"}. + * - @param object $block_instance: The block instance. + * - @param string $attribute_name: The name of an attribute used to retrieve an override value from the block context. + * The callback should return a string that will be used to override the block's original value, or null. + * } + * @return boolean Whether the registration was successful. */ function wp_block_bindings_register_source( $source_name, array $source_properties ) { add_action( diff --git a/src/wp-includes/block-bindings/class-wp-block-bindings-registry.php b/src/wp-includes/block-bindings/class-wp-block-bindings-registry.php index 74a736f1ec335..c85819868feeb 100644 --- a/src/wp-includes/block-bindings/class-wp-block-bindings-registry.php +++ b/src/wp-includes/block-bindings/class-wp-block-bindings-registry.php @@ -43,18 +43,20 @@ class WP_Block_Bindings_Registry { * @since 6.5.0 * * @param string $source_name The name of the source. - * @param array $source_properties The array of arguments that are used to register a source. The array has two elements: - * 1. string $label The label of the source. - * 2. callback $get_value_callback A callback executed when - * the source is processed during block rendering. - * The callback should have the following signature: - * - * `function (object $source_attrs, object $block_instance, string $attribute_name): string` - * - @param object $source_attrs: Object containing source ID used to look up the override value, i.e. {"value": "{ID}"}. - * - @param object $block_instance: The block instance. - * - @param string $attribute_name: The name of an attribute used to retrieve an override value from the block context. - * The callback should return a string that will be used to override the block's original value. - * + * @param array $source_properties { + * The array of arguments that are used to register a source. We use an array so that we can easily extend + * the API to pass additional arguments in the future. For now, it should be comprised of two elements: + * + * @type string $label The label of the source. + * @type callback $get_value_callback A callback executed when the source is processed during block rendering. + * The callback should have the following signature: + * + * `function (object $source_attrs, object $block_instance, string $attribute_name): string` + * - @param object $source_attrs: Object containing source ID used to look up the override value, i.e. {"value": "{ID}"}. + * - @param object $block_instance: The block instance. + * - @param string $attribute_name: The name of an attribute used to retrieve an override value from the block context. + * The callback should return a string that will be used to override the block's original value, or null. + * } * @return boolean Whether the registration was successful. */ public function register( $source_name, array $source_properties ) { From 1729f65d6a976687325501530ae6eb600c46a6fd Mon Sep 17 00:00:00 2001 From: Ricardo Artemio Morales Date: Fri, 26 Jan 2024 18:49:55 -0500 Subject: [PATCH 42/72] Update callback comment with return type details; fix PHPCS --- src/wp-includes/block-bindings/block-bindings.php | 2 +- .../block-bindings/class-wp-block-bindings-registry.php | 2 +- src/wp-includes/block-bindings/sources/pattern.php | 2 +- src/wp-includes/block-bindings/sources/post-meta.php | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/wp-includes/block-bindings/block-bindings.php b/src/wp-includes/block-bindings/block-bindings.php index 3310bf251a676..e971980c5d4b2 100644 --- a/src/wp-includes/block-bindings/block-bindings.php +++ b/src/wp-includes/block-bindings/block-bindings.php @@ -31,7 +31,7 @@ * - @param object $source_attrs: Object containing source ID used to look up the override value, i.e. {"value": "{ID}"}. * - @param object $block_instance: The block instance. * - @param string $attribute_name: The name of an attribute used to retrieve an override value from the block context. - * The callback should return a string that will be used to override the block's original value, or null. + * The callback has a mixed return type; it may return a string to override the block's original value, null, false to remove an attribtute, etc. * } * @return boolean Whether the registration was successful. */ diff --git a/src/wp-includes/block-bindings/class-wp-block-bindings-registry.php b/src/wp-includes/block-bindings/class-wp-block-bindings-registry.php index c85819868feeb..bd7952acc7f3b 100644 --- a/src/wp-includes/block-bindings/class-wp-block-bindings-registry.php +++ b/src/wp-includes/block-bindings/class-wp-block-bindings-registry.php @@ -55,7 +55,7 @@ class WP_Block_Bindings_Registry { * - @param object $source_attrs: Object containing source ID used to look up the override value, i.e. {"value": "{ID}"}. * - @param object $block_instance: The block instance. * - @param string $attribute_name: The name of an attribute used to retrieve an override value from the block context. - * The callback should return a string that will be used to override the block's original value, or null. + * The callback has a mixed return type; it may return a string to override the block's original value, null, false to remove an attribtute, etc. * } * @return boolean Whether the registration was successful. */ diff --git a/src/wp-includes/block-bindings/sources/pattern.php b/src/wp-includes/block-bindings/sources/pattern.php index ed938b713a481..5c1bc02676ccf 100644 --- a/src/wp-includes/block-bindings/sources/pattern.php +++ b/src/wp-includes/block-bindings/sources/pattern.php @@ -18,6 +18,6 @@ function pattern_source_callback( $source_attrs, $block_instance, $attribute_nam 'core/pattern-attributes', array( 'label' => __( 'Pattern Attributes' ), - 'get_value_callback' => 'pattern_source_callback' + 'get_value_callback' => 'pattern_source_callback', ) ); diff --git a/src/wp-includes/block-bindings/sources/post-meta.php b/src/wp-includes/block-bindings/sources/post-meta.php index 706d5ba018416..deb1875fa6300 100644 --- a/src/wp-includes/block-bindings/sources/post-meta.php +++ b/src/wp-includes/block-bindings/sources/post-meta.php @@ -28,6 +28,6 @@ function post_meta_source_callback( $source_attrs ) { 'core/post-meta', array( 'label' => _x( 'Post Meta', 'Post metadata to be read and used to substitute block content' ), - 'get_value_callback' => 'post_meta_source_callback' - ), + 'get_value_callback' => 'post_meta_source_callback', + ) ); From 9ea2c9aca31074f2baa367b63347a2a4c6c6426f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Greg=20Zi=C3=B3=C5=82kowski?= Date: Mon, 29 Jan 2024 09:52:18 +0100 Subject: [PATCH 43/72] Code quality improvements for the Block Bindings Registry --- .../class-wp-block-bindings-registry.php | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/src/wp-includes/block-bindings/class-wp-block-bindings-registry.php b/src/wp-includes/block-bindings/class-wp-block-bindings-registry.php index bd7952acc7f3b..d32b11223e87a 100644 --- a/src/wp-includes/block-bindings/class-wp-block-bindings-registry.php +++ b/src/wp-includes/block-bindings/class-wp-block-bindings-registry.php @@ -2,18 +2,19 @@ /** * Block Bindings API: WP_Block_Bindings_Registry class. * - * Support for overriding content in blocks by connecting them to different sources. + * Supports overriding content in blocks by connecting them to different sources. * * @package WordPress * @subpackage Block Bindings + * @since 6.5.0 */ /** - * Core class used to define supported blocks, register sources, and populate HTML with content from those sources. + * Core class used for interacting with block binding sources. * * @since 6.5.0 */ -class WP_Block_Bindings_Registry { +final class WP_Block_Bindings_Registry { /** * Holds the registered block bindings sources, keyed by source identifier. @@ -33,7 +34,7 @@ class WP_Block_Bindings_Registry { private static $instance = null; /** - * Function to register a new block binding source. + * Registers a new block binding source. * * Sources are used to override block's original attributes with a value * coming from the source. Once a source is registered, it can be used by a @@ -42,7 +43,7 @@ class WP_Block_Bindings_Registry { * * @since 6.5.0 * - * @param string $source_name The name of the source. + * @param string $source_name The name of the source. * @param array $source_properties { * The array of arguments that are used to register a source. We use an array so that we can easily extend * the API to pass additional arguments in the future. For now, it should be comprised of two elements: @@ -51,11 +52,11 @@ class WP_Block_Bindings_Registry { * @type callback $get_value_callback A callback executed when the source is processed during block rendering. * The callback should have the following signature: * - * `function (object $source_attrs, object $block_instance, string $attribute_name): string` - * - @param object $source_attrs: Object containing source ID used to look up the override value, i.e. {"value": "{ID}"}. + * `function (object $source_args, object $block_instance, string $attribute_name): mixed` + * - @param object $source_args: Object containing source arguments used to look up the override value, i.e. {"key": "foo"}. * - @param object $block_instance: The block instance. * - @param string $attribute_name: The name of an attribute used to retrieve an override value from the block context. - * The callback has a mixed return type; it may return a string to override the block's original value, null, false to remove an attribtute, etc. + * The callback has a mixed return type; it may return a string to override the block's original value, null, false to remove an attribute, etc. * } * @return boolean Whether the registration was successful. */ @@ -68,7 +69,7 @@ public function register( $source_name, array $source_properties ) { } /** - * Retrieves the list of registered block bindings sources. + * Retrieves the list of all registered block bindings sources. * * @since 6.5.0 * @@ -84,7 +85,7 @@ public function get_all_registered() { * @since 6.5.0 * * @param string $source_name The name of the source. - * @return bool True if the source is registered, false otherwise. + * @return array|null The registered block binding source, or `null` if it is not registered. */ public function get_registered( $source_name ) { if ( ! $this->is_registered( $source_name ) ) { @@ -95,12 +96,12 @@ public function get_registered( $source_name ) { } /** - * Checks if a block source is registered. + * Checks if a block binding source is registered. * * @since 6.5.0 * * @param string $source_name The name of the source. - * @return bool True if the source is registered, false otherwise. + * @return bool `true` if the block binding source is registered, `false` otherwise. */ public function is_registered( $source_name ) { return isset( $this->sources[ $source_name ] ); @@ -113,7 +114,7 @@ public function is_registered( $source_name ) { * * @since 6.5.0 * - * @return WP_Block_Bindings_Registry The WP_Block_Bindings_Registry instance. + * @return WP_Block_Bindings_Registry The main instance. */ public static function get_instance() { if ( null === self::$instance ) { From fd090ab133c67450dfeff05f4c44e849b493885c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Greg=20Zi=C3=B3=C5=82kowski?= Date: Mon, 29 Jan 2024 10:34:46 +0100 Subject: [PATCH 44/72] Ensure that code comments fit into the line length limit --- .../class-wp-block-bindings-registry.php | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/wp-includes/block-bindings/class-wp-block-bindings-registry.php b/src/wp-includes/block-bindings/class-wp-block-bindings-registry.php index d32b11223e87a..f2c5d985bf733 100644 --- a/src/wp-includes/block-bindings/class-wp-block-bindings-registry.php +++ b/src/wp-includes/block-bindings/class-wp-block-bindings-registry.php @@ -45,18 +45,20 @@ final class WP_Block_Bindings_Registry { * * @param string $source_name The name of the source. * @param array $source_properties { - * The array of arguments that are used to register a source. We use an array so that we can easily extend - * the API to pass additional arguments in the future. For now, it should be comprised of two elements: + * The array of arguments that are used to register a source. * - * @type string $label The label of the source. + * @type string $label The label of the source. * @type callback $get_value_callback A callback executed when the source is processed during block rendering. * The callback should have the following signature: * - * `function (object $source_args, object $block_instance, string $attribute_name): mixed` - * - @param object $source_args: Object containing source arguments used to look up the override value, i.e. {"key": "foo"}. - * - @param object $block_instance: The block instance. - * - @param string $attribute_name: The name of an attribute used to retrieve an override value from the block context. - * The callback has a mixed return type; it may return a string to override the block's original value, null, false to remove an attribute, etc. + * `function ($source_args, $block_instance,$attribute_name): mixed` + * - @param array $source_args Array containing source arguments + * used to look up the override value, + * i.e. {"key": "foo"}. + * - @param WP_Block $block_instance The block instance. + * - @param string $attribute_name The name of an attribute . + * The callback has a mixed return type; it may return a string to override + * the block's original value, null, false to remove an attribute, etc. * } * @return boolean Whether the registration was successful. */ From 6ea69cd9ce30d8cfaed471fd1ae0956b378bde57 Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Thu, 25 Jan 2024 20:21:46 +0000 Subject: [PATCH 45/72] Updates to the test --- tests/phpunit/tests/block-bindings.php | 110 ++++++++++++++++--------- 1 file changed, 72 insertions(+), 38 deletions(-) diff --git a/tests/phpunit/tests/block-bindings.php b/tests/phpunit/tests/block-bindings.php index 4df7db4374234..fc307cbc90726 100644 --- a/tests/phpunit/tests/block-bindings.php +++ b/tests/phpunit/tests/block-bindings.php @@ -17,13 +17,19 @@ class WP_Block_Bindings_Registry_Test extends WP_UnitTestCase { * @covers WP_Block_Bindings_Registry::register_source */ public function test_register_source() { - $wp_block_bindings = new WP_Block_Bindings_Registry(); + $wp_block_bindings = WP_Block_Bindings_Registry::get_instance(); $source_name = 'test_source'; $label = 'Test Source'; $apply = function () { }; - $wp_block_bindings->register( $source_name, array( $label, $apply ) ); + $wp_block_bindings->register( + $source_name, + array( + 'label' => $label, + 'apply' => $apply, + ) + ); $sources = $wp_block_bindings->get_all_registered(); $this->assertArrayHasKey( $source_name, $sources ); @@ -32,22 +38,53 @@ public function test_register_source() { } /** - * Test replace_html method for content. + * Test * - * @covers WP_Block_Bindings_Registry::replace_html + * @covers WP_Block_Bindings_Registry:: */ public function test_replace_html_for_paragraph_content() { - $wp_block_bindings = new WP_Block_Bindings_Registry(); + $wp_block_bindings = WP_Block_Bindings_Registry::get_instance(); + + $source_name = 'test_source'; + $label = 'Test Source'; + $apply = function () { + return 'test source value'; + }; + + $wp_block_bindings->register( + $source_name, + array( + 'label' => $label, + 'apply' => $apply, + ) + ); $block_content = '

Hello World

'; $block_name = 'core/paragraph'; - $block_attr = 'content'; - $source_value = 'Updated Content'; - $result = $wp_block_bindings->replace_html( $block_content, $block_name, $block_attr, $source_value ); + $block = new WP_Block( + array( + 'blockName' => $block_name, + 'innerHTML' => $block_content, + 'attrs' => array( + 'metadata' => array( + 'bindings' => array( + 'content' => array( + 'source' => array( + 'name' => $source_name, + ), + ), + ), + ), + ), + ) + ); + + $expected = '

test source value

'; + $result = $block->render(); // Check if the block content was updated correctly. - $this->assertStringContainsString( $source_value, $result, 'The block content should be updated with the value returned by the source.' ); + $this->assertEquals( $expected, $result, 'The block content should be updated with the value returned by the source.' ); } /** @@ -56,14 +93,14 @@ public function test_replace_html_for_paragraph_content() { * @covers WP_Block_Bindings_Registry::replace_html */ public function test_replace_html_for_attribute() { - $wp_block_bindings = new WP_Block_Bindings_Registry(); - $block_content = ''; - $block_name = 'core/button'; - $block_attr = 'url'; - $source_value = 'Updated URL'; - - $result = $wp_block_bindings->replace_html( $block_content, $block_name, $block_attr, $source_value ); - $this->assertStringContainsString( $source_value, $result, 'The block content should be updated with the value returned by the source.' ); + // $wp_block_bindings = new WP_Block_Bindings_Registry(); + // $block_content = ''; + // $block_name = 'core/button'; + // $block_attr = 'url'; + // $source_value = 'Updated URL'; + + // $result = $wp_block_bindings->replace_html( $block_content, $block_name, $block_attr, $source_value ); + // $this->assertStringContainsString( $source_value, $result, 'The block content should be updated with the value returned by the source.' ); } /** @@ -72,16 +109,16 @@ public function test_replace_html_for_attribute() { * @covers WP_Block_Bindings_Registry::replace_html */ public function test_replace_html_with_unregistered_block() { - $wp_block_bindings = new WP_Block_Bindings_Registry(); + // $wp_block_bindings = new WP_Block_Bindings_Registry(); - $block_content = '

Hello World

'; - $block_name = 'NONEXISTENT'; - $block_attr = 'content'; - $source_value = 'Updated Content'; + // $block_content = '

Hello World

'; + // $block_name = 'NONEXISTENT'; + // $block_attr = 'content'; + // $source_value = 'Updated Content'; - $result = $wp_block_bindings->replace_html( $block_content, $block_name, $block_attr, $source_value ); + // $result = $wp_block_bindings->replace_html( $block_content, $block_name, $block_attr, $source_value ); - $this->assertEquals( $block_content, $result, 'The block content should not be updated if the block type is not registered.' ); + // $this->assertEquals( $block_content, $result, 'The block content should not be updated if the block type is not registered.' ); } /** @@ -91,16 +128,16 @@ public function test_replace_html_with_unregistered_block() { * @covers WP_Block_Bindings_Registry::replace_html */ public function test_replace_html_with_registered_block_but_unsupported_source_type() { - $wp_block_bindings = new WP_Block_Bindings_Registry(); + // $wp_block_bindings = new WP_Block_Bindings_Registry(); - $block_content = '
Hello World
'; - $block_name = 'core/paragraph'; - $block_attr = 'NONEXISTENT'; - $source_value = 'Updated Content'; + // $block_content = '
Hello World
'; + // $block_name = 'core/paragraph'; + // $block_attr = 'NONEXISTENT'; + // $source_value = 'Updated Content'; - $result = $wp_block_bindings->replace_html( $block_content, $block_name, $block_attr, $source_value ); + // $result = $wp_block_bindings->replace_html( $block_content, $block_name, $block_attr, $source_value ); - $this->assertEquals( $block_content, $result, 'The block content should not be updated if the block type does not support the attribute.' ); + // $this->assertEquals( $block_content, $result, 'The block content should not be updated if the block type does not support the attribute.' ); } /** @@ -109,7 +146,7 @@ public function test_replace_html_with_registered_block_but_unsupported_source_t * @covers _process_block_bindings */ public function test_post_meta_bindings_source() { - $wp_block_bindings = wp_block_bindings(); + $wp_block_bindings = WP_Block_Bindings_Registry::get_instance(); // Register a custom binding source. $source_name = 'test_source'; @@ -117,9 +154,7 @@ public function test_post_meta_bindings_source() { $apply = function () { return 'test source value'; }; - $wp_block_bindings->register_source( $source_name, array( $label, $apply ) ); - - $block_content = '

This content will be overriden

'; + $wp_block_bindings->register( $source_name, array( $label, $apply ) ); // Parsed block representing a paragraph block. $block = array( @@ -143,9 +178,8 @@ public function test_post_meta_bindings_source() { // Block instance representing a paragraph block. $block_instance = new WP_Block( $block ); - $content = $wp_block_bindings->process_bindings( $block_content, $block, $block_instance ); - - $result = '

test source value

'; + $content = $block_instance->render(); + $result = '

test source value

'; $this->assertEquals( $result, $content, 'The block content should be updated with the value returned by the custom binding source.' ); } From 58938672a1306e12d69b850bea8be7aebb790883 Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Mon, 29 Jan 2024 13:36:47 +0000 Subject: [PATCH 46/72] Remove the files already committed in #5965 and #5966. --- .../block-bindings/block-bindings.php | 56 -------- .../class-wp-block-bindings-registry.php | 128 ------------------ .../block-bindings/sources/pattern.php | 2 +- .../block-bindings/sources/post-meta.php | 2 +- src/wp-settings.php | 2 - 5 files changed, 2 insertions(+), 188 deletions(-) delete mode 100644 src/wp-includes/block-bindings/block-bindings.php delete mode 100644 src/wp-includes/block-bindings/class-wp-block-bindings-registry.php diff --git a/src/wp-includes/block-bindings/block-bindings.php b/src/wp-includes/block-bindings/block-bindings.php deleted file mode 100644 index e971980c5d4b2..0000000000000 --- a/src/wp-includes/block-bindings/block-bindings.php +++ /dev/null @@ -1,56 +0,0 @@ -register( $source_name, $source_properties ); - } - ); -} - -/** - * Retrieves the list of registered block sources. - * - * @since 6.5.0 - * - * @return array The list of registered block sources. - */ -function wp_block_bindings_get_all_registered() { - return WP_Block_Bindings_Registry::get_instance()->get_all_registered(); -} diff --git a/src/wp-includes/block-bindings/class-wp-block-bindings-registry.php b/src/wp-includes/block-bindings/class-wp-block-bindings-registry.php deleted file mode 100644 index f2c5d985bf733..0000000000000 --- a/src/wp-includes/block-bindings/class-wp-block-bindings-registry.php +++ /dev/null @@ -1,128 +0,0 @@ -sources[ $source_name ] ) ) { - $this->sources[ $source_name ] = $source_properties; - return true; - } - return false; - } - - /** - * Retrieves the list of all registered block bindings sources. - * - * @since 6.5.0 - * - * @return array The array of registered sources. - */ - public function get_all_registered() { - return $this->sources; - } - - /** - * Retrieves a registered block bindings source. - * - * @since 6.5.0 - * - * @param string $source_name The name of the source. - * @return array|null The registered block binding source, or `null` if it is not registered. - */ - public function get_registered( $source_name ) { - if ( ! $this->is_registered( $source_name ) ) { - return null; - } - - return $this->sources[ $source_name ]; - } - - /** - * Checks if a block binding source is registered. - * - * @since 6.5.0 - * - * @param string $source_name The name of the source. - * @return bool `true` if the block binding source is registered, `false` otherwise. - */ - public function is_registered( $source_name ) { - return isset( $this->sources[ $source_name ] ); - } - - /** - * Utility method to retrieve the main instance of the class. - * - * The instance will be created if it does not exist yet. - * - * @since 6.5.0 - * - * @return WP_Block_Bindings_Registry The main instance. - */ - public static function get_instance() { - if ( null === self::$instance ) { - self::$instance = new self(); - } - - return self::$instance; - } -} diff --git a/src/wp-includes/block-bindings/sources/pattern.php b/src/wp-includes/block-bindings/sources/pattern.php index 5c1bc02676ccf..380a44331a413 100644 --- a/src/wp-includes/block-bindings/sources/pattern.php +++ b/src/wp-includes/block-bindings/sources/pattern.php @@ -14,7 +14,7 @@ function pattern_source_callback( $source_attrs, $block_instance, $attribute_nam return _wp_array_get( $block_instance->context, array( 'pattern/overrides', $block_id, $attribute_name ), null ); } -wp_block_bindings_register_source( +register_block_bindings_source( 'core/pattern-attributes', array( 'label' => __( 'Pattern Attributes' ), diff --git a/src/wp-includes/block-bindings/sources/post-meta.php b/src/wp-includes/block-bindings/sources/post-meta.php index deb1875fa6300..6679edcf30ed6 100644 --- a/src/wp-includes/block-bindings/sources/post-meta.php +++ b/src/wp-includes/block-bindings/sources/post-meta.php @@ -24,7 +24,7 @@ function post_meta_source_callback( $source_attrs ) { return get_post_meta( $post_id, $source_attrs['value'], true ); } -wp_block_bindings_register_source( +register_block_bindings_source( 'core/post-meta', array( 'label' => _x( 'Post Meta', 'Post metadata to be read and used to substitute block content' ), diff --git a/src/wp-settings.php b/src/wp-settings.php index 43b83c21df5b4..793b591666eea 100644 --- a/src/wp-settings.php +++ b/src/wp-settings.php @@ -376,8 +376,6 @@ require ABSPATH . WPINC . '/fonts.php'; require ABSPATH . WPINC . '/class-wp-script-modules.php'; require ABSPATH . WPINC . '/script-modules.php'; -require ABSPATH . WPINC . '/block-bindings/block-bindings.php'; -require ABSPATH . WPINC . '/block-bindings/class-wp-block-bindings-registry.php'; require ABSPATH . WPINC . '/block-bindings/sources/post-meta.php'; require ABSPATH . WPINC . '/block-bindings/sources/pattern.php'; From d10b16547e2b637ea8de0a6b89a4921e21cdc0a8 Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Mon, 29 Jan 2024 13:40:28 +0000 Subject: [PATCH 47/72] Update block bindings function call in class-wp-block.php --- 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 0c60aed029980..8d17610529fb2 100644 --- a/src/wp-includes/class-wp-block.php +++ b/src/wp-includes/class-wp-block.php @@ -253,7 +253,7 @@ private function process_block_bindings( $block_content ) { return $block_content; } - $block_bindings_sources = wp_block_bindings_get_all_registered(); + $block_bindings_sources = get_all_registered_block_bindings_sources(); $modified_block_content = $block_content; foreach ( $block['attrs']['metadata']['bindings'] as $binding_attribute => $binding_source ) { // If the attribute is not in the list, process next attribute. From c324e6588e52754b15d6f49990e422c7338818c9 Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Mon, 29 Jan 2024 15:49:45 +0000 Subject: [PATCH 48/72] Add set_up to block-bindings tests --- tests/phpunit/tests/block-bindings/register.php | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/phpunit/tests/block-bindings/register.php b/tests/phpunit/tests/block-bindings/register.php index 0c6cce6f41577..2ef0a20ac1839 100644 --- a/tests/phpunit/tests/block-bindings/register.php +++ b/tests/phpunit/tests/block-bindings/register.php @@ -16,6 +16,22 @@ class Tests_Block_Bindings_Register extends WP_UnitTestCase { 'label' => 'Test source', ); + /** + * Set up before each test. + * + * @since 6.5.0 + */ + public function set_up() { + foreach ( get_all_registered_block_bindings_sources() as $source_name => $source_properties ) { + if ( str_starts_with( $source_name, 'test/' ) ) { + unregister_block_bindings_source( $source_name ); + } + unregister_block_bindings_source( $source_name ); + } + + parent::set_up(); + } + /** * Tear down after each test. * From a0d550aece0b0e92c9d98490d0444af980268865 Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Mon, 29 Jan 2024 17:16:06 +0000 Subject: [PATCH 49/72] Refactor block bindings tests --- tests/phpunit/tests/block-bindings.php | 158 ++----------------------- 1 file changed, 11 insertions(+), 147 deletions(-) diff --git a/tests/phpunit/tests/block-bindings.php b/tests/phpunit/tests/block-bindings.php index fc307cbc90726..09a6f4a743f35 100644 --- a/tests/phpunit/tests/block-bindings.php +++ b/tests/phpunit/tests/block-bindings.php @@ -11,32 +11,6 @@ */ class WP_Block_Bindings_Registry_Test extends WP_UnitTestCase { - /** - * Test register_source method. - * - * @covers WP_Block_Bindings_Registry::register_source - */ - public function test_register_source() { - $wp_block_bindings = WP_Block_Bindings_Registry::get_instance(); - - $source_name = 'test_source'; - $label = 'Test Source'; - $apply = function () { }; - - $wp_block_bindings->register( - $source_name, - array( - 'label' => $label, - 'apply' => $apply, - ) - ); - - $sources = $wp_block_bindings->get_all_registered(); - $this->assertArrayHasKey( $source_name, $sources ); - $this->assertEquals( $label, $sources[ $source_name ]['label'], 'The label should match the one in the registered source' ); - $this->assertEquals( $apply, $sources[ $source_name ]['apply'], 'The apply callback should match the one in the registered source' ); - } - /** * Test * @@ -45,40 +19,27 @@ public function test_register_source() { public function test_replace_html_for_paragraph_content() { $wp_block_bindings = WP_Block_Bindings_Registry::get_instance(); - $source_name = 'test_source'; - $label = 'Test Source'; - $apply = function () { + $source_name = 'test_source'; + $label = 'Test Source'; + $get_value_callback = function () { return 'test source value'; }; $wp_block_bindings->register( $source_name, array( - 'label' => $label, - 'apply' => $apply, + 'label' => $label, + 'get_value_callback' => $get_value_callback, ) ); - $block_content = '

Hello World

'; - $block_name = 'core/paragraph'; + $block_content = <<

This should not appear

+HTML; - $block = new WP_Block( - array( - 'blockName' => $block_name, - 'innerHTML' => $block_content, - 'attrs' => array( - 'metadata' => array( - 'bindings' => array( - 'content' => array( - 'source' => array( - 'name' => $source_name, - ), - ), - ), - ), - ), - ) - ); + $parsed_blocks = parse_blocks( $block_content ); + + $block = new WP_Block( $parsed_blocks[0] ); $expected = '

test source value

'; $result = $block->render(); @@ -86,101 +47,4 @@ public function test_replace_html_for_paragraph_content() { // Check if the block content was updated correctly. $this->assertEquals( $expected, $result, 'The block content should be updated with the value returned by the source.' ); } - - /** - * Test replace_html method for attributes. - * - * @covers WP_Block_Bindings_Registry::replace_html - */ - public function test_replace_html_for_attribute() { - // $wp_block_bindings = new WP_Block_Bindings_Registry(); - // $block_content = ''; - // $block_name = 'core/button'; - // $block_attr = 'url'; - // $source_value = 'Updated URL'; - - // $result = $wp_block_bindings->replace_html( $block_content, $block_name, $block_attr, $source_value ); - // $this->assertStringContainsString( $source_value, $result, 'The block content should be updated with the value returned by the source.' ); - } - - /** - * Test case for scenarios where block type is not registered. - * - * @covers WP_Block_Bindings_Registry::replace_html - */ - public function test_replace_html_with_unregistered_block() { - // $wp_block_bindings = new WP_Block_Bindings_Registry(); - - // $block_content = '

Hello World

'; - // $block_name = 'NONEXISTENT'; - // $block_attr = 'content'; - // $source_value = 'Updated Content'; - - // $result = $wp_block_bindings->replace_html( $block_content, $block_name, $block_attr, $source_value ); - - // $this->assertEquals( $block_content, $result, 'The block content should not be updated if the block type is not registered.' ); - } - - /** - * Test case for scenarios where block is registered but attribute does not - * exist on block type. - * - * @covers WP_Block_Bindings_Registry::replace_html - */ - public function test_replace_html_with_registered_block_but_unsupported_source_type() { - // $wp_block_bindings = new WP_Block_Bindings_Registry(); - - // $block_content = '
Hello World
'; - // $block_name = 'core/paragraph'; - // $block_attr = 'NONEXISTENT'; - // $source_value = 'Updated Content'; - - // $result = $wp_block_bindings->replace_html( $block_content, $block_name, $block_attr, $source_value ); - - // $this->assertEquals( $block_content, $result, 'The block content should not be updated if the block type does not support the attribute.' ); - } - - /** - * Test case for the _process_block_bindings function using a custom binding source. - * - * @covers _process_block_bindings - */ - public function test_post_meta_bindings_source() { - $wp_block_bindings = WP_Block_Bindings_Registry::get_instance(); - - // Register a custom binding source. - $source_name = 'test_source'; - $label = 'Test Source'; - $apply = function () { - return 'test source value'; - }; - $wp_block_bindings->register( $source_name, array( $label, $apply ) ); - - // Parsed block representing a paragraph block. - $block = array( - 'blockName' => 'core/paragraph', - 'attrs' => array( - 'metadata' => array( - 'bindings' => array( - 'content' => array( - 'source' => array( - 'name' => 'test_source', - 'attributes' => array( - 'value' => 'does not matter, it is not used by the test source', - ), - ), - ), - ), - ), - ), - ); - - // Block instance representing a paragraph block. - $block_instance = new WP_Block( $block ); - - $content = $block_instance->render(); - $result = '

test source value

'; - - $this->assertEquals( $result, $content, 'The block content should be updated with the value returned by the custom binding source.' ); - } } From 3f242d3caf3e8b2a0439945a290e9554722b07e8 Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Mon, 29 Jan 2024 22:17:26 +0000 Subject: [PATCH 50/72] Fix block bindings registration unit test and test setup --- tests/phpunit/tests/block-bindings.php | 25 ++++++++++++++----- .../phpunit/tests/block-bindings/register.php | 6 ----- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/tests/phpunit/tests/block-bindings.php b/tests/phpunit/tests/block-bindings.php index 09a6f4a743f35..338c7a8544bb2 100644 --- a/tests/phpunit/tests/block-bindings.php +++ b/tests/phpunit/tests/block-bindings.php @@ -12,20 +12,33 @@ class WP_Block_Bindings_Registry_Test extends WP_UnitTestCase { /** - * Test + * Set up before each test. + * + * @since 6.5.0 + */ + public function set_up() { + foreach ( get_all_registered_block_bindings_sources() as $source_name => $source_properties ) { + unregister_block_bindings_source( $source_name ); + } + + parent::set_up(); + } + + /** + * Test if the block content is updated with the value returned by the source. + * + * @since 6.5.0 * * @covers WP_Block_Bindings_Registry:: */ public function test_replace_html_for_paragraph_content() { - $wp_block_bindings = WP_Block_Bindings_Registry::get_instance(); - - $source_name = 'test_source'; + $source_name = 'test/source'; $label = 'Test Source'; $get_value_callback = function () { return 'test source value'; }; - $wp_block_bindings->register( + register_block_bindings_source( $source_name, array( 'label' => $label, @@ -34,7 +47,7 @@ public function test_replace_html_for_paragraph_content() { ); $block_content = <<

This should not appear

+

This should not appear

HTML; $parsed_blocks = parse_blocks( $block_content ); diff --git a/tests/phpunit/tests/block-bindings/register.php b/tests/phpunit/tests/block-bindings/register.php index 2ef0a20ac1839..a87ae1dc56f3a 100644 --- a/tests/phpunit/tests/block-bindings/register.php +++ b/tests/phpunit/tests/block-bindings/register.php @@ -23,9 +23,6 @@ class Tests_Block_Bindings_Register extends WP_UnitTestCase { */ public function set_up() { foreach ( get_all_registered_block_bindings_sources() as $source_name => $source_properties ) { - if ( str_starts_with( $source_name, 'test/' ) ) { - unregister_block_bindings_source( $source_name ); - } unregister_block_bindings_source( $source_name ); } @@ -39,9 +36,6 @@ public function set_up() { */ public function tear_down() { foreach ( get_all_registered_block_bindings_sources() as $source_name => $source_properties ) { - if ( str_starts_with( $source_name, 'test/' ) ) { - unregister_block_bindings_source( $source_name ); - } unregister_block_bindings_source( $source_name ); } From d202e5d4a103c23e6d42ed4dba0df5f105d559ba Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Tue, 30 Jan 2024 13:38:58 +0000 Subject: [PATCH 51/72] Move tests to block-bindings directory --- .../tests/block-bindings/block-bindings.php | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 tests/phpunit/tests/block-bindings/block-bindings.php diff --git a/tests/phpunit/tests/block-bindings/block-bindings.php b/tests/phpunit/tests/block-bindings/block-bindings.php new file mode 100644 index 0000000000000..0c1ae0995caab --- /dev/null +++ b/tests/phpunit/tests/block-bindings/block-bindings.php @@ -0,0 +1,66 @@ + $source_properties ) { + if ( str_starts_with( $source_name, 'test/' ) ) { + unregister_block_bindings_source( $source_name ); + } + } + + parent::set_up(); + } + + /** + * Test if the block content is updated with the value returned by the source. + * + * @ticket 60282 + * + * @covers register_block_bindings_source + */ + public function test_replace_html_for_paragraph_content() { + $source_name = 'test/source'; + $label = 'Test Source'; + $get_value_callback = function () { + return 'test source value'; + }; + + register_block_bindings_source( + $source_name, + array( + 'label' => $label, + 'get_value_callback' => $get_value_callback, + ) + ); + + $block_content = <<

This should not appear

+HTML; + + $parsed_blocks = parse_blocks( $block_content ); + $block = new WP_Block( $parsed_blocks[0] ); + + $expected = '

test source value

'; + $result = $block->render(); + + // Check if the block content was updated correctly. + $this->assertEquals( $expected, $result, 'The block content should be updated with the value returned by the source.' ); + } +} From 2a20dee68f078098394e615f0dae565fa81e8dd6 Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Tue, 30 Jan 2024 15:52:16 +0000 Subject: [PATCH 52/72] Remove unnecessary attributes from the test --- tests/phpunit/tests/block-bindings/block-bindings.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/phpunit/tests/block-bindings/block-bindings.php b/tests/phpunit/tests/block-bindings/block-bindings.php index 0c1ae0995caab..f0bab20e9d04d 100644 --- a/tests/phpunit/tests/block-bindings/block-bindings.php +++ b/tests/phpunit/tests/block-bindings/block-bindings.php @@ -11,7 +11,7 @@ * * @covers register_block_bindings_source */ -class WP_Block_Bindings_Registry_Test extends WP_UnitTestCase { +class WP_Block_Bindings_Test extends WP_UnitTestCase { /** * Set up before each test. @@ -51,7 +51,7 @@ public function test_replace_html_for_paragraph_content() { ); $block_content = <<

This should not appear

+

This should not appear

HTML; $parsed_blocks = parse_blocks( $block_content ); From 2b4cf3341dd5b66135988cd2a01f5674d51e506c Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Tue, 30 Jan 2024 17:05:11 +0000 Subject: [PATCH 53/72] Incorporate PHP changes from https://github.com/WordPress/gutenberg/pull/58337 --- .../block-bindings/sources/post-meta.php | 6 +++++- src/wp-includes/class-wp-block.php | 20 ++++++++----------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/wp-includes/block-bindings/sources/post-meta.php b/src/wp-includes/block-bindings/sources/post-meta.php index 6679edcf30ed6..95715cf615c0b 100644 --- a/src/wp-includes/block-bindings/sources/post-meta.php +++ b/src/wp-includes/block-bindings/sources/post-meta.php @@ -6,6 +6,10 @@ * @package WordPress */ function post_meta_source_callback( $source_attrs ) { + if ( ! isset( $source_attrs['key'] ) ) { + return null; + } + // Use the postId attribute if available if ( isset( $source_attrs['postId'] ) ) { $post_id = $source_attrs['postId']; @@ -21,7 +25,7 @@ function post_meta_source_callback( $source_attrs ) { return null; } - return get_post_meta( $post_id, $source_attrs['value'], true ); + return get_post_meta( $post_id, $source_attrs['key'], true ); } register_block_bindings_source( diff --git a/src/wp-includes/class-wp-block.php b/src/wp-includes/class-wp-block.php index 8d17610529fb2..a426c0c5c7810 100644 --- a/src/wp-includes/class-wp-block.php +++ b/src/wp-includes/class-wp-block.php @@ -211,16 +211,12 @@ public function __get( $name ) { * "metadata": { * "bindings": { * "title": { - * "source": { - * "name": "post_meta", - * "attributes": { "value": "text_custom_field" } - * } + * "source": "post_meta", + * "args": { "key": "text_custom_field" } * }, * "url": { - * "source": { - * "name": "post_meta", - * "attributes": { "value": "url_custom_field" } - * } + * "source": "post_meta", + * "args": { "key": "url_custom_field" } * } * } * } @@ -261,16 +257,16 @@ private function process_block_bindings( $block_content ) { continue; } // If no source is provided, or that source is not registered, process next attribute. - if ( ! isset( $binding_source['source'] ) || ! isset( $binding_source['source']['name'] ) || ! isset( $block_bindings_sources[ $binding_source['source']['name'] ] ) ) { + if ( ! isset( $binding_source['source'] ) || ! is_string( $binding_source['source'] ) || ! isset( $block_bindings_sources[ $binding_source['source'] ] ) ) { continue; } - $source_callback = $block_bindings_sources[ $binding_source['source']['name'] ]['get_value_callback']; + $source_callback = $block_bindings_sources[ $binding_source['source'] ]['get_value_callback']; // Get the value based on the source. - if ( ! isset( $binding_source['source']['attributes'] ) ) { + if ( ! isset( $binding_source['source']['args'] ) ) { $source_args = array(); } else { - $source_args = $binding_source['source']['attributes']; + $source_args = $binding_source['source']['args']; } $source_value = $source_callback( $source_args, $this, $binding_attribute ); // If the value is null, process next attribute. From 8604e82450c940cc3a5bf8454538fef270b9ace7 Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Tue, 30 Jan 2024 17:10:48 +0000 Subject: [PATCH 54/72] Fix binding source arguments in WP_Block class --- 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 a426c0c5c7810..e82fcbe36cc32 100644 --- a/src/wp-includes/class-wp-block.php +++ b/src/wp-includes/class-wp-block.php @@ -263,10 +263,10 @@ private function process_block_bindings( $block_content ) { $source_callback = $block_bindings_sources[ $binding_source['source'] ]['get_value_callback']; // Get the value based on the source. - if ( ! isset( $binding_source['source']['args'] ) ) { + if ( ! isset( $binding_source['args'] ) ) { $source_args = array(); } else { - $source_args = $binding_source['source']['args']; + $source_args = $binding_source['args']; } $source_value = $source_callback( $source_args, $this, $binding_attribute ); // If the value is null, process next attribute. From 65a7c09ba7bc8f22499dea3657c03ac026d7c8f1 Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Tue, 30 Jan 2024 17:18:57 +0000 Subject: [PATCH 55/72] Test passing arguments to the source --- tests/phpunit/tests/block-bindings.php | 63 ------------------ .../tests/block-bindings/block-bindings.php | 64 +++++++++++++++---- 2 files changed, 52 insertions(+), 75 deletions(-) delete mode 100644 tests/phpunit/tests/block-bindings.php diff --git a/tests/phpunit/tests/block-bindings.php b/tests/phpunit/tests/block-bindings.php deleted file mode 100644 index 338c7a8544bb2..0000000000000 --- a/tests/phpunit/tests/block-bindings.php +++ /dev/null @@ -1,63 +0,0 @@ - $source_properties ) { - unregister_block_bindings_source( $source_name ); - } - - parent::set_up(); - } - - /** - * Test if the block content is updated with the value returned by the source. - * - * @since 6.5.0 - * - * @covers WP_Block_Bindings_Registry:: - */ - public function test_replace_html_for_paragraph_content() { - $source_name = 'test/source'; - $label = 'Test Source'; - $get_value_callback = function () { - return 'test source value'; - }; - - register_block_bindings_source( - $source_name, - array( - 'label' => $label, - 'get_value_callback' => $get_value_callback, - ) - ); - - $block_content = <<

This should not appear

-HTML; - - $parsed_blocks = parse_blocks( $block_content ); - - $block = new WP_Block( $parsed_blocks[0] ); - - $expected = '

test source value

'; - $result = $block->render(); - - // Check if the block content was updated correctly. - $this->assertEquals( $expected, $result, 'The block content should be updated with the value returned by the source.' ); - } -} diff --git a/tests/phpunit/tests/block-bindings/block-bindings.php b/tests/phpunit/tests/block-bindings/block-bindings.php index f0bab20e9d04d..d8e63fa6bcf8e 100644 --- a/tests/phpunit/tests/block-bindings/block-bindings.php +++ b/tests/phpunit/tests/block-bindings/block-bindings.php @@ -13,6 +13,11 @@ */ class WP_Block_Bindings_Test extends WP_UnitTestCase { + const SOURCE_NAME = 'test/source'; + const SOURCE_LABEL = array( + 'label' => 'Test source', + ); + /** * Set up before each test. * @@ -29,29 +34,27 @@ public function set_up() { } /** - * Test if the block content is updated with the value returned by the source. - * - * @ticket 60282 - * - * @covers register_block_bindings_source - */ - public function test_replace_html_for_paragraph_content() { - $source_name = 'test/source'; - $label = 'Test Source'; + * Test if the block content is updated with the value returned by the source. + * + * @ticket 60282 + * + * @covers register_block_bindings_source + */ + public function test_update_block_with_value_from_source() { $get_value_callback = function () { return 'test source value'; }; register_block_bindings_source( - $source_name, + self::SOURCE_NAME, array( - 'label' => $label, + 'label' => self::SOURCE_LABEL, 'get_value_callback' => $get_value_callback, ) ); $block_content = <<

This should not appear

+

This should not appear

HTML; $parsed_blocks = parse_blocks( $block_content ); @@ -63,4 +66,41 @@ public function test_replace_html_for_paragraph_content() { // Check if the block content was updated correctly. $this->assertEquals( $expected, $result, 'The block content should be updated with the value returned by the source.' ); } + + /** + * Test passing arguments to the source. + * + * @ticket 60282 + * + * @covers register_block_bindings_source + */ + public function test_passing_arguments_to_source() { + $get_value_callback = function ( $source_args, $block_instance, $attribute_name ) { + $value = $source_args['key']; + return "The attribute name is '$attribute_name' and its binding has argument 'key' with value '$value'."; + }; + + register_block_bindings_source( + self::SOURCE_NAME, + array( + 'label' => self::SOURCE_LABEL, + 'get_value_callback' => $get_value_callback, + ) + ); + + $key = 'test'; + + $block_content = <<

This should not appear

+HTML; + + $parsed_blocks = parse_blocks( $block_content ); + $block = new WP_Block( $parsed_blocks[0] ); + + $expected = "

The attribute name is 'content' and its binding has argument 'key' with value 'test'.

"; + $result = $block->render(); + + // Check if the block content was updated correctly. + $this->assertEquals( $expected, $result, 'The block content should be updated with the value returned by the source.' ); + } } From 1ceef1b29561f79d9f6b9787274cbe96ab6915bf Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Tue, 30 Jan 2024 17:47:59 +0000 Subject: [PATCH 56/72] Undo the changes in register.php --- src/wp-includes/block-bindings/sources/pattern.php | 2 +- tests/phpunit/tests/block-bindings/register.php | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/wp-includes/block-bindings/sources/pattern.php b/src/wp-includes/block-bindings/sources/pattern.php index 380a44331a413..d7e730c89e150 100644 --- a/src/wp-includes/block-bindings/sources/pattern.php +++ b/src/wp-includes/block-bindings/sources/pattern.php @@ -17,7 +17,7 @@ function pattern_source_callback( $source_attrs, $block_instance, $attribute_nam register_block_bindings_source( 'core/pattern-attributes', array( - 'label' => __( 'Pattern Attributes' ), + 'label' => _x( 'Pattern Attributes', '' ), 'get_value_callback' => 'pattern_source_callback', ) ); diff --git a/tests/phpunit/tests/block-bindings/register.php b/tests/phpunit/tests/block-bindings/register.php index a87ae1dc56f3a..26ac630440ba2 100644 --- a/tests/phpunit/tests/block-bindings/register.php +++ b/tests/phpunit/tests/block-bindings/register.php @@ -23,7 +23,9 @@ class Tests_Block_Bindings_Register extends WP_UnitTestCase { */ public function set_up() { foreach ( get_all_registered_block_bindings_sources() as $source_name => $source_properties ) { - unregister_block_bindings_source( $source_name ); + if ( str_starts_with( $source_name, 'test/' ) ) { + unregister_block_bindings_source( $source_name ); + } } parent::set_up(); @@ -36,6 +38,9 @@ public function set_up() { */ public function tear_down() { foreach ( get_all_registered_block_bindings_sources() as $source_name => $source_properties ) { + if ( str_starts_with( $source_name, 'test/' ) ) { + unregister_block_bindings_source( $source_name ); + } unregister_block_bindings_source( $source_name ); } From 5f397aa256a30f184e4c44ccbef441dc45d5b78d Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Tue, 30 Jan 2024 17:52:41 +0000 Subject: [PATCH 57/72] Update the docstring for `process_block_bindings` --- 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 e82fcbe36cc32..85a10c11d6392 100644 --- a/src/wp-includes/class-wp-block.php +++ b/src/wp-includes/class-wp-block.php @@ -196,7 +196,7 @@ public function __get( $name ) { * * A block might contain bindings in its attributes. Bindings are mappings * between an attribute of the block and a source. A "source" is a function - * registered with `$this->register()` that defines how to + * registered with `register_block_bindings_source()` that defines how to * retrieve a value from outside the block, e.g. from post meta. * * This function will process those bindings and replace the HTML with the value of the binding. From f2a611b4f76f2791536d42d0d13b8f49ac13dfd2 Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Tue, 30 Jan 2024 17:55:52 +0000 Subject: [PATCH 58/72] Update source_name parameter documentation in block-bindings.php and class-wp-block-bindings-registry.php --- src/wp-includes/block-bindings.php | 4 +++- src/wp-includes/class-wp-block-bindings-registry.php | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/wp-includes/block-bindings.php b/src/wp-includes/block-bindings.php index d0def1c86ea9e..a996475989d5c 100644 --- a/src/wp-includes/block-bindings.php +++ b/src/wp-includes/block-bindings.php @@ -19,7 +19,9 @@ * * @since 6.5.0 * - * @param string $source_name The name of the source. + * @param string $source_name The name of the source. It must be a string containing a namespace prefix, i.e. + * `my-plugin/my-custom-source`. It must only contain lowercase alphanumeric + * characters, the forward slash `/` and dashes. * @param array $source_properties { * The array of arguments that are used to register a source. * diff --git a/src/wp-includes/class-wp-block-bindings-registry.php b/src/wp-includes/class-wp-block-bindings-registry.php index 8b363875529d0..81969c2d5671d 100644 --- a/src/wp-includes/class-wp-block-bindings-registry.php +++ b/src/wp-includes/class-wp-block-bindings-registry.php @@ -42,7 +42,9 @@ final class WP_Block_Bindings_Registry { * * @since 6.5.0 * - * @param string $source_name The name of the source. + * @param string $source_name The name of the source. It must be a string containing a namespace prefix, i.e. + * `my-plugin/my-custom-source`. It must only contain lowercase alphanumeric + * characters, the forward slash `/` and dashes. * @param array $source_properties { * The array of arguments that are used to register a source. * From a1e1bac8b3c9dfa0bc0a2be644c3b4382a2e3dc3 Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Tue, 30 Jan 2024 18:42:13 +0000 Subject: [PATCH 59/72] Refactor post_meta_source_callback function to use is_post_publicly_viewable() instead of checking post status directly --- src/wp-includes/block-bindings/sources/post-meta.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wp-includes/block-bindings/sources/post-meta.php b/src/wp-includes/block-bindings/sources/post-meta.php index 95715cf615c0b..b8fdd5d2775db 100644 --- a/src/wp-includes/block-bindings/sources/post-meta.php +++ b/src/wp-includes/block-bindings/sources/post-meta.php @@ -21,7 +21,7 @@ function post_meta_source_callback( $source_attrs ) { // If a post isn't public, we need to prevent // unauthorized users from accessing the post meta. $post = get_post( $post_id ); - if ( ( $post && 'publish' !== $post->post_status && ! current_user_can( 'read_post', $post_id ) ) || post_password_required( $post_id ) ) { + if ( ( is_post_publicly_viewable( $post ) && ! current_user_can( 'read_post', $post_id ) ) || post_password_required( $post_id ) ) { return null; } From 26571a748c2577ce70044027725579564c544f02 Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Tue, 30 Jan 2024 18:51:05 +0000 Subject: [PATCH 60/72] Remove unnecessary condition for unregistering block bindings sources --- tests/phpunit/tests/block-bindings/register.php | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/tests/phpunit/tests/block-bindings/register.php b/tests/phpunit/tests/block-bindings/register.php index f077fc62f2878..a87ae1dc56f3a 100644 --- a/tests/phpunit/tests/block-bindings/register.php +++ b/tests/phpunit/tests/block-bindings/register.php @@ -23,9 +23,7 @@ class Tests_Block_Bindings_Register extends WP_UnitTestCase { */ public function set_up() { foreach ( get_all_registered_block_bindings_sources() as $source_name => $source_properties ) { - if ( str_starts_with( $source_name, 'test/' ) ) { - unregister_block_bindings_source( $source_name ); - } + unregister_block_bindings_source( $source_name ); } parent::set_up(); @@ -38,9 +36,7 @@ public function set_up() { */ public function tear_down() { foreach ( get_all_registered_block_bindings_sources() as $source_name => $source_properties ) { - if ( str_starts_with( $source_name, 'test/' ) ) { - unregister_block_bindings_source( $source_name ); - } + unregister_block_bindings_source( $source_name ); } parent::tear_down(); From d189f50862512d8e0d7127e01fbee433a246abff Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Tue, 30 Jan 2024 19:13:32 +0000 Subject: [PATCH 61/72] Fix access control logic in post_meta_source_callback function --- src/wp-includes/block-bindings/sources/post-meta.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wp-includes/block-bindings/sources/post-meta.php b/src/wp-includes/block-bindings/sources/post-meta.php index b8fdd5d2775db..1f432dd267787 100644 --- a/src/wp-includes/block-bindings/sources/post-meta.php +++ b/src/wp-includes/block-bindings/sources/post-meta.php @@ -21,7 +21,7 @@ function post_meta_source_callback( $source_attrs ) { // If a post isn't public, we need to prevent // unauthorized users from accessing the post meta. $post = get_post( $post_id ); - if ( ( is_post_publicly_viewable( $post ) && ! current_user_can( 'read_post', $post_id ) ) || post_password_required( $post_id ) ) { + if ( ( ! is_post_publicly_viewable( $post ) && ! current_user_can( 'read_post', $post_id ) ) || post_password_required( $post_id ) ) { return null; } From d7201cea4e859f6830199544e72a20f07b72537e Mon Sep 17 00:00:00 2001 From: Ricardo Artemio Morales Date: Tue, 30 Jan 2024 17:18:32 -0500 Subject: [PATCH 62/72] Optimize call to post_password_required() --- src/wp-includes/block-bindings/sources/post-meta.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wp-includes/block-bindings/sources/post-meta.php b/src/wp-includes/block-bindings/sources/post-meta.php index 1f432dd267787..218221c2527d1 100644 --- a/src/wp-includes/block-bindings/sources/post-meta.php +++ b/src/wp-includes/block-bindings/sources/post-meta.php @@ -21,7 +21,7 @@ function post_meta_source_callback( $source_attrs ) { // If a post isn't public, we need to prevent // unauthorized users from accessing the post meta. $post = get_post( $post_id ); - if ( ( ! is_post_publicly_viewable( $post ) && ! current_user_can( 'read_post', $post_id ) ) || post_password_required( $post_id ) ) { + if ( ( ! is_post_publicly_viewable( $post ) && ! current_user_can( 'read_post', $post_id ) ) || post_password_required( $post ) ) { return null; } From 69ed7541a89a432704b52cae3b17b7c520418355 Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Wed, 31 Jan 2024 11:04:52 +0000 Subject: [PATCH 63/72] Update pattern source label and PHPDoc string. --- src/wp-includes/block-bindings/sources/pattern.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/wp-includes/block-bindings/sources/pattern.php b/src/wp-includes/block-bindings/sources/pattern.php index d7e730c89e150..f18fcec9f6c06 100644 --- a/src/wp-includes/block-bindings/sources/pattern.php +++ b/src/wp-includes/block-bindings/sources/pattern.php @@ -1,7 +1,7 @@ _x( 'Pattern Attributes', '' ), 'get_value_callback' => 'pattern_source_callback', From f10a4fe1f46f4215141ebf10ddbcdfbaa0f7969b Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Wed, 31 Jan 2024 12:55:59 +0000 Subject: [PATCH 64/72] Update block bindings source labels --- src/wp-includes/block-bindings/sources/pattern.php | 2 +- src/wp-includes/block-bindings/sources/post-meta.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/wp-includes/block-bindings/sources/pattern.php b/src/wp-includes/block-bindings/sources/pattern.php index f18fcec9f6c06..a544728eb8d33 100644 --- a/src/wp-includes/block-bindings/sources/pattern.php +++ b/src/wp-includes/block-bindings/sources/pattern.php @@ -17,7 +17,7 @@ function pattern_source_callback( $source_attrs, $block_instance, $attribute_nam register_block_bindings_source( 'core/pattern-overrides', array( - 'label' => _x( 'Pattern Attributes', '' ), + 'label' => _x( 'Pattern Overrides', 'block bindings source' ), 'get_value_callback' => 'pattern_source_callback', ) ); diff --git a/src/wp-includes/block-bindings/sources/post-meta.php b/src/wp-includes/block-bindings/sources/post-meta.php index 218221c2527d1..bacfc4da3c429 100644 --- a/src/wp-includes/block-bindings/sources/post-meta.php +++ b/src/wp-includes/block-bindings/sources/post-meta.php @@ -31,7 +31,7 @@ function post_meta_source_callback( $source_attrs ) { register_block_bindings_source( 'core/post-meta', array( - 'label' => _x( 'Post Meta', 'Post metadata to be read and used to substitute block content' ), + 'label' => _x( 'Post Meta', 'block bindings source' ), 'get_value_callback' => 'post_meta_source_callback', ) ); From 0bec8330a7102284a747458b37b3960cab4321a6 Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Thu, 1 Feb 2024 10:02:46 +0000 Subject: [PATCH 65/72] Wrap the block binding registrations with 'init' actions --- .../block-bindings/sources/pattern.php | 18 +++++++++++------- .../block-bindings/sources/post-meta.php | 18 +++++++++++------- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/src/wp-includes/block-bindings/sources/pattern.php b/src/wp-includes/block-bindings/sources/pattern.php index a544728eb8d33..5e304fd1351eb 100644 --- a/src/wp-includes/block-bindings/sources/pattern.php +++ b/src/wp-includes/block-bindings/sources/pattern.php @@ -14,10 +14,14 @@ function pattern_source_callback( $source_attrs, $block_instance, $attribute_nam return _wp_array_get( $block_instance->context, array( 'pattern/overrides', $block_id, $attribute_name ), null ); } -register_block_bindings_source( - 'core/pattern-overrides', - array( - 'label' => _x( 'Pattern Overrides', 'block bindings source' ), - 'get_value_callback' => 'pattern_source_callback', - ) -); +function register_block_bindings_pattern_overrides_source() { + register_block_bindings_source( + 'core/pattern-overrides', + array( + 'label' => _x( 'Pattern Overrides', 'block bindings source' ), + 'get_value_callback' => 'pattern_source_callback', + ) + ); +} + +add_action( 'init', 'register_block_bindings_pattern_overrides_source' ); diff --git a/src/wp-includes/block-bindings/sources/post-meta.php b/src/wp-includes/block-bindings/sources/post-meta.php index bacfc4da3c429..29ee04b5b2a8f 100644 --- a/src/wp-includes/block-bindings/sources/post-meta.php +++ b/src/wp-includes/block-bindings/sources/post-meta.php @@ -28,10 +28,14 @@ function post_meta_source_callback( $source_attrs ) { return get_post_meta( $post_id, $source_attrs['key'], true ); } -register_block_bindings_source( - 'core/post-meta', - array( - 'label' => _x( 'Post Meta', 'block bindings source' ), - 'get_value_callback' => 'post_meta_source_callback', - ) -); +function gutenberg_register_block_bindings_post_meta_source() { + register_block_bindings_source( + 'core/post-meta', + array( + 'label' => _x( 'Post Meta', 'block bindings source' ), + 'get_value_callback' => 'post_meta_source_callback', + ) + ); +} + +add_action( 'init', 'gutenberg_register_block_bindings_post_meta_source' ); From 3134faa0510cf92fb5b686d8c9773ad0d2b8c5f5 Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Thu, 1 Feb 2024 10:06:17 +0000 Subject: [PATCH 66/72] Use $this->block_type directly in replace_html method --- 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 25daa530977f7..6d66dbe95df3b 100644 --- a/src/wp-includes/class-wp-block.php +++ b/src/wp-includes/class-wp-block.php @@ -291,7 +291,7 @@ private function process_block_bindings( $block_content ) { * @param string $source_value The value used to replace the HTML. */ private function replace_html( string $block_content, string $block_name, string $block_attr, string $source_value ) { - $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block_name ); + $block_type = $this->block_type; if ( null === $block_type || ! isset( $block_type->attributes[ $block_attr ] ) ) { return $block_content; } From 9491be32d7f5e490dbe2aa086320032b2387f969 Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Thu, 1 Feb 2024 10:16:43 +0000 Subject: [PATCH 67/72] Mark functions passed to init actions in block binding sources as private and add PHP docstrings --- src/wp-includes/block-bindings/sources/pattern.php | 7 +++++++ src/wp-includes/block-bindings/sources/post-meta.php | 6 ++++++ 2 files changed, 13 insertions(+) diff --git a/src/wp-includes/block-bindings/sources/pattern.php b/src/wp-includes/block-bindings/sources/pattern.php index 5e304fd1351eb..d79f69f408320 100644 --- a/src/wp-includes/block-bindings/sources/pattern.php +++ b/src/wp-includes/block-bindings/sources/pattern.php @@ -14,6 +14,13 @@ function pattern_source_callback( $source_attrs, $block_instance, $attribute_nam return _wp_array_get( $block_instance->context, array( 'pattern/overrides', $block_id, $attribute_name ), null ); } + +/** + * Registers the "pattern" source for the Block Bindings API. + * + * @access private + * @since 6.5.0 + */ function register_block_bindings_pattern_overrides_source() { register_block_bindings_source( 'core/pattern-overrides', diff --git a/src/wp-includes/block-bindings/sources/post-meta.php b/src/wp-includes/block-bindings/sources/post-meta.php index 29ee04b5b2a8f..b772600b1a9db 100644 --- a/src/wp-includes/block-bindings/sources/post-meta.php +++ b/src/wp-includes/block-bindings/sources/post-meta.php @@ -28,6 +28,12 @@ function post_meta_source_callback( $source_attrs ) { return get_post_meta( $post_id, $source_attrs['key'], true ); } +/** + * Registers the "post_meta" source for the Block Bindings API. + * + * @access private + * @since 6.5.0 + */ function gutenberg_register_block_bindings_post_meta_source() { register_block_bindings_source( 'core/post-meta', From c95b92fe9936b4790bd7de2890188c898ba2aee0 Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Thu, 1 Feb 2024 10:21:28 +0000 Subject: [PATCH 68/72] Prefix block binding function names with `_` to mark as private --- src/wp-includes/block-bindings/sources/pattern.php | 2 +- src/wp-includes/block-bindings/sources/post-meta.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/wp-includes/block-bindings/sources/pattern.php b/src/wp-includes/block-bindings/sources/pattern.php index d79f69f408320..f67299e82ab61 100644 --- a/src/wp-includes/block-bindings/sources/pattern.php +++ b/src/wp-includes/block-bindings/sources/pattern.php @@ -21,7 +21,7 @@ function pattern_source_callback( $source_attrs, $block_instance, $attribute_nam * @access private * @since 6.5.0 */ -function register_block_bindings_pattern_overrides_source() { +function _register_block_bindings_pattern_overrides_source() { register_block_bindings_source( 'core/pattern-overrides', array( diff --git a/src/wp-includes/block-bindings/sources/post-meta.php b/src/wp-includes/block-bindings/sources/post-meta.php index b772600b1a9db..87d9482b44973 100644 --- a/src/wp-includes/block-bindings/sources/post-meta.php +++ b/src/wp-includes/block-bindings/sources/post-meta.php @@ -34,7 +34,7 @@ function post_meta_source_callback( $source_attrs ) { * @access private * @since 6.5.0 */ -function gutenberg_register_block_bindings_post_meta_source() { +function _gutenberg_register_block_bindings_post_meta_source() { register_block_bindings_source( 'core/post-meta', array( From 3619e184c193dca15dca72dc6fa7a167aedefa4e Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Thu, 1 Feb 2024 10:22:17 +0000 Subject: [PATCH 69/72] Update the function names again --- src/wp-includes/block-bindings/sources/pattern.php | 2 +- src/wp-includes/block-bindings/sources/post-meta.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/wp-includes/block-bindings/sources/pattern.php b/src/wp-includes/block-bindings/sources/pattern.php index f67299e82ab61..863f73da871e0 100644 --- a/src/wp-includes/block-bindings/sources/pattern.php +++ b/src/wp-includes/block-bindings/sources/pattern.php @@ -31,4 +31,4 @@ function _register_block_bindings_pattern_overrides_source() { ); } -add_action( 'init', 'register_block_bindings_pattern_overrides_source' ); +add_action( 'init', '_register_block_bindings_pattern_overrides_source' ); diff --git a/src/wp-includes/block-bindings/sources/post-meta.php b/src/wp-includes/block-bindings/sources/post-meta.php index 87d9482b44973..0aa55ba1800bc 100644 --- a/src/wp-includes/block-bindings/sources/post-meta.php +++ b/src/wp-includes/block-bindings/sources/post-meta.php @@ -34,7 +34,7 @@ function post_meta_source_callback( $source_attrs ) { * @access private * @since 6.5.0 */ -function _gutenberg_register_block_bindings_post_meta_source() { +function _register_block_bindings_post_meta_source() { register_block_bindings_source( 'core/post-meta', array( @@ -44,4 +44,4 @@ function _gutenberg_register_block_bindings_post_meta_source() { ); } -add_action( 'init', 'gutenberg_register_block_bindings_post_meta_source' ); +add_action( 'init', '_register_block_bindings_post_meta_source' ); From c8232ace2a0b24125a5eb5578aa2ca3b30e80e83 Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Thu, 1 Feb 2024 12:11:29 +0000 Subject: [PATCH 70/72] Prevent pattern and post meta sources from registering in unit tests --- tests/phpunit/includes/functions.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/phpunit/includes/functions.php b/tests/phpunit/includes/functions.php index 81d4339db1bf8..0fdff9c71ae46 100644 --- a/tests/phpunit/includes/functions.php +++ b/tests/phpunit/includes/functions.php @@ -339,10 +339,15 @@ function _wp_rest_server_class_filter() { * @since 5.0.0 */ function _unhook_block_registration() { + // Block types. require __DIR__ . '/unregister-blocks-hooks.php'; remove_action( 'init', 'register_core_block_types_from_metadata' ); remove_action( 'init', 'register_block_core_legacy_widget' ); remove_action( 'init', 'register_block_core_widget_group' ); remove_action( 'init', 'register_core_block_types_from_metadata' ); + + // Block binding sources. + remove_action( 'init', '_register_block_bindings_pattern_overrides_source' ); + remove_action( 'init', '_register_block_bindings_post_meta_source' ); } tests_add_filter( 'init', '_unhook_block_registration', 1000 ); From 871cedc9fd524fc2476c6192de839349597ab34b Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Thu, 1 Feb 2024 12:27:18 +0000 Subject: [PATCH 71/72] Use `call_user_func_array` to call the source binding callback in WP_Block class --- 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 6d66dbe95df3b..842ab0f8576d0 100644 --- a/src/wp-includes/class-wp-block.php +++ b/src/wp-includes/class-wp-block.php @@ -268,7 +268,7 @@ private function process_block_bindings( $block_content ) { } else { $source_args = $binding_source['args']; } - $source_value = $source_callback( $source_args, $this, $binding_attribute ); + $source_value = call_user_func_array( $source_callback, array( $source_args, $this, $binding_attribute ) ); // If the value is null, process next attribute. if ( is_null( $source_value ) ) { continue; From 604f7a2cb3767163c7d12a4f571f76b7a47d20a6 Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Thu, 1 Feb 2024 12:42:26 +0000 Subject: [PATCH 72/72] Validate that `$block['attrs']['metadata']['bindings']` is an array in `process_block_bindings()` --- src/wp-includes/class-wp-block.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/wp-includes/class-wp-block.php b/src/wp-includes/class-wp-block.php index 842ab0f8576d0..f639594b1129a 100644 --- a/src/wp-includes/class-wp-block.php +++ b/src/wp-includes/class-wp-block.php @@ -244,8 +244,12 @@ private function process_block_bindings( $block_content ) { 'core/button' => array( 'url', 'text' ), ); - // If the block doesn't have the bindings property or isn't one of the allowed block types, return. - if ( ! isset( $block['attrs']['metadata']['bindings'] ) || ! isset( $allowed_blocks[ $this->name ] ) ) { + // If the block doesn't have the bindings property, isn't one of the allowed + // block types, or the bindings property is not an array, return the block content. + if ( ! isset( $block['attrs']['metadata']['bindings'] ) || + ! is_array( $block['attrs']['metadata']['bindings'] ) || + ! isset( $allowed_blocks[ $this->name ] ) + ) { return $block_content; }