Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Block Bindings: Update source registration syntax and remove APIs that should be private #58205

Merged
118 changes: 106 additions & 12 deletions lib/compat/wordpress-6.5/block-bindings/block-bindings.php
Expand Up @@ -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 );
}
}

Expand All @@ -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.
Comment on lines +90 to +92
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you are backporting this file, make sure that multilines comments are with /* */

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks Carlos! The backport is here btw WordPress/wordpress-develop#5888 if you ever want to have a look.

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 ) . "</$selector>";
$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()}</$button_wrapper>";
$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;
}
137 changes: 21 additions & 116 deletions lib/compat/wordpress-6.5/block-bindings/class-wp-block-bindings.php
Expand Up @@ -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 ) . "</$selector>";
$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()}</$button_wrapper>";
$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;
}

/**
Expand Down
6 changes: 4 additions & 2 deletions lib/compat/wordpress-6.5/block-bindings/sources/pattern.php
Expand Up @@ -29,7 +29,9 @@
};
wp_block_bindings_register_source(
'pattern_attributes',
__( 'Pattern Attributes', 'gutenberg' ),
$pattern_source_callback
array(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There should be no textdomain used for translations expected to land in core.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! I removed it for both the pattern and post-meta sources in c9a245e

'label' => __( 'Pattern Attributes' ),
'apply' => $pattern_source_callback,
)
);
}
Expand Up @@ -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,
)
);
}
2 changes: 1 addition & 1 deletion lib/compat/wordpress-6.5/blocks.php
Expand Up @@ -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;
}
Expand Down