From ea329575989de58fc3b558b46f9a90cbfac635cc Mon Sep 17 00:00:00 2001 From: Michal Date: Wed, 24 Jan 2024 18:46:18 +0000 Subject: [PATCH] Block Bindings: Update source registration syntax and remove APIs that should be private (#58205) * Refactor block bindings registration Second param is an array now * Add array types * Update params in the sources * Refactor block bindings to use gutenberg_block_bindings_replace_html * rename $source_args to $source_properties * dont safeguard `gutenberg_block_bindings_replace_html` against redeclaration * Remove textdomain for pattern attributes and post meta sources --- .../block-bindings/block-bindings.php | 118 +++++++++++++-- .../class-wp-block-bindings.php | 137 +++--------------- .../block-bindings/sources/pattern.php | 6 +- .../block-bindings/sources/post-meta.php | 6 +- lib/compat/wordpress-6.5/blocks.php | 2 +- 5 files changed, 136 insertions(+), 133 deletions(-) diff --git a/lib/compat/wordpress-6.5/block-bindings/block-bindings.php b/lib/compat/wordpress-6.5/block-bindings/block-bindings.php index 83a1d6132f5f4..f9b3394661393 100644 --- a/lib/compat/wordpress-6.5/block-bindings/block-bindings.php +++ b/lib/compat/wordpress-6.5/block-bindings/block-bindings.php @@ -27,18 +27,24 @@ function wp_block_bindings() { * Registers a new source for block bindings. * * @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_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: + * + * `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 */ if ( ! function_exists( 'wp_block_bindings_register_source' ) ) { - 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, array $source_properties ) { + wp_block_bindings()->register_source( $source_name, $source_properties ); } } @@ -62,8 +68,96 @@ function wp_block_bindings_get_sources() { * @param string $source_value The value used to replace the HTML. * @return string The modified block content. */ -if ( ! function_exists( 'wp_block_bindings_replace_html' ) ) { - 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 ); +function gutenberg_block_bindings_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>" . 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; } diff --git a/lib/compat/wordpress-6.5/block-bindings/class-wp-block-bindings.php b/lib/compat/wordpress-6.5/block-bindings/class-wp-block-bindings.php index fedc652688a24..68b51348010e3 100644 --- a/lib/compat/wordpress-6.5/block-bindings/class-wp-block-bindings.php +++ b/lib/compat/wordpress-6.5/block-bindings/class-wp-block-bindings.php @@ -25,126 +25,31 @@ class WP_Block_Bindings { private $sources = array(); /** - * Function to register a new source. + * Function to register a new block binding source. * - * @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. + * 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. * - * @return void - */ - public function register_source( $source_name, $label, $apply ) { - $this->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 $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: + * + * `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 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 void */ - 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>" . 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; + public function register_source( $source_name, array $source_properties ) { + $this->sources[ $source_name ] = $source_properties; } /** diff --git a/lib/compat/wordpress-6.5/block-bindings/sources/pattern.php b/lib/compat/wordpress-6.5/block-bindings/sources/pattern.php index 740d1983d6fe8..65ddb7278e703 100644 --- a/lib/compat/wordpress-6.5/block-bindings/sources/pattern.php +++ b/lib/compat/wordpress-6.5/block-bindings/sources/pattern.php @@ -29,7 +29,9 @@ }; wp_block_bindings_register_source( 'pattern_attributes', - __( 'Pattern Attributes', 'gutenberg' ), - $pattern_source_callback + array( + 'label' => __( 'Pattern Attributes' ), + 'apply' => $pattern_source_callback, + ) ); } diff --git a/lib/compat/wordpress-6.5/block-bindings/sources/post-meta.php b/lib/compat/wordpress-6.5/block-bindings/sources/post-meta.php index 2d53dab6d321a..e52b4f289ccdd 100644 --- a/lib/compat/wordpress-6.5/block-bindings/sources/post-meta.php +++ b/lib/compat/wordpress-6.5/block-bindings/sources/post-meta.php @@ -18,7 +18,9 @@ }; wp_block_bindings_register_source( 'post_meta', - __( 'Post Meta', 'gutenberg' ), - $post_meta_source_callback + array( + 'label' => __( 'Post Meta' ), + 'apply' => $post_meta_source_callback, + ) ); } diff --git a/lib/compat/wordpress-6.5/blocks.php b/lib/compat/wordpress-6.5/blocks.php index dccaeba5cc335..e1b91364fe22e 100644 --- a/lib/compat/wordpress-6.5/blocks.php +++ b/lib/compat/wordpress-6.5/blocks.php @@ -117,7 +117,7 @@ function gutenberg_process_block_bindings( $block_content, $block, $block_instan } // 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 ); + $modified_block_content = gutenberg_block_bindings_replace_html( $modified_block_content, $block_instance->name, $binding_attribute, $source_value ); } return $modified_block_content; }