From 2a9e826ab9777d0997e402af1a9881743d0ca3c2 Mon Sep 17 00:00:00 2001 From: Mario Santos Date: Mon, 29 Jan 2024 13:38:07 +0100 Subject: [PATCH 01/11] Update sources registration logic --- .../block-bindings/sources/pattern.php | 30 +++++++++++++++++++ .../block-bindings/sources/post-meta.php | 25 +++++++++------- 2 files changed, 45 insertions(+), 10 deletions(-) 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 9540e329596c9..a8a895fe626bb 100644 --- a/lib/compat/wordpress-6.5/block-bindings/sources/pattern.php +++ b/lib/compat/wordpress-6.5/block-bindings/sources/pattern.php @@ -35,3 +35,33 @@ ) ); } + +if ( ! function_exists( 'gutenberg_register_block_bindings_pattern_overrides_source' ) && ! function_exists( 'gutenberg_block_bindings_pattern_overrides_callback' ) ) { + function gutenberg_block_bindings_pattern_overrides_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']; + } else { + // I tried using $block_instance->context['postId'] but it wasn't available in the image block. + $post_id = get_the_ID(); + } + + return get_post_meta( $post_id, $source_attrs['key'], true ); + } + + function gutenberg_register_block_bindings_pattern_overrides_source() { + wp_block_bindings_register_source( + 'core/post-meta', + array( + 'label' => __( 'Post Meta' ), + 'apply' => 'gutenberg_block_bindings_pattern_overrides_callback', + ) + ); + } +} + +add_action( 'init', 'gutenberg_register_block_bindings_pattern_overrides_source' ); 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 6d8951b5660ab..f979bb2b4b34a 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 @@ -4,8 +4,8 @@ * * @package gutenberg */ -if ( function_exists( 'wp_block_bindings_register_source' ) ) { - $post_meta_source_callback = function ( $source_attrs ) { +if ( ! function_exists( 'gutenberg_register_block_bindings_post_meta_source' ) && ! function_exists( 'gutenberg_block_bindings_post_meta_callback' ) ) { + function gutenberg_block_bindings_post_meta_callback( $source_attrs ) { if ( ! isset( $source_attrs['key'] ) ) { return null; } @@ -19,12 +19,17 @@ } return get_post_meta( $post_id, $source_attrs['key'], true ); - }; - wp_block_bindings_register_source( - 'core/post-meta', - array( - 'label' => __( 'Post Meta' ), - 'apply' => $post_meta_source_callback, - ) - ); + } + + function gutenberg_register_block_bindings_post_meta_source() { + wp_block_bindings_register_source( + 'core/post-meta', + array( + 'label' => __( 'Post Meta' ), + 'apply' => 'gutenberg_block_bindings_post_meta_callback', + ) + ); + } } + +add_action( 'init', 'gutenberg_register_block_bindings_post_meta_source' ); From f808acdb78850651fc54b3f412202763472c332a Mon Sep 17 00:00:00 2001 From: Mario Santos Date: Mon, 29 Jan 2024 13:58:06 +0100 Subject: [PATCH 02/11] Remove old function from pattern source --- .../block-bindings/sources/pattern.php | 32 ------------------- 1 file changed, 32 deletions(-) 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 a8a895fe626bb..1434ab3b1b5c7 100644 --- a/lib/compat/wordpress-6.5/block-bindings/sources/pattern.php +++ b/lib/compat/wordpress-6.5/block-bindings/sources/pattern.php @@ -4,38 +4,6 @@ * * @package gutenberg */ -if ( function_exists( 'wp_block_bindings_register_source' ) ) { - $pattern_source_callback = function ( $source_attrs, $block_instance, $attribute_name ) { - if ( ! _wp_array_get( $block_instance->attributes, array( 'metadata', 'id' ), false ) ) { - return null; - } - $block_id = $block_instance->attributes['metadata']['id']; - $attribute_override = _wp_array_get( $block_instance->context, array( 'pattern/overrides', $block_id, $attribute_name ), null ); - if ( null === $attribute_override ) { - return null; - } - switch ( $attribute_override[0] ) { - case 0: // remove - /** - * TODO: This currently doesn't remove the attribute, but only set it to an empty string. - * It's a temporary solution until the block binding API supports different operations. - */ - return ''; - case 1: // replace - return $attribute_override[1]; - default: - return null; - } - }; - wp_block_bindings_register_source( - 'core/pattern-attributes', - array( - 'label' => __( 'Pattern Attributes' ), - 'apply' => $pattern_source_callback, - ) - ); -} - if ( ! function_exists( 'gutenberg_register_block_bindings_pattern_overrides_source' ) && ! function_exists( 'gutenberg_block_bindings_pattern_overrides_callback' ) ) { function gutenberg_block_bindings_pattern_overrides_callback( $source_attrs ) { if ( ! isset( $source_attrs['key'] ) ) { From 36b3bea5bf724dbbd3f35593297fb647cc3e9b9e Mon Sep 17 00:00:00 2001 From: Mario Santos Date: Mon, 29 Jan 2024 14:13:54 +0100 Subject: [PATCH 03/11] Update pattern source logic --- .../block-bindings/sources/pattern.php | 35 +++++++++++-------- 1 file changed, 21 insertions(+), 14 deletions(-) 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 1434ab3b1b5c7..97c61240efed6 100644 --- a/lib/compat/wordpress-6.5/block-bindings/sources/pattern.php +++ b/lib/compat/wordpress-6.5/block-bindings/sources/pattern.php @@ -5,27 +5,34 @@ * @package gutenberg */ if ( ! function_exists( 'gutenberg_register_block_bindings_pattern_overrides_source' ) && ! function_exists( 'gutenberg_block_bindings_pattern_overrides_callback' ) ) { - function gutenberg_block_bindings_pattern_overrides_callback( $source_attrs ) { - if ( ! isset( $source_attrs['key'] ) ) { + function gutenberg_block_bindings_pattern_overrides_callback( $source_attrs, $block_instance, $attribute_name ) { + if ( ! _wp_array_get( $block_instance->attributes, array( 'metadata', 'id' ), false ) ) { return null; } - - // 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. - $post_id = get_the_ID(); + $block_id = $block_instance->attributes['metadata']['id']; + $attribute_override = _wp_array_get( $block_instance->context, array( 'pattern/overrides', $block_id, $attribute_name ), null ); + if ( null === $attribute_override ) { + return null; + } + switch ( $attribute_override[0] ) { + case 0: // remove + /** + * TODO: This currently doesn't remove the attribute, but only set it to an empty string. + * It's a temporary solution until the block binding API supports different operations. + */ + return ''; + case 1: // replace + return $attribute_override[1]; + default: + return null; } - - return get_post_meta( $post_id, $source_attrs['key'], true ); } function gutenberg_register_block_bindings_pattern_overrides_source() { - wp_block_bindings_register_source( - 'core/post-meta', + register_block_bindings_source( + 'core/pattern-attributes', array( - 'label' => __( 'Post Meta' ), + 'label' => __( 'Pattern Attributes' ), 'apply' => 'gutenberg_block_bindings_pattern_overrides_callback', ) ); From c23c6698b1aa671ff8af4ad337b30c04eb5ae205 Mon Sep 17 00:00:00 2001 From: Mario Santos Date: Mon, 29 Jan 2024 14:16:11 +0100 Subject: [PATCH 04/11] Adapt `WP_Block_Bindings_Registry` class --- .../class-wp-block-bindings-registry.php | 197 ++++++++++++++++++ .../class-wp-block-bindings.php | 63 ------ lib/load.php | 2 +- 3 files changed, 198 insertions(+), 64 deletions(-) create mode 100644 lib/compat/wordpress-6.5/block-bindings/class-wp-block-bindings-registry.php delete mode 100644 lib/compat/wordpress-6.5/block-bindings/class-wp-block-bindings.php diff --git a/lib/compat/wordpress-6.5/block-bindings/class-wp-block-bindings-registry.php b/lib/compat/wordpress-6.5/block-bindings/class-wp-block-bindings-registry.php new file mode 100644 index 0000000000000..7db8ab6492767 --- /dev/null +++ b/lib/compat/wordpress-6.5/block-bindings/class-wp-block-bindings-registry.php @@ -0,0 +1,197 @@ +is_registered( $source_name ) ) { + _doing_it_wrong( + __METHOD__, + /* translators: %s: Block bindings source name. */ + sprintf( __( 'Block bindings sources "%s" already registered.' ), $source_name ), + '6.5.0' + ); + return false; + } + + $source = array_merge( + array( 'name' => $source_name ), + $source_properties + ); + + $this->sources[ $source_name ] = $source; + + return $source; + } + + /** + * Unregisters a block bindings source. + * + * @since 6.5.0 + * + * @param string $source_name Block bindings source name including namespace. + * @return array|false The unregistred block bindings source on success and `false` otherwise. + */ + public function unregister( $source_name ) { + if ( ! $this->is_registered( $source_name ) ) { + _doing_it_wrong( + __METHOD__, + /* translators: %s: Block bindings source name. */ + sprintf( __( 'Block bindings "%s" not found.' ), $source_name ), + '6.5.0' + ); + return false; + } + + $unregistered_source = $this->sources[ $source_name ]; + unset( $this->sources[ $source_name ] ); + + return $unregistered_source; + } + + /** + * 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 bindings 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 bindings source is registered. + * + * @since 6.5.0 + * + * @param string $source_name The name of the source. + * @return bool `true` if the block bindings 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/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 deleted file mode 100644 index 68b51348010e3..0000000000000 --- a/lib/compat/wordpress-6.5/block-bindings/class-wp-block-bindings.php +++ /dev/null @@ -1,63 +0,0 @@ -sources[ $source_name ] = $source_properties; - } - - /** - * Retrieves the list of registered block sources. - * - * @return array The array of registered sources. - */ - public function get_sources() { - return $this->sources; - } -} diff --git a/lib/load.php b/lib/load.php index 4b2b4d5d8b0db..d1fc2a3bb2663 100644 --- a/lib/load.php +++ b/lib/load.php @@ -112,7 +112,7 @@ function gutenberg_is_experiment_enabled( $name ) { require __DIR__ . '/compat/wordpress-6.5/interactivity-api/interactivity-api.php'; require __DIR__ . '/compat/wordpress-6.5/class-wp-script-modules.php'; require __DIR__ . '/compat/wordpress-6.5/scripts-modules.php'; -require __DIR__ . '/compat/wordpress-6.5/block-bindings/class-wp-block-bindings.php'; +require __DIR__ . '/compat/wordpress-6.5/block-bindings/class-wp-block-bindings-registry.php'; require __DIR__ . '/compat/wordpress-6.5/block-bindings/block-bindings.php'; require __DIR__ . '/compat/wordpress-6.5/block-bindings/sources/post-meta.php'; require __DIR__ . '/compat/wordpress-6.5/block-bindings/sources/pattern.php'; From 9ca57ac02bddf366ad00e87a3dfce20863122966 Mon Sep 17 00:00:00 2001 From: Mario Santos Date: Mon, 29 Jan 2024 14:18:10 +0100 Subject: [PATCH 05/11] Adapt block bindings methods --- .../block-bindings/block-bindings.php | 84 +++++++++++-------- lib/compat/wordpress-6.5/blocks.php | 2 +- 2 files changed, 49 insertions(+), 37 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 f9b3394661393..6c874d58f5c4e 100644 --- a/lib/compat/wordpress-6.5/block-bindings/block-bindings.php +++ b/lib/compat/wordpress-6.5/block-bindings/block-bindings.php @@ -2,60 +2,72 @@ /** * Block Bindings API * - * This file contains functions for managing block bindings in WordPress. + * Contains functions for managing block bindings in WordPress. * - * @since 17.6.0 - * @package gutenberg + * @package WordPress + * @subpackage Block Bindings + * @since 6.5.0 */ /** - * Retrieves the singleton instance of WP_Block_Bindings. + * Registers a new block bindings source. * - * @return WP_Block_Bindings The WP_Block_Bindings instance. + * 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 + * + * @param string $source_name The name of the source. + * @param array $source_properties { + * The array of arguments that are used to register a 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 ($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 array|false Source when the registration was successful, or `false` on failure. */ -if ( ! function_exists( 'wp_block_bindings' ) ) { - function wp_block_bindings() { - static $instance = null; - if ( is_null( $instance ) ) { - $instance = new WP_Block_Bindings(); - } - return $instance; +if ( ! function_exists( 'register_block_bindings_source' ) ) { + function register_block_bindings_source( $source_name, array $source_properties ) { + return WP_Block_Bindings_Registry::get_instance()->register( $source_name, $source_properties ); } } /** - * Registers a new source for block bindings. - * - * @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: + * Unregisters a block bindings 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 Block bindings source name including namespace. + * @return array|false The unregistred block bindings source on success and `false` otherwise. */ -if ( ! function_exists( 'wp_block_bindings_register_source' ) ) { - function wp_block_bindings_register_source( $source_name, array $source_properties ) { - wp_block_bindings()->register_source( $source_name, $source_properties ); +if ( ! function_exists( 'unregister_block_bindings_source' ) ) { + function unregister_block_bindings_source( $source_name ) { + return WP_Block_Bindings_Registry::get_instance()->unregister( $source_name ); } } /** - * Retrieves the list of registered block sources. + * Retrieves the list of all registered block bindings sources. + * + * @since 6.5.0 * - * @return array The list of registered block sources. + * @return array The array of registered block bindings sources. */ -if ( ! function_exists( 'wp_block_bindings_get_sources' ) ) { - function wp_block_bindings_get_sources() { - return wp_block_bindings()->get_sources(); +if ( ! function_exists( 'get_all_registered_block_bindings_sources' ) ) { + function get_all_registered_block_bindings_sources() { + return WP_Block_Bindings_Registry::get_instance()->get_all_registered(); } } @@ -69,7 +81,7 @@ function wp_block_bindings_get_sources() { * @return string The modified block content. */ 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 ); + $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block_name ); if ( null === $block_type ) { return; } diff --git a/lib/compat/wordpress-6.5/blocks.php b/lib/compat/wordpress-6.5/blocks.php index 7888973397f6c..a0021bef8deed 100644 --- a/lib/compat/wordpress-6.5/blocks.php +++ b/lib/compat/wordpress-6.5/blocks.php @@ -86,7 +86,7 @@ function gutenberg_process_block_bindings( $block_content, $block, $block_instan * } */ - $block_bindings_sources = wp_block_bindings_get_sources(); + $block_bindings_sources = get_all_registered_block_bindings_sources(); $modified_block_content = $block_content; foreach ( $block['attrs']['metadata']['bindings'] as $binding_attribute => $binding_source ) { From 5f8c0bd8e7400d015e30cf51cdc0c5066fcefaab Mon Sep 17 00:00:00 2001 From: Mario Santos Date: Mon, 29 Jan 2024 14:19:11 +0100 Subject: [PATCH 06/11] Override core sources in Gutenberg --- lib/compat/wordpress-6.5/block-bindings/sources/pattern.php | 4 ++++ .../wordpress-6.5/block-bindings/sources/post-meta.php | 6 +++++- 2 files changed, 9 insertions(+), 1 deletion(-) 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 97c61240efed6..1f6e6029b083c 100644 --- a/lib/compat/wordpress-6.5/block-bindings/sources/pattern.php +++ b/lib/compat/wordpress-6.5/block-bindings/sources/pattern.php @@ -29,6 +29,10 @@ function gutenberg_block_bindings_pattern_overrides_callback( $source_attrs, $bl } function gutenberg_register_block_bindings_pattern_overrides_source() { + // Override the "core/pattern-attributes" source from core. + if ( array_key_exists( 'core/pattern-attributes', get_all_registered_block_bindings_sources() ) ) { + unregister_block_bindings_source( 'core/pattern-attributes' ); + } register_block_bindings_source( 'core/pattern-attributes', array( 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 f979bb2b4b34a..e915a02b398c7 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 @@ -22,7 +22,11 @@ function gutenberg_block_bindings_post_meta_callback( $source_attrs ) { } function gutenberg_register_block_bindings_post_meta_source() { - wp_block_bindings_register_source( + // Override the "core/post-meta" source from core. + if ( array_key_exists( 'core/post-meta', get_all_registered_block_bindings_sources() ) ) { + unregister_block_bindings_source( 'core/post-meta' ); + } + register_block_bindings_source( 'core/post-meta', array( 'label' => __( 'Post Meta' ), From 9686be158c07634746f6e58e29eadaa3664e20df Mon Sep 17 00:00:00 2001 From: Mario Santos Date: Mon, 29 Jan 2024 14:20:47 +0100 Subject: [PATCH 07/11] Use `get_value_callback` --- lib/compat/wordpress-6.5/block-bindings/sources/pattern.php | 4 ++-- lib/compat/wordpress-6.5/block-bindings/sources/post-meta.php | 4 ++-- lib/compat/wordpress-6.5/blocks.php | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) 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 1f6e6029b083c..956739bba324e 100644 --- a/lib/compat/wordpress-6.5/block-bindings/sources/pattern.php +++ b/lib/compat/wordpress-6.5/block-bindings/sources/pattern.php @@ -36,8 +36,8 @@ function gutenberg_register_block_bindings_pattern_overrides_source() { register_block_bindings_source( 'core/pattern-attributes', array( - 'label' => __( 'Pattern Attributes' ), - 'apply' => 'gutenberg_block_bindings_pattern_overrides_callback', + 'label' => __( 'Pattern Attributes' ), + 'get_value_callback' => 'gutenberg_block_bindings_pattern_overrides_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 e915a02b398c7..48e8598b208f6 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 @@ -29,8 +29,8 @@ function gutenberg_register_block_bindings_post_meta_source() { register_block_bindings_source( 'core/post-meta', array( - 'label' => __( 'Post Meta' ), - 'apply' => 'gutenberg_block_bindings_post_meta_callback', + 'label' => __( 'Post Meta' ), + 'get_value_callback' => 'gutenberg_block_bindings_post_meta_callback', ) ); } diff --git a/lib/compat/wordpress-6.5/blocks.php b/lib/compat/wordpress-6.5/blocks.php index a0021bef8deed..75afd0a8e3f83 100644 --- a/lib/compat/wordpress-6.5/blocks.php +++ b/lib/compat/wordpress-6.5/blocks.php @@ -99,7 +99,7 @@ function gutenberg_process_block_bindings( $block_content, $block, $block_instan continue; } - $source_callback = $block_bindings_sources[ $binding_source['source'] ]['apply']; + $source_callback = $block_bindings_sources[ $binding_source['source'] ]['get_value_callback']; // Get the value based on the source. if ( ! isset( $binding_source['args'] ) ) { $source_args = array(); From 27cb39f170a28622930bd8aede0be74a70650769 Mon Sep 17 00:00:00 2001 From: Mario Santos Date: Mon, 29 Jan 2024 14:26:19 +0100 Subject: [PATCH 08/11] Move `block_bindings_replace_html` to `blocks.php` --- .../block-bindings/block-bindings.php | 103 --------- lib/compat/wordpress-6.5/blocks.php | 214 +++++++++++++----- 2 files changed, 156 insertions(+), 161 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 6c874d58f5c4e..3da4ecc185ab5 100644 --- a/lib/compat/wordpress-6.5/block-bindings/block-bindings.php +++ b/lib/compat/wordpress-6.5/block-bindings/block-bindings.php @@ -70,106 +70,3 @@ function get_all_registered_block_bindings_sources() { return WP_Block_Bindings_Registry::get_instance()->get_all_registered(); } } - -/** - * 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 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/blocks.php b/lib/compat/wordpress-6.5/blocks.php index 75afd0a8e3f83..6c4b604e97e15 100644 --- a/lib/compat/wordpress-6.5/blocks.php +++ b/lib/compat/wordpress-6.5/blocks.php @@ -46,8 +46,109 @@ function gutenberg_register_metadata_attribute( $args ) { } add_filter( 'register_block_type_args', 'gutenberg_register_metadata_attribute' ); +/** + * 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 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; +} -if ( ! function_exists( 'gutenberg_process_block_bindings' ) ) { /** * Process the block bindings attribute. * @@ -55,68 +156,65 @@ function gutenberg_register_metadata_attribute( $args ) { * @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', 'linkTarget' ), - ); - - // 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; - } +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', 'linkTarget' ), + ); + + // 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": "core/post-meta", - * "args": { "key": "text_custom_field" } - * }, - * "url": { - * "source": "core/post-meta", - * "args": { "key": "url_custom_field" } - * } - * } - */ - - $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. - 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'] ) || ! is_string( $binding_source['source'] ) || ! isset( $block_bindings_sources[ $binding_source['source'] ] ) ) { - continue; - } + /* + * Assuming the following format for the bindings property of the "metadata" attribute: + * + * "bindings": { + * "title": { + * "source": "core/post-meta", + * "args": { "key": "text_custom_field" } + * }, + * "url": { + * "source": "core/post-meta", + * "args": { "key": "url_custom_field" } + * } + * } + */ - $source_callback = $block_bindings_sources[ $binding_source['source'] ]['get_value_callback']; - // Get the value based on the source. - if ( ! isset( $binding_source['args'] ) ) { - $source_args = array(); - } else { - $source_args = $binding_source['args']; - } - $source_value = $source_callback( $source_args, $block_instance, $binding_attribute ); - // If the value is null, process next attribute. - if ( is_null( $source_value ) ) { - continue; - } + $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. + 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'] ) || ! is_string( $binding_source['source'] ) || ! isset( $block_bindings_sources[ $binding_source['source'] ] ) ) { + continue; + } - // Process the HTML based on the block and the attribute. - $modified_block_content = gutenberg_block_bindings_replace_html( $modified_block_content, $block_instance->name, $binding_attribute, $source_value ); + $source_callback = $block_bindings_sources[ $binding_source['source'] ]['get_value_callback']; + // Get the value based on the source. + if ( ! isset( $binding_source['args'] ) ) { + $source_args = array(); + } else { + $source_args = $binding_source['args']; + } + $source_value = $source_callback( $source_args, $block_instance, $binding_attribute ); + // If the value is null, process next attribute. + if ( is_null( $source_value ) ) { + continue; } - return $modified_block_content; + + // Process the HTML based on the block and the attribute. + $modified_block_content = gutenberg_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 ); From 9587798a175b88499058d57bb41b36fb69d1f9e2 Mon Sep 17 00:00:00 2001 From: Mario Santos Date: Mon, 29 Jan 2024 15:14:18 +0100 Subject: [PATCH 09/11] Remove unnecessary `function_exists` checks --- .../block-bindings/sources/pattern.php | 64 +++++++++---------- .../block-bindings/sources/post-meta.php | 50 +++++++-------- 2 files changed, 55 insertions(+), 59 deletions(-) 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 956739bba324e..b43484660f1f8 100644 --- a/lib/compat/wordpress-6.5/block-bindings/sources/pattern.php +++ b/lib/compat/wordpress-6.5/block-bindings/sources/pattern.php @@ -4,43 +4,41 @@ * * @package gutenberg */ -if ( ! function_exists( 'gutenberg_register_block_bindings_pattern_overrides_source' ) && ! function_exists( 'gutenberg_block_bindings_pattern_overrides_callback' ) ) { - function gutenberg_block_bindings_pattern_overrides_callback( $source_attrs, $block_instance, $attribute_name ) { - if ( ! _wp_array_get( $block_instance->attributes, array( 'metadata', 'id' ), false ) ) { - return null; - } - $block_id = $block_instance->attributes['metadata']['id']; - $attribute_override = _wp_array_get( $block_instance->context, array( 'pattern/overrides', $block_id, $attribute_name ), null ); - if ( null === $attribute_override ) { +function gutenberg_block_bindings_pattern_overrides_callback( $source_attrs, $block_instance, $attribute_name ) { + if ( ! _wp_array_get( $block_instance->attributes, array( 'metadata', 'id' ), false ) ) { + return null; + } + $block_id = $block_instance->attributes['metadata']['id']; + $attribute_override = _wp_array_get( $block_instance->context, array( 'pattern/overrides', $block_id, $attribute_name ), null ); + if ( null === $attribute_override ) { + return null; + } + switch ( $attribute_override[0] ) { + case 0: // remove + /** + * TODO: This currently doesn't remove the attribute, but only set it to an empty string. + * It's a temporary solution until the block binding API supports different operations. + */ + return ''; + case 1: // replace + return $attribute_override[1]; + default: return null; - } - switch ( $attribute_override[0] ) { - case 0: // remove - /** - * TODO: This currently doesn't remove the attribute, but only set it to an empty string. - * It's a temporary solution until the block binding API supports different operations. - */ - return ''; - case 1: // replace - return $attribute_override[1]; - default: - return null; - } } +} - function gutenberg_register_block_bindings_pattern_overrides_source() { - // Override the "core/pattern-attributes" source from core. - if ( array_key_exists( 'core/pattern-attributes', get_all_registered_block_bindings_sources() ) ) { - unregister_block_bindings_source( 'core/pattern-attributes' ); - } - register_block_bindings_source( - 'core/pattern-attributes', - array( - 'label' => __( 'Pattern Attributes' ), - 'get_value_callback' => 'gutenberg_block_bindings_pattern_overrides_callback', - ) - ); +function gutenberg_register_block_bindings_pattern_overrides_source() { + // Override the "core/pattern-attributes" source from core. + if ( array_key_exists( 'core/pattern-attributes', get_all_registered_block_bindings_sources() ) ) { + unregister_block_bindings_source( 'core/pattern-attributes' ); } + register_block_bindings_source( + 'core/pattern-attributes', + array( + 'label' => __( 'Pattern Attributes' ), + 'get_value_callback' => 'gutenberg_block_bindings_pattern_overrides_callback', + ) + ); } add_action( 'init', 'gutenberg_register_block_bindings_pattern_overrides_source' ); 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 48e8598b208f6..4166ed3243ef0 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 @@ -4,36 +4,34 @@ * * @package gutenberg */ -if ( ! function_exists( 'gutenberg_register_block_bindings_post_meta_source' ) && ! function_exists( 'gutenberg_block_bindings_post_meta_callback' ) ) { - function gutenberg_block_bindings_post_meta_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']; - } else { - // I tried using $block_instance->context['postId'] but it wasn't available in the image block. - $post_id = get_the_ID(); - } +function gutenberg_block_bindings_post_meta_callback( $source_attrs ) { + if ( ! isset( $source_attrs['key'] ) ) { + return null; + } - return get_post_meta( $post_id, $source_attrs['key'], true ); + // 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. + $post_id = get_the_ID(); } - function gutenberg_register_block_bindings_post_meta_source() { - // Override the "core/post-meta" source from core. - if ( array_key_exists( 'core/post-meta', get_all_registered_block_bindings_sources() ) ) { - unregister_block_bindings_source( 'core/post-meta' ); - } - register_block_bindings_source( - 'core/post-meta', - array( - 'label' => __( 'Post Meta' ), - 'get_value_callback' => 'gutenberg_block_bindings_post_meta_callback', - ) - ); + return get_post_meta( $post_id, $source_attrs['key'], true ); +} + +function gutenberg_register_block_bindings_post_meta_source() { + // Override the "core/post-meta" source from core. + if ( array_key_exists( 'core/post-meta', get_all_registered_block_bindings_sources() ) ) { + unregister_block_bindings_source( 'core/post-meta' ); } + register_block_bindings_source( + 'core/post-meta', + array( + 'label' => __( 'Post Meta' ), + 'get_value_callback' => 'gutenberg_block_bindings_post_meta_callback', + ) + ); } add_action( 'init', 'gutenberg_register_block_bindings_post_meta_source' ); From 056f09b72b9406d9646d63021ecb55f4ad8e0a12 Mon Sep 17 00:00:00 2001 From: Mario Santos Date: Mon, 29 Jan 2024 15:43:59 +0100 Subject: [PATCH 10/11] Load class file only if it doesn't exist --- lib/load.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/load.php b/lib/load.php index d1fc2a3bb2663..3a1537f247524 100644 --- a/lib/load.php +++ b/lib/load.php @@ -112,10 +112,12 @@ function gutenberg_is_experiment_enabled( $name ) { require __DIR__ . '/compat/wordpress-6.5/interactivity-api/interactivity-api.php'; require __DIR__ . '/compat/wordpress-6.5/class-wp-script-modules.php'; require __DIR__ . '/compat/wordpress-6.5/scripts-modules.php'; -require __DIR__ . '/compat/wordpress-6.5/block-bindings/class-wp-block-bindings-registry.php'; require __DIR__ . '/compat/wordpress-6.5/block-bindings/block-bindings.php'; require __DIR__ . '/compat/wordpress-6.5/block-bindings/sources/post-meta.php'; require __DIR__ . '/compat/wordpress-6.5/block-bindings/sources/pattern.php'; +if ( ! class_exists( 'WP_Block_Bindings_Registry' ) ) { + require __DIR__ . '/compat/wordpress-6.5/block-bindings/class-wp-block-bindings-registry.php'; +} // Experimental features. From 26aebb5fc0c118ff3d81a55e793049bb676819d5 Mon Sep 17 00:00:00 2001 From: Mario Santos Date: Mon, 29 Jan 2024 16:06:27 +0100 Subject: [PATCH 11/11] Load class before it is used --- lib/load.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/load.php b/lib/load.php index 3a1537f247524..60a198cdf4d3c 100644 --- a/lib/load.php +++ b/lib/load.php @@ -112,12 +112,12 @@ function gutenberg_is_experiment_enabled( $name ) { require __DIR__ . '/compat/wordpress-6.5/interactivity-api/interactivity-api.php'; require __DIR__ . '/compat/wordpress-6.5/class-wp-script-modules.php'; require __DIR__ . '/compat/wordpress-6.5/scripts-modules.php'; -require __DIR__ . '/compat/wordpress-6.5/block-bindings/block-bindings.php'; -require __DIR__ . '/compat/wordpress-6.5/block-bindings/sources/post-meta.php'; -require __DIR__ . '/compat/wordpress-6.5/block-bindings/sources/pattern.php'; if ( ! class_exists( 'WP_Block_Bindings_Registry' ) ) { require __DIR__ . '/compat/wordpress-6.5/block-bindings/class-wp-block-bindings-registry.php'; } +require __DIR__ . '/compat/wordpress-6.5/block-bindings/block-bindings.php'; +require __DIR__ . '/compat/wordpress-6.5/block-bindings/sources/post-meta.php'; +require __DIR__ . '/compat/wordpress-6.5/block-bindings/sources/pattern.php'; // Experimental features.