Skip to content

Commit

Permalink
Editor: Refactor block binding processing and attribute computation
Browse files Browse the repository at this point in the history
Refactors the processing of block bindings into steps:
- Gets the value for each "bound" attribute from the respective source.
- Returns the computer attributes with values from the sources.
- The computed attributes get injected into block's content.
- The `render_callback` gets the updated list of attributes and processeded block content.

Fixes #60282.
Props czapla, gziolo, andraganescu, santosguillamot.



git-svn-id: https://develop.svn.wordpress.org/trunk@57574 602fd350-edb4-49c9-b593-d223f7449a82
  • Loading branch information
gziolo committed Feb 9, 2024
1 parent 5c596f3 commit ac11d1b
Show file tree
Hide file tree
Showing 2 changed files with 93 additions and 27 deletions.
39 changes: 24 additions & 15 deletions src/wp-includes/class-wp-block.php
Original file line number Diff line number Diff line change
Expand Up @@ -192,15 +192,15 @@ public function __get( $name ) {
}

/**
* Processes the block bindings in block's attributes.
* Processes the block bindings and updates the block attributes with the values from the sources.
*
* A block might contain bindings in its attributes. Bindings are mappings
* between an attribute of the block and a source. A "source" is a function
* registered with `register_block_bindings_source()` that defines how to
* retrieve a value from outside the block, e.g. from post meta.
*
* This function will process those bindings and replace the HTML with the value of the binding.
* The value is retrieved from the source of the binding.
* This function will process those bindings and update the block's attributes
* with the values coming from the bindings.
*
* ### Example
*
Expand Down Expand Up @@ -228,13 +228,13 @@ public function __get( $name ) {
*
* @since 6.5.0
*
* @param string $block_content Block content.
* @param array $block The full block, including name and attributes.
* @return string The modified block content.
* @return array The computed block attributes for the provided block bindings.
*/
private function process_block_bindings( $block_content ) {
private function process_block_bindings() {
$parsed_block = $this->parsed_block;

$computed_attributes = array();

// 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(
Expand All @@ -251,10 +251,9 @@ private function process_block_bindings( $block_content ) {
empty( $parsed_block['attrs']['metadata']['bindings'] ) ||
! is_array( $parsed_block['attrs']['metadata']['bindings'] )
) {
return $block_content;
return $computed_attributes;
}

$modified_block_content = $block_content;
foreach ( $parsed_block['attrs']['metadata']['bindings'] as $attribute_name => $block_binding ) {
// If the attribute is not in the allowed list, process next attribute.
if ( ! in_array( $attribute_name, $allowed_blocks[ $this->name ], true ) ) {
Expand All @@ -275,11 +274,11 @@ private function process_block_bindings( $block_content ) {

// If the value is not null, process the HTML based on the block and the attribute.
if ( ! is_null( $source_value ) ) {
$modified_block_content = $this->replace_html( $modified_block_content, $attribute_name, $source_value );
$computed_attributes[ $attribute_name ] = $source_value;
}
}

return $modified_block_content;
return $computed_attributes;
}

/**
Expand Down Expand Up @@ -391,6 +390,7 @@ private function replace_html( string $block_content, string $attribute_name, $s
* Generates the render output for the block.
*
* @since 5.5.0
* @since 6.5.0 Added block bindings processing.
*
* @global WP_Post $post Global post object.
*
Expand All @@ -410,6 +410,13 @@ public function render( $options = array() ) {
)
);

// Process the block bindings and get attributes updated with the values from the sources.
$computed_attributes = $this->process_block_bindings();
if ( ! empty( $computed_attributes ) ) {
// Merge the computed attributes with the original attributes
$this->attributes = array_merge( $this->attributes, $computed_attributes );
}

$is_dynamic = $options['dynamic'] && $this->name && null !== $this->block_type && $this->block_type->is_dynamic();
$block_content = '';

Expand Down Expand Up @@ -445,6 +452,12 @@ public function render( $options = array() ) {
}
}

if ( ! empty( $computed_attributes ) && ! empty( $block_content ) ) {
foreach ( $computed_attributes as $attribute_name => $source_value ) {
$block_content = $this->replace_html( $block_content, $attribute_name, $source_value );
}
}

if ( $is_dynamic ) {
$global_post = $post;
$parent = WP_Block_Supports::$block_to_render;
Expand Down Expand Up @@ -488,10 +501,6 @@ public function render( $options = array() ) {
}
}

// Process the block bindings for this block, if any are registered. This
// will replace the block content with the value from a registered binding source.
$block_content = $this->process_block_bindings( $block_content );

/**
* Filters the content of a single block.
*
Expand Down
81 changes: 69 additions & 12 deletions tests/phpunit/tests/block-bindings/render.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,16 +52,24 @@ public function test_update_block_with_value_from_source() {
);

$block_content = <<<HTML
<!-- wp:paragraph {"metadata":{"bindings":{"content":{"source":"test/source"}}}} --><p>This should not appear</p><!-- /wp:paragraph -->
<!-- wp:paragraph {"metadata":{"bindings":{"content":{"source":"test/source"}}}} -->
<p>This should not appear</p>
<!-- /wp:paragraph -->
HTML;

$parsed_blocks = parse_blocks( $block_content );
$block = new WP_Block( $parsed_blocks[0] );
$result = $block->render();

$expected = '<p>test source value</p>';
$result = $block->render();

$this->assertEquals( $expected, $result, 'The block content should be updated with the value returned by the source.' );
$this->assertSame(
'test source value',
$block->attributes['content'],
"The 'content' attribute should be updated with the value returned by the source."
);
$this->assertSame(
'<p>test source value</p>',
trim( $result ),
'The block content should be updated with the value returned by the source.'
);
}

/**
Expand All @@ -85,17 +93,66 @@ public function test_passing_arguments_to_source() {
)
);

$value = 'test';
$block_content = <<<HTML
<!-- wp:paragraph {"metadata":{"bindings":{"content":{"source":"test/source", "args": {"key": "$value"}}}}} --><p>This should not appear</p><!-- /wp:paragraph -->
<!-- wp:paragraph {"metadata":{"bindings":{"content":{"source":"test/source", "args": {"key": "test"}}}}} -->
<p>This should not appear</p>
<!-- /wp:paragraph -->
HTML;

$parsed_blocks = parse_blocks( $block_content );
$block = new WP_Block( $parsed_blocks[0] );
$result = $block->render();

$this->assertSame(
"The attribute name is 'content' and its binding has argument 'key' with value 'test'.",
$block->attributes['content'],
"The 'content' attribute should be updated with the value returned by the source."
);
$this->assertSame(
"<p>The attribute name is 'content' and its binding has argument 'key' with value 'test'.</p>",
trim( $result ),
'The block content should be updated with the value returned by the source.'
);
}

/**
* Tests if the block content is updated with the value returned by the source
* for the Image block in the placeholder state.
*
* @ticket 60282
*
* @covers ::register_block_bindings_source
*/
public function test_update_block_with_value_from_source_image_placeholder() {
$get_value_callback = function () {
return 'https://example.com/image.jpg';
};

register_block_bindings_source(
self::SOURCE_NAME,
array(
'label' => self::SOURCE_LABEL,
'get_value_callback' => $get_value_callback,
)
);

$expected = "<p>The attribute name is 'content' and its binding has argument 'key' with value '$value'.</p>";
$result = $block->render();
$block_content = <<<HTML
<!-- wp:image {"metadata":{"bindings":{"url":{"source":"test/source"}}}} -->
<figure class="wp-block-image"><img alt=""/></figure>
<!-- /wp:image -->
HTML;
$parsed_blocks = parse_blocks( $block_content );
$block = new WP_Block( $parsed_blocks[0] );
$result = $block->render();

$this->assertEquals( $expected, $result, 'The block content should be updated with the value returned by the source.' );
$this->assertSame(
'https://example.com/image.jpg',
$block->attributes['url'],
"The 'url' attribute should be updated with the value returned by the source."
);
$this->assertSame(
'<figure class="wp-block-image"><img src="https://example.com/image.jpg" alt=""/></figure>',
trim( $result ),
'The block content should be updated with the value returned by the source.'
);
}
}

0 comments on commit ac11d1b

Please sign in to comment.