-
Notifications
You must be signed in to change notification settings - Fork 4.2k
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 API: Add block bindings PHP registration mechanisms and "Post meta" source under the experimental flag #57249
Changes from all commits
2fd22b9
d98b4e6
4d085c5
a88e9dc
999784f
9105898
35bd58d
f57d531
766295e
34e2e40
bd099d2
a1099d6
0d686c9
b3a21d4
f0b1fda
3704637
e0604df
25fa668
7fb646c
34e3f29
8ff5352
6a7074d
7e8576c
62adeb8
5231b1c
5a212e6
b051f17
23629de
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
<?php | ||
/** | ||
* Define the mechanism to replace the HTML depending on the block attributes. | ||
* | ||
* @package gutenberg | ||
*/ | ||
|
||
if ( ! function_exists( 'block_bindings_replace_html' ) ) { | ||
/** | ||
* Depending on the block attributes, replace the proper HTML based on the value returned by the source. | ||
* | ||
* @param string $block_content Block Content. | ||
* @param string $block_name The name of the block to process. | ||
* @param string $block_attr The attribute of the block we want to process. | ||
* @param string $source_value The value used to replace the HTML. | ||
*/ | ||
function 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>" . esc_html( $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; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
<?php | ||
/** | ||
* Require the necessary files. | ||
* | ||
* @package gutenberg | ||
*/ | ||
|
||
require_once __DIR__ . '/sources/index.php'; | ||
require_once __DIR__ . '/html-processing.php'; | ||
|
||
// Register the sources. | ||
$gutenberg_experiments = get_option( 'gutenberg-experiments' ); | ||
if ( $gutenberg_experiments ) { | ||
if ( array_key_exists( 'gutenberg-pattern-partial-syncing', $gutenberg_experiments ) ) { | ||
require_once __DIR__ . '/sources/pattern.php'; | ||
} | ||
if ( array_key_exists( 'gutenberg-block-bindings', $gutenberg_experiments ) ) { | ||
require_once __DIR__ . '/sources/post-meta.php'; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
<?php | ||
/** | ||
* Define the mechanism to add new sources available in the block bindings API. | ||
* | ||
* @package gutenberg | ||
*/ | ||
|
||
global $block_bindings_sources; | ||
$block_bindings_sources = array(); | ||
Comment on lines
+8
to
+9
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A lot of the registries in WordPress use the approach of having a singleton class, so it could be something to replicate here. Some examples:
If a class is used, it's worth thinking about how it might be extended by the gutenberg codebase when shipped in core if changes need to be made to block bindings. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for sharing! @michalczaplinski @artemiomorales It'd be great if you can explore this possibility. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks! Agreed that we should begin improving the code as we look to bring this into Core. In order to keep things moving and start getting more feedback on the feature, and since this is still an experiment, I think we can get this PR merged first then immediately begin another PR to refactor and start moving the logic over to a Block Bindings class 👍 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Great feedback, @talldan. That’s the direction I would recommend, too. I agree with @artemiomorales as well that this refactoring might be easier to perform as a focused effort in a follow up PR. All functionality is behind an experiment. Well, another option is to open a new branch targeting this branch if you decide to spend more time polishing this PR. Some inspiration for how to architect the class and helper functions can be borrowed from the recent work on ES Modules API in WordPress/wordpress-develop#5818 where @felixarntz did an outstanding job explaining several good practices to take into account. That combined with prior work referenced by Dan should give you necessary insights to refactor the code. |
||
if ( ! function_exists( 'register_block_bindings_source' ) ) { | ||
/** | ||
* Function to register a new source. | ||
* | ||
* @param string $source_name The name of the source. | ||
* @param string $label The label of the source. | ||
* @param callable(object, object, string): string $apply - The callback executed when the source is processed during block rendering. | ||
* - 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. | ||
* | ||
* | ||
* @return void | ||
*/ | ||
function register_block_bindings_source( $source_name, $label, $apply ) { | ||
global $block_bindings_sources; | ||
$block_bindings_sources[ $source_name ] = array( | ||
'label' => $label, | ||
'apply' => $apply, | ||
); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
<?php | ||
/** | ||
* Add the metadata source to the block bindings API. | ||
* | ||
* @package gutenberg | ||
*/ | ||
|
||
if ( function_exists( 'register_block_bindings_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']; | ||
return _wp_array_get( $block_instance->context, array( 'pattern/overrides', $block_id, $attribute_name ), null ); | ||
}; | ||
register_block_bindings_source( | ||
'pattern_attributes', | ||
__( 'Pattern Attributes', 'gutenberg' ), | ||
$pattern_source_callback | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
<?php | ||
/** | ||
* Add the post_meta source to the block bindings API. | ||
* | ||
* @package gutenberg | ||
*/ | ||
|
||
if ( function_exists( 'register_block_bindings_source' ) ) { | ||
$post_meta_source_callback = function ( $source_attrs ) { | ||
// 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['value'], true ); | ||
}; | ||
register_block_bindings_source( | ||
'post_meta', | ||
__( 'Post Meta', 'gutenberg' ), | ||
$post_meta_source_callback | ||
); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we're removing block supports. we should probably remove this entire file, which exists inside theEDIT: Since we may be adding block supports in the future, perhaps removing this file isn't necessary.block-supports/
directory, and put its logic elsewhere.