diff --git a/src/wp-includes/block-template-utils.php b/src/wp-includes/block-template-utils.php index 460372b8cc7cf..987118581a4ca 100644 --- a/src/wp-includes/block-template-utils.php +++ b/src/wp-includes/block-template-utils.php @@ -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; } diff --git a/src/wp-includes/blocks.php b/src/wp-includes/blocks.php index d82ea1284487d..b739e1244d912 100644 --- a/src/wp-includes/blocks.php +++ b/src/wp-includes/blocks.php @@ -927,22 +927,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'] ) ) { @@ -961,16 +955,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; } diff --git a/tests/phpunit/tests/blocks/serialize.php b/tests/phpunit/tests/blocks/serialize.php index 3bfda3ed55a8f..533e8f5c63428 100644 --- a/tests/phpunit/tests/blocks/serialize.php +++ b/tests/phpunit/tests/blocks/serialize.php @@ -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() { @@ -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 = "Example.\n\nExample.\n\n"; $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( "Example.\n\nExample.\n\n", @@ -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 ) { + $blocks = parse_blocks( $original ); + + $actual = traverse_and_serialize_blocks( + $blocks, + function ( $block ) { + return $block; + } + ); + + $this->assertSame( $original, $actual ); + } }