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

Blocks: Introduce a variation of serialize blocks helper with traversing #5246

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/wp-includes/block-template-utils.php
Original file line number Diff line number Diff line change
Expand Up @@ -609,7 +609,7 @@ function _build_block_template_result_from_file( $template_file, $template_type
}

$blocks = parse_blocks( $template_content );
$template->content = serialize_blocks( $blocks, '_inject_theme_attribute_in_template_part_block' );
$template->content = traverse_and_serialize_blocks( $blocks, '_inject_theme_attribute_in_template_part_block' );

return $template;
}
Expand Down
89 changes: 75 additions & 14 deletions src/wp-includes/blocks.php
Original file line number Diff line number Diff line change
Expand Up @@ -849,22 +849,16 @@ function get_comment_delimited_block_content( $block_name, $block_attributes, $b
* instead preserve the markup as parsed.
*
* @since 5.3.1
* @since 6.4.0 The `$callback` parameter was added.
*
* @param array $block A representative array of a single parsed block object. See WP_Block_Parser_Block.
* @param callable|null $callback Optional. Callback to run on each block in the tree before serialization. Default null.
* @param array $block A representative array of a single parsed block object. See WP_Block_Parser_Block.
* @return string String of rendered HTML.
*/
function serialize_block( $block, $callback = null ) {
if ( is_callable( $callback ) ) {
$block = call_user_func( $callback, $block );
}

function serialize_block( $block ) {
$block_content = '';

$index = 0;
foreach ( $block['innerContent'] as $chunk ) {
$block_content .= is_string( $chunk ) ? $chunk : serialize_block( $block['innerBlocks'][ $index++ ], $callback );
$block_content .= is_string( $chunk ) ? $chunk : serialize_block( $block['innerBlocks'][ $index++ ] );
}

if ( ! is_array( $block['attrs'] ) ) {
Expand All @@ -883,16 +877,83 @@ function serialize_block( $block, $callback = null ) {
* parsed blocks.
*
* @since 5.3.1
* @since 6.4.0 The `$callback` parameter was added.
*
* @param array[] $blocks An array of representative arrays of parsed block objects. See serialize_block().
* @param callable|null $callback Optional. Callback to run on each block in the tree before serialization. Default null.
* @param array[] $blocks An array of representative arrays of parsed block objects. See serialize_block().
* @return string String of rendered HTML.
*/
function serialize_blocks( $blocks ) {
return implode( '', array_map( 'serialize_block', $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.
*
* 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.
*
* @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.
*/
function traverse_and_serialize_block( $block, $callback ) {
$block_content = '';
$block_index = 0;

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

if ( ! is_array( $block['attrs'] ) ) {
$block['attrs'] = array();
}

return get_comment_delimited_block_content(
$block['blockName'],
$block['attrs'],
$block_content
);
}

/**
* Traverses the blocks applying transformations using the callback provided,
* and returns a joined string of the aggregate serialization of the given parsed blocks.
*
* 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.
*
* @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.
*/
function serialize_blocks( $blocks, $callback = null ) {
function traverse_and_serialize_blocks( $blocks, $callback ) {
$result = '';
foreach ( $blocks as $block ) {
$result .= serialize_block( $block, $callback );
// 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 );
}
return $result;
}
Expand Down
35 changes: 29 additions & 6 deletions tests/phpunit/tests/blocks/serialize.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,15 @@ class Tests_Blocks_Serialize extends WP_UnitTestCase {

/**
* @dataProvider data_serialize_identity_from_parsed
*
* @param string $original Original block markup.
*/
public function test_serialize_identity_from_parsed( $original ) {
$blocks = parse_blocks( $original );

$actual = serialize_blocks( $blocks );
$expected = $original;
$actual = serialize_blocks( $blocks );

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

public function data_serialize_identity_from_parsed() {
Expand Down Expand Up @@ -58,13 +59,13 @@ public function test_serialized_block_name() {
/**
* @ticket 59327
*
* @covers ::serialize_blocks
* @covers ::traverse_and_serialize_blocks
*/
public function test_callback_argument() {
public function test_traverse_and_serialize_blocks() {
$markup = "<!-- wp:outer --><!-- wp:inner {\"key\":\"value\"} -->Example.<!-- /wp:inner -->\n\nExample.\n\n<!-- wp:void /--><!-- /wp:outer -->";
$blocks = parse_blocks( $markup );

$actual = serialize_blocks( $blocks, array( __CLASS__, 'add_attribute_to_inner_block' ) );
$actual = traverse_and_serialize_blocks( $blocks, array( __CLASS__, 'add_attribute_to_inner_block' ) );

$this->assertSame(
"<!-- wp:outer --><!-- wp:inner {\"key\":\"value\",\"myattr\":\"myvalue\"} -->Example.<!-- /wp:inner -->\n\nExample.\n\n<!-- wp:void /--><!-- /wp:outer -->",
Expand All @@ -78,4 +79,26 @@ public static function add_attribute_to_inner_block( $block ) {
}
return $block;
}

/**
* @ticket 59327
*
* @covers ::traverse_and_serialize_blocks
*
* @dataProvider data_serialize_identity_from_parsed
*
* @param string $original Original block markup.
*/
public function test_traverse_and_serialize_identity_from_parsed( $original ) {
Copy link
Member

Choose a reason for hiding this comment

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

For better placement could you add this test after test_serialize_identity_from_parsed?

Copy link
Member Author

Choose a reason for hiding this comment

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

What's the rationale behind it? Are there any recommendations regarding the order of functions in test files? It's now located next to other tests covering traverse_and_serialize_blocks.

Copy link
Member Author

Choose a reason for hiding this comment

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

@mukeshpanchal27, I landed these changes, but I'm happy to work on a follow-up once you respond to the comment. There are two follow-up commits planned to finalize backporting Block Hooks feature from Gutenberg by @ockham that depend on this work.

$blocks = parse_blocks( $original );

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

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