From def1b1fda4c7f093da7b65e7b43ce2090f285e2b Mon Sep 17 00:00:00 2001 From: Ella Date: Thu, 16 May 2024 11:26:53 +0200 Subject: [PATCH 01/11] Backport: Templates perf: resolve patterns server side --- src/wp-includes/blocks.php | 56 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/src/wp-includes/blocks.php b/src/wp-includes/blocks.php index 986af9a865b7c..8bf77df70483f 100644 --- a/src/wp-includes/blocks.php +++ b/src/wp-includes/blocks.php @@ -1344,6 +1344,60 @@ function traverse_and_serialize_block( $block, $pre_callback = null, $post_callb ); } +function replace_pattern_blocks( $blocks, &$inner_content = null ) { + // Keep track of seen references to avoid infinite loops. + static $seen_refs = array(); + $i = 0; + while ( $i < count( $blocks ) ) { + if ( 'core/pattern' === $blocks[ $i ]['blockName'] ) { + $slug = $blocks[ $i ]['attrs']['slug']; + + if ( isset( $seen_refs[ $slug ] ) ) { + // Skip recursive patterns. + array_splice( $blocks, $i, 1 ); + continue; + } + + $registry = WP_Block_Patterns_Registry::get_instance(); + $pattern = $registry->get_registered( $slug ); + + // Skip unknown patterns. + if ( ! $pattern ) { + ++$i; + continue; + } + + $blocks_to_insert = parse_blocks( $pattern['content'] ); + $seen_refs[ $slug ] = true; + $blocks_to_insert = replace_pattern_blocks( $blocks_to_insert ); + unset( $seen_refs[ $slug ] ); + array_splice( $blocks, $i, 1, $blocks_to_insert ); + + // If we have inner content, we need to insert nulls in the + // inner content array, otherwise serialize_blocks will skip + // blocks. + if ( $inner_content ) { + $null_indices = array_keys( $inner_content, null, true ); + $content_index = $null_indices[ $i ]; + $nulls = array_fill( 0, count( $blocks_to_insert ), null ); + array_splice( $inner_content, $content_index, 1, $nulls ); + } + + // Skip inserted blocks. + $i += count( $blocks_to_insert ); + } else { + if ( ! empty( $blocks[ $i ]['innerBlocks'] ) ) { + $blocks[ $i ]['innerBlocks'] = replace_pattern_blocks( + $blocks[ $i ]['innerBlocks'], + $blocks[ $i ]['innerContent'] + ); + } + ++$i; + } + } + return $blocks; +} + /** * Given an array of parsed block trees, applies callbacks before and after serializing them and * returns their concatenated output. @@ -1383,6 +1437,8 @@ function traverse_and_serialize_blocks( $blocks, $pre_callback = null, $post_cal $result = ''; $parent_block = null; // At the top level, there is no parent block to pass to the callbacks; yet the callbacks expect a reference. + $blocks = replace_pattern_blocks( $blocks ); + foreach ( $blocks as $index => $block ) { if ( is_callable( $pre_callback ) ) { $prev = 0 === $index From 24abdb6f8f3dc0b921b97c664977266d0dcae24d Mon Sep 17 00:00:00 2001 From: Ella Date: Wed, 29 May 2024 16:23:20 +0300 Subject: [PATCH 02/11] Sync latest changes --- src/wp-includes/blocks.php | 17 ++++++++++++----- .../class-wp-rest-block-patterns-controller.php | 6 ++++++ .../class-wp-rest-templates-controller.php | 6 ++++++ 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/src/wp-includes/blocks.php b/src/wp-includes/blocks.php index 8bf77df70483f..d589ced870c6f 100644 --- a/src/wp-includes/blocks.php +++ b/src/wp-includes/blocks.php @@ -1344,7 +1344,16 @@ function traverse_and_serialize_block( $block, $pre_callback = null, $post_callb ); } -function replace_pattern_blocks( $blocks, &$inner_content = null ) { +/** + * Replaces patterns in a block tree with their content. + * + * @since 6.6.0 + * + * @param array $blocks An array blocks. + * + * @return array An array of blocks with patterns replaced by their content. + */ +function resolve_pattern_blocks( $blocks, &$inner_content = null ) { // Keep track of seen references to avoid infinite loops. static $seen_refs = array(); $i = 0; @@ -1369,7 +1378,7 @@ function replace_pattern_blocks( $blocks, &$inner_content = null ) { $blocks_to_insert = parse_blocks( $pattern['content'] ); $seen_refs[ $slug ] = true; - $blocks_to_insert = replace_pattern_blocks( $blocks_to_insert ); + $blocks_to_insert = resolve_pattern_blocks( $blocks_to_insert ); unset( $seen_refs[ $slug ] ); array_splice( $blocks, $i, 1, $blocks_to_insert ); @@ -1387,7 +1396,7 @@ function replace_pattern_blocks( $blocks, &$inner_content = null ) { $i += count( $blocks_to_insert ); } else { if ( ! empty( $blocks[ $i ]['innerBlocks'] ) ) { - $blocks[ $i ]['innerBlocks'] = replace_pattern_blocks( + $blocks[ $i ]['innerBlocks'] = resolve_pattern_blocks( $blocks[ $i ]['innerBlocks'], $blocks[ $i ]['innerContent'] ); @@ -1437,8 +1446,6 @@ function traverse_and_serialize_blocks( $blocks, $pre_callback = null, $post_cal $result = ''; $parent_block = null; // At the top level, there is no parent block to pass to the callbacks; yet the callbacks expect a reference. - $blocks = replace_pattern_blocks( $blocks ); - foreach ( $blocks as $index => $block ) { if ( is_callable( $pre_callback ) ) { $prev = 0 === $index diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-block-patterns-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-block-patterns-controller.php index d8f083924e030..c98b2a7c57c5e 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-block-patterns-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-block-patterns-controller.php @@ -162,6 +162,12 @@ protected function migrate_pattern_categories( $pattern ) { * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function prepare_item_for_response( $item, $request ) { + // Resolve pattern blocks so they don't need to be resolved client-side + // in the editor, improving performance. + $blocks = parse_blocks( $item['content'] ); + $blocks = resolve_pattern_blocks( $blocks ); + $item['content'] = serialize_blocks( $blocks ); + $fields = $this->get_fields_for_response( $request ); $keys = array( 'name' => 'name', diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-templates-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-templates-controller.php index 1c2a7697c4ec0..0239748cc66a6 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-templates-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-templates-controller.php @@ -636,6 +636,12 @@ protected function prepare_item_for_database( $request ) { * @return WP_REST_Response Response object. */ public function prepare_item_for_response( $item, $request ) { + // Resolve pattern blocks so they don't need to be resolved client-side + // in the editor, improving performance. + $blocks = parse_blocks( $item->content ); + $blocks = resolve_pattern_blocks( $blocks ); + $item->content = serialize_blocks( $blocks ); + // Restores the more descriptive, specific name for use within this method. $template = $item; From 259aa27dfb29ca23887999a899b5370451035722 Mon Sep 17 00:00:00 2001 From: Ella Date: Mon, 3 Jun 2024 09:27:17 +0300 Subject: [PATCH 03/11] Start adding unit tests --- src/wp-includes/blocks.php | 9 +++++- .../tests/blocks/resolve-pattern-blocks.php | 31 +++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 tests/phpunit/tests/blocks/resolve-pattern-blocks.php diff --git a/src/wp-includes/blocks.php b/src/wp-includes/blocks.php index d589ced870c6f..1ee3445dc23ff 100644 --- a/src/wp-includes/blocks.php +++ b/src/wp-includes/blocks.php @@ -1359,7 +1359,14 @@ function resolve_pattern_blocks( $blocks, &$inner_content = null ) { $i = 0; while ( $i < count( $blocks ) ) { if ( 'core/pattern' === $blocks[ $i ]['blockName'] ) { - $slug = $blocks[ $i ]['attrs']['slug']; + $attrs = $blocks[ $i ]['attrs']; + + if ( empty( $attrs['slug'] ) ) { + ++$i; + continue; + } + + $slug = $attrs['slug']; if ( isset( $seen_refs[ $slug ] ) ) { // Skip recursive patterns. diff --git a/tests/phpunit/tests/blocks/resolve-pattern-blocks.php b/tests/phpunit/tests/blocks/resolve-pattern-blocks.php new file mode 100644 index 0000000000000..f0ed57d40117b --- /dev/null +++ b/tests/phpunit/tests/blocks/resolve-pattern-blocks.php @@ -0,0 +1,31 @@ +assertSame( $expected, serialize_blocks( $actual ) ); + } + + public function data_all() { + return array( + // Works without attributes, leaves the block as is. + array( '', '' ), + ); + } +} From 9a064d9119e02b71fb4feac236926a5eb34b2479 Mon Sep 17 00:00:00 2001 From: Ella Date: Mon, 3 Jun 2024 09:39:55 +0300 Subject: [PATCH 04/11] Add more tests --- .../tests/blocks/resolve-pattern-blocks.php | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/tests/phpunit/tests/blocks/resolve-pattern-blocks.php b/tests/phpunit/tests/blocks/resolve-pattern-blocks.php index f0ed57d40117b..4bcbf78589692 100644 --- a/tests/phpunit/tests/blocks/resolve-pattern-blocks.php +++ b/tests/phpunit/tests/blocks/resolve-pattern-blocks.php @@ -10,6 +10,27 @@ * @group blocks */ class Tests_Blocks_Resolve_Pattern_Blocks extends WP_UnitTestCase { + public function set_up() { + parent::set_up(); + + register_block_pattern( 'core/test', array( + 'title' => 'Test', + 'content' => 'HelloWorld', + 'description' => 'Test pattern.', + ) ); + register_block_pattern( 'core/recursive', array( + 'title' => 'Recursive', + 'content' => 'Recursive', + 'description' => 'Recursive pattern.', + ) ); + } + + public function tear_down() { + parent::tear_down(); + + unregister_block_pattern( 'core/test' ); + unregister_block_pattern( 'core/recursive' ); + } /** * @dataProvider data_all @@ -26,6 +47,12 @@ public function data_all() { return array( // Works without attributes, leaves the block as is. array( '', '' ), + // Resolves the pattern. + array( '', 'HelloWorld' ), + // Skip recursive patterns. + array( '', 'Recursive' ), + // Resolves the pattern within a block. + array( 'BeforeAfter', 'BeforeHelloWorldAfter' ), ); } } From 11afd375bd9ec93938497529100a34d996e993b3 Mon Sep 17 00:00:00 2001 From: Ella Date: Mon, 3 Jun 2024 09:44:12 +0300 Subject: [PATCH 05/11] php lint --- .../tests/blocks/resolve-pattern-blocks.php | 52 +++++++++++-------- 1 file changed, 29 insertions(+), 23 deletions(-) diff --git a/tests/phpunit/tests/blocks/resolve-pattern-blocks.php b/tests/phpunit/tests/blocks/resolve-pattern-blocks.php index 4bcbf78589692..f6174ab1caa39 100644 --- a/tests/phpunit/tests/blocks/resolve-pattern-blocks.php +++ b/tests/phpunit/tests/blocks/resolve-pattern-blocks.php @@ -10,33 +10,39 @@ * @group blocks */ class Tests_Blocks_Resolve_Pattern_Blocks extends WP_UnitTestCase { - public function set_up() { + public function set_up() { parent::set_up(); - register_block_pattern( 'core/test', array( - 'title' => 'Test', - 'content' => 'HelloWorld', - 'description' => 'Test pattern.', - ) ); - register_block_pattern( 'core/recursive', array( - 'title' => 'Recursive', - 'content' => 'Recursive', - 'description' => 'Recursive pattern.', - ) ); + register_block_pattern( + 'core/test', + array( + 'title' => 'Test', + 'content' => 'HelloWorld', + 'description' => 'Test pattern.', + ) + ); + register_block_pattern( + 'core/recursive', + array( + 'title' => 'Recursive', + 'content' => 'Recursive', + 'description' => 'Recursive pattern.', + ) + ); } - public function tear_down() { - parent::tear_down(); + public function tear_down() { + parent::tear_down(); - unregister_block_pattern( 'core/test' ); - unregister_block_pattern( 'core/recursive' ); - } + unregister_block_pattern( 'core/test' ); + unregister_block_pattern( 'core/recursive' ); + } /** * @dataProvider data_all * * @param string $input - * @param string $expected + * @param string $expected */ public function test_all( $input, $expected ) { $actual = resolve_pattern_blocks( parse_blocks( $input ) ); @@ -47,12 +53,12 @@ public function data_all() { return array( // Works without attributes, leaves the block as is. array( '', '' ), - // Resolves the pattern. - array( '', 'HelloWorld' ), - // Skip recursive patterns. - array( '', 'Recursive' ), - // Resolves the pattern within a block. - array( 'BeforeAfter', 'BeforeHelloWorldAfter' ), + // Resolves the pattern. + array( '', 'HelloWorld' ), + // Skip recursive patterns. + array( '', 'Recursive' ), + // Resolves the pattern within a block. + array( 'BeforeAfter', 'BeforeHelloWorldAfter' ), ); } } From a884258ef9aaab4406b76bab5deb9199fd8f46b2 Mon Sep 17 00:00:00 2001 From: Ella Date: Mon, 3 Jun 2024 10:17:48 +0300 Subject: [PATCH 06/11] php lint --- tests/phpunit/tests/blocks/resolve-pattern-blocks.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/phpunit/tests/blocks/resolve-pattern-blocks.php b/tests/phpunit/tests/blocks/resolve-pattern-blocks.php index f6174ab1caa39..b99aaf432f3ae 100644 --- a/tests/phpunit/tests/blocks/resolve-pattern-blocks.php +++ b/tests/phpunit/tests/blocks/resolve-pattern-blocks.php @@ -19,7 +19,7 @@ public function set_up() { 'title' => 'Test', 'content' => 'HelloWorld', 'description' => 'Test pattern.', - ) + ) ); register_block_pattern( 'core/recursive', From 8a8c841383ef7dd1c6e3bdf0bef5bca9dd4485ed Mon Sep 17 00:00:00 2001 From: Anton Vlasenko Date: Mon, 3 Jun 2024 10:06:49 +0200 Subject: [PATCH 07/11] Add the @covers tag. --- tests/phpunit/tests/blocks/resolve-pattern-blocks.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/phpunit/tests/blocks/resolve-pattern-blocks.php b/tests/phpunit/tests/blocks/resolve-pattern-blocks.php index b99aaf432f3ae..91f52b47cd5f5 100644 --- a/tests/phpunit/tests/blocks/resolve-pattern-blocks.php +++ b/tests/phpunit/tests/blocks/resolve-pattern-blocks.php @@ -8,6 +8,7 @@ * @since 6.6.0 * * @group blocks + * @covers resolve_pattern_blocks */ class Tests_Blocks_Resolve_Pattern_Blocks extends WP_UnitTestCase { public function set_up() { From c8231848fce0e4b89dbed77657c2baeb71510657 Mon Sep 17 00:00:00 2001 From: Anton Vlasenko Date: Mon, 3 Jun 2024 10:35:46 +0200 Subject: [PATCH 08/11] Rename the test class according to the coding standards. --- .../{resolve-pattern-blocks.php => resolvePatternBlocks.php} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename tests/phpunit/tests/blocks/{resolve-pattern-blocks.php => resolvePatternBlocks.php} (96%) diff --git a/tests/phpunit/tests/blocks/resolve-pattern-blocks.php b/tests/phpunit/tests/blocks/resolvePatternBlocks.php similarity index 96% rename from tests/phpunit/tests/blocks/resolve-pattern-blocks.php rename to tests/phpunit/tests/blocks/resolvePatternBlocks.php index 91f52b47cd5f5..f6a7d9ca0ce39 100644 --- a/tests/phpunit/tests/blocks/resolve-pattern-blocks.php +++ b/tests/phpunit/tests/blocks/resolvePatternBlocks.php @@ -10,7 +10,7 @@ * @group blocks * @covers resolve_pattern_blocks */ -class Tests_Blocks_Resolve_Pattern_Blocks extends WP_UnitTestCase { +class Tests_Blocks_ResolvePatternBlocks extends WP_UnitTestCase { public function set_up() { parent::set_up(); From 3938a2fb455937c3ec2b7c73d6c975d6d497bd8b Mon Sep 17 00:00:00 2001 From: Anton Vlasenko Date: Mon, 3 Jun 2024 10:51:56 +0200 Subject: [PATCH 09/11] Use named data providers. --- .../tests/blocks/resolvePatternBlocks.php | 29 ++++++++++++------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/tests/phpunit/tests/blocks/resolvePatternBlocks.php b/tests/phpunit/tests/blocks/resolvePatternBlocks.php index f6a7d9ca0ce39..493d9aa3c9baf 100644 --- a/tests/phpunit/tests/blocks/resolvePatternBlocks.php +++ b/tests/phpunit/tests/blocks/resolvePatternBlocks.php @@ -40,26 +40,33 @@ public function tear_down() { } /** - * @dataProvider data_all + * @dataProvider data_should_resolve_pattern_blocks_as_expected * - * @param string $input - * @param string $expected + * @ticket 61228 + * + * @param string $blocks A string representing blocks that need resolving. + * @param string $expected Expected result. */ - public function test_all( $input, $expected ) { - $actual = resolve_pattern_blocks( parse_blocks( $input ) ); + public function test_should_resolve_pattern_blocks_as_expected( $blocks, $expected ) { + $actual = resolve_pattern_blocks( parse_blocks( $blocks ) ); $this->assertSame( $expected, serialize_blocks( $actual ) ); } - public function data_all() { + /** + * Data provider. + * + * @return array + */ + public function data_should_resolve_pattern_blocks_as_expected() { return array( // Works without attributes, leaves the block as is. - array( '', '' ), + 'pattern with no slug attribute' => array( '', '' ), // Resolves the pattern. - array( '', 'HelloWorld' ), - // Skip recursive patterns. - array( '', 'Recursive' ), + 'test pattern' => array( '', 'HelloWorld' ), + // Skips recursive patterns. + 'recursive pattern' => array( '', 'Recursive' ), // Resolves the pattern within a block. - array( 'BeforeAfter', 'BeforeHelloWorldAfter' ), + 'pattern within a block' => array( 'BeforeAfter', 'BeforeHelloWorldAfter' ), ); } } From 85fddc4c83674645912b938ff4d6978eb1426ce7 Mon Sep 17 00:00:00 2001 From: Anton Vlasenko Date: Mon, 3 Jun 2024 12:15:12 +0200 Subject: [PATCH 10/11] parent::tear_down() should usually be called at the end of the method. --- tests/phpunit/tests/blocks/resolvePatternBlocks.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/phpunit/tests/blocks/resolvePatternBlocks.php b/tests/phpunit/tests/blocks/resolvePatternBlocks.php index 493d9aa3c9baf..b2e6fa6463f7d 100644 --- a/tests/phpunit/tests/blocks/resolvePatternBlocks.php +++ b/tests/phpunit/tests/blocks/resolvePatternBlocks.php @@ -33,10 +33,10 @@ public function set_up() { } public function tear_down() { - parent::tear_down(); - unregister_block_pattern( 'core/test' ); unregister_block_pattern( 'core/recursive' ); + + parent::tear_down(); } /** From c16e3e240060e0ffe8adedd6faa35a1951e5ce20 Mon Sep 17 00:00:00 2001 From: Ella Date: Mon, 3 Jun 2024 16:38:12 +0300 Subject: [PATCH 11/11] Avoid extra arg --- src/wp-includes/blocks.php | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/wp-includes/blocks.php b/src/wp-includes/blocks.php index 1ee3445dc23ff..77eb23de1844d 100644 --- a/src/wp-includes/blocks.php +++ b/src/wp-includes/blocks.php @@ -1353,7 +1353,8 @@ function traverse_and_serialize_block( $block, $pre_callback = null, $post_callb * * @return array An array of blocks with patterns replaced by their content. */ -function resolve_pattern_blocks( $blocks, &$inner_content = null ) { +function resolve_pattern_blocks( $blocks ) { + static $inner_content; // Keep track of seen references to avoid infinite loops. static $seen_refs = array(); $i = 0; @@ -1385,7 +1386,10 @@ function resolve_pattern_blocks( $blocks, &$inner_content = null ) { $blocks_to_insert = parse_blocks( $pattern['content'] ); $seen_refs[ $slug ] = true; + $prev_inner_content = $inner_content; + $inner_content = null; $blocks_to_insert = resolve_pattern_blocks( $blocks_to_insert ); + $inner_content = $prev_inner_content; unset( $seen_refs[ $slug ] ); array_splice( $blocks, $i, 1, $blocks_to_insert ); @@ -1403,10 +1407,13 @@ function resolve_pattern_blocks( $blocks, &$inner_content = null ) { $i += count( $blocks_to_insert ); } else { if ( ! empty( $blocks[ $i ]['innerBlocks'] ) ) { + $prev_inner_content = $inner_content; + $inner_content = $blocks[ $i ]['innerContent']; $blocks[ $i ]['innerBlocks'] = resolve_pattern_blocks( - $blocks[ $i ]['innerBlocks'], - $blocks[ $i ]['innerContent'] + $blocks[ $i ]['innerBlocks'] ); + $blocks[ $i ]['innerContent'] = $inner_content; + $inner_content = $prev_inner_content; } ++$i; }