Skip to content

Commit

Permalink
Block Bindings: Update source registration syntax and remove APIs tha…
Browse files Browse the repository at this point in the history
…t 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
  • Loading branch information
michalczaplinski committed Jan 24, 2024
1 parent ca5f16a commit ea32957
Show file tree
Hide file tree
Showing 5 changed files with 136 additions and 133 deletions.
118 changes: 106 additions & 12 deletions lib/compat/wordpress-6.5/block-bindings/block-bindings.php
Original file line number Diff line number Diff line change
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.
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)
);
}
6 changes: 4 additions & 2 deletions lib/compat/wordpress-6.5/block-bindings/sources/post-meta.php
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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

0 comments on commit ea32957

Please sign in to comment.