Skip to content

Commit

Permalink
Blocks: Change traverse_and_serialize_block(s)'s callback signature.
Browse files Browse the repository at this point in the history
During work on #59399, it was discovered that ''sibling'' block insertion wasn't likely going to work the way it was planned, which required devising an alternative solution. This new solution requires some changes to `traverse_and_serialize_block(s)`:

- Change the signature of the existing callback such that:
  - the return value is a string that will be prepended to the result of the inner block traversal and serialization;
  - the function arguments are: a ''reference'' to the current block (so it can be modified inline, which is important e.g. for `theme` attribute insertion), the parent block, and the previous block (instead of the block index and chunk index).
- Add a second callback argument to `traverse_and_serialize_block(s)`, which is called ''after'' the block is traversed and serialized.
  - Its function arguments are a reference to the current block, the parent block, and the next block.

Props gziolo.
Fixes #59412. See #59313.

git-svn-id: https://develop.svn.wordpress.org/trunk@56644 602fd350-edb4-49c9-b593-d223f7449a82
  • Loading branch information
ockham committed Sep 21, 2023
1 parent 8fec1ec commit aa033cb
Show file tree
Hide file tree
Showing 4 changed files with 110 additions and 51 deletions.
5 changes: 2 additions & 3 deletions src/wp-includes/block-template-utils.php
Original file line number Diff line number Diff line change
Expand Up @@ -515,16 +515,15 @@ function _inject_theme_attribute_in_block_template_content( $template_content )
* @access private
*
* @param array $block a parsed block.
* @return array Updated block.
* @return void
*/
function _inject_theme_attribute_in_template_part_block( $block ) {
function _inject_theme_attribute_in_template_part_block( &$block ) {
if (
'core/template-part' === $block['blockName'] &&
! isset( $block['attrs']['theme'] )
) {
$block['attrs']['theme'] = get_stylesheet();
}
return $block;
}

/**
Expand Down
124 changes: 93 additions & 31 deletions src/wp-includes/blocks.php
Original file line number Diff line number Diff line change
Expand Up @@ -886,38 +886,68 @@ function serialize_blocks( $blocks ) {
}

/**
* Traverses the block applying transformations using the callback provided and returns the content of a block,
* including comment delimiters, serializing all attributes from the given parsed block.
* Traverses a parsed block tree and applies callbacks before and after serializing it.
*
* This should be used when there is a need to modify the saved block.
* Prefer `serialize_block` when preparing a block to be saved to post content.
* Recursively traverses the block and its inner blocks and applies the two callbacks provided as
* arguments, the first one before serializing the block, and the second one after serializing it.
* If either callback returns a string value, it will be prepended and appended to the serialized
* block markup, respectively.
*
* The callbacks will receive a reference to the current block as their first argument, so that they
* can also modify it, and the current block's parent block as second argument. Finally, the
* `$pre_callback` receives the previous block, whereas the `$post_callback` receives
* the next block as third argument.
*
* Serialized blocks are returned including comment delimiters, and with all attributes serialized.
*
* This function should be used when there is a need to modify the saved block, or to inject markup
* into the return value. Prefer `serialize_block` when preparing a block to be saved to post content.
*
* @since 6.4.0
*
* @see serialize_block()
*
* @param array $block A representative array of a single parsed block object. See WP_Block_Parser_Block.
* @param callable $callback Callback to run on each block in the tree before serialization.
* It is called with the following arguments: $block, $parent_block, $block_index, $chunk_index.
* @return string String of rendered HTML.
* @param array $block A representative array of a single parsed block object. See WP_Block_Parser_Block.
* @param callable $pre_callback Callback to run on each block in the tree before it is traversed and serialized.
* It is called with the following arguments: &$block, $parent_block, $previous_block.
* Its string return value will be prepended to the serialized block markup.
* @param callable $post_callback Callback to run on each block in the tree after it is traversed and serialized.
* It is called with the following arguments: &$block, $parent_block, $next_block.
* Its string return value will be appended to the serialized block markup.
* @return string Serialized block markup.
*/
function traverse_and_serialize_block( $block, $callback ) {
function traverse_and_serialize_block( $block, $pre_callback = null, $post_callback = null ) {
$block_content = '';
$block_index = 0;

foreach ( $block['innerContent'] as $chunk_index => $chunk ) {
foreach ( $block['innerContent'] as $chunk ) {
if ( is_string( $chunk ) ) {
$block_content .= $chunk;
} else {
$inner_block = call_user_func(
$callback,
$block['innerBlocks'][ $block_index ],
$block,
$block_index,
$chunk_index
);
$inner_block = $block['innerBlocks'][ $block_index ];

if ( is_callable( $pre_callback ) ) {
$prev = 0 === $block_index
? null
: $block['innerBlocks'][ $block_index - 1 ];
$block_content .= call_user_func_array(
$pre_callback,
array( &$inner_block, $block, $prev )
);
}

$block_content .= traverse_and_serialize_block( $inner_block, $pre_callback, $post_callback );

if ( is_callable( $post_callback ) ) {
$next = count( $block['innerBlocks'] ) - 1 === $block_index
? null
: $block['innerBlocks'][ $block_index + 1 ];
$block_content .= call_user_func_array(
$post_callback,
array( &$inner_block, $block, $next )
);
}
$block_index++;
$block_content .= traverse_and_serialize_block( $inner_block, $callback );
}
}

Expand All @@ -933,27 +963,59 @@ function traverse_and_serialize_block( $block, $callback ) {
}

/**
* Traverses the blocks applying transformations using the callback provided,
* and returns a joined string of the aggregate serialization of the given parsed blocks.
* Given an array of parsed block trees, applies callbacks before and after serializing them and
* returns their concatenated output.
*
* Recursively traverses the blocks and their inner blocks and applies the two callbacks provided as
* arguments, the first one before serializing a block, and the second one after serializing.
* If either callback returns a string value, it will be prepended and appended to the serialized
* block markup, respectively.
*
* The callbacks will receive a reference to the current block as their first argument, so that they
* can also modify it, and the current block's parent block as second argument. Finally, the
* `$pre_callback` receives the previous block, whereas the `$post_callback` receives
* the next block as third argument.
*
* This should be used when there is a need to modify the saved blocks.
* Prefer `serialize_blocks` when preparing blocks to be saved to post content.
* Serialized blocks are returned including comment delimiters, and with all attributes serialized.
*
* This function should be used when there is a need to modify the saved blocks, or to inject markup
* into the return value. Prefer `serialize_blocks` when preparing blocks to be saved to post content.
*
* @since 6.4.0
*
* @see serialize_blocks()
*
* @param array[] $blocks An array of representative arrays of parsed block objects. See serialize_block().
* @param callable $callback Callback to run on each block in the tree before serialization.
* It is called with the following arguments: $block, $parent_block, $block_index, $chunk_index.
* @return string String of rendered HTML.
* @param array[] $blocks An array of parsed blocks. See WP_Block_Parser_Block.
* @param callable $pre_callback Callback to run on each block in the tree before it is traversed and serialized.
* It is called with the following arguments: &$block, $parent_block, $previous_block.
* Its string return value will be prepended to the serialized block markup.
* @param callable $post_callback Callback to run on each block in the tree after it is traversed and serialized.
* It is called with the following arguments: &$block, $parent_block, $next_block.
* Its string return value will be appended to the serialized block markup.
* @return string Serialized block markup.
*/
function traverse_and_serialize_blocks( $blocks, $callback ) {
function traverse_and_serialize_blocks( $blocks, $pre_callback = null, $post_callback = null ) {
$result = '';
foreach ( $blocks as $block ) {
// At the top level, there is no parent block, block index, or chunk index to pass to the callback.
$block = call_user_func( $callback, $block );
$result .= traverse_and_serialize_block( $block, $callback );
foreach ( $blocks as $index => $block ) {
if ( is_callable( $pre_callback ) ) {
$prev = 0 === $index
? null
: $blocks[ $index - 1 ];
$result .= call_user_func_array(
$pre_callback,
array( &$block, null, $prev ) // At the top level, there is no parent block to pass to the callback.
);
}
$result .= traverse_and_serialize_block( $block, $pre_callback, $post_callback );
if ( is_callable( $post_callback ) ) {
$next = count( $blocks ) - 1 === $index
? null
: $blocks[ $index + 1 ];
$result .= call_user_func_array(
$post_callback,
array( &$block, null, $next ) // At the top level, there is no parent block to pass to the callback.
);
}
}
return $result;
}
Expand Down
20 changes: 11 additions & 9 deletions tests/phpunit/tests/block-template-utils.php
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ public function data_build_block_template_result_from_file_injects_theme_attribu
* @covers ::_inject_theme_attribute_in_template_part_block
*/
public function test_inject_theme_attribute_in_template_part_block() {
$template_part_block_without_theme_attribute = array(
$template_part_block = array(
'blockName' => 'core/template-part',
'attrs' => array(
'slug' => 'header',
Expand All @@ -238,7 +238,7 @@ public function test_inject_theme_attribute_in_template_part_block() {
'innerBlocks' => array(),
);

$actual = _inject_theme_attribute_in_template_part_block( $template_part_block_without_theme_attribute );
_inject_theme_attribute_in_template_part_block( $template_part_block );
$expected = array(
'blockName' => 'core/template-part',
'attrs' => array(
Expand All @@ -254,7 +254,7 @@ public function test_inject_theme_attribute_in_template_part_block() {
);
$this->assertSame(
$expected,
$actual,
$template_part_block,
'`theme` attribute was not correctly injected in template part block.'
);
}
Expand All @@ -265,7 +265,7 @@ public function test_inject_theme_attribute_in_template_part_block() {
* @covers ::_inject_theme_attribute_in_template_part_block
*/
public function test_not_inject_theme_attribute_in_template_part_block_theme_attribute_exists() {
$template_part_block_with_existing_theme_attribute = array(
$template_part_block = array(
'blockName' => 'core/template-part',
'attrs' => array(
'slug' => 'header',
Expand All @@ -279,10 +279,11 @@ public function test_not_inject_theme_attribute_in_template_part_block_theme_att
'innerBlocks' => array(),
);

$actual = _inject_theme_attribute_in_template_part_block( $template_part_block_with_existing_theme_attribute );
$expected = $template_part_block;
_inject_theme_attribute_in_template_part_block( $template_part_block );
$this->assertSame(
$template_part_block_with_existing_theme_attribute,
$actual,
$expected,
$template_part_block,
'Existing `theme` attribute in template part block was not respected by attribute injection.'
);
}
Expand All @@ -301,10 +302,11 @@ public function test_not_inject_theme_attribute_non_template_part_block() {
'innerBlocks' => array(),
);

$actual = _inject_theme_attribute_in_template_part_block( $non_template_part_block );
$expected = $non_template_part_block;
_inject_theme_attribute_in_template_part_block( $non_template_part_block );
$this->assertSame(
$expected,
$non_template_part_block,
$actual,
'`theme` attribute injection modified non-template-part block.'
);
}
Expand Down
12 changes: 4 additions & 8 deletions tests/phpunit/tests/blocks/serialize.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ public function test_serialized_block_name() {

/**
* @ticket 59327
* @ticket 59412
*
* @covers ::traverse_and_serialize_blocks
*/
Expand All @@ -73,15 +74,15 @@ public function test_traverse_and_serialize_blocks() {
);
}

public static function add_attribute_to_inner_block( $block ) {
public static function add_attribute_to_inner_block( &$block ) {
if ( 'core/inner' === $block['blockName'] ) {
$block['attrs']['myattr'] = 'myvalue';
}
return $block;
}

/**
* @ticket 59327
* @ticket 59412
*
* @covers ::traverse_and_serialize_blocks
*
Expand All @@ -92,12 +93,7 @@ public static function add_attribute_to_inner_block( $block ) {
public function test_traverse_and_serialize_identity_from_parsed( $original ) {
$blocks = parse_blocks( $original );

$actual = traverse_and_serialize_blocks(
$blocks,
function ( $block ) {
return $block;
}
);
$actual = traverse_and_serialize_blocks( $blocks );

$this->assertSame( $original, $actual );
}
Expand Down

0 comments on commit aa033cb

Please sign in to comment.