From be401b0a68eb8b313589362906db3c50d08baf85 Mon Sep 17 00:00:00 2001 From: Ramon Date: Tue, 14 Oct 2025 17:08:30 +1100 Subject: [PATCH 1/9] Modifies the `resolve_pattern_blocks` function to include metadata for single-root patterns, allowing the pattern name and title to be stored in the block attributes. Adds unit tests. --- src/wp-includes/blocks.php | 11 ++++++- .../tests/blocks/resolvePatternBlocks.php | 33 ++++++++++++++++++- 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/src/wp-includes/blocks.php b/src/wp-includes/blocks.php index a8ed76d3bd71f..05d6fe92acb3f 100644 --- a/src/wp-includes/blocks.php +++ b/src/wp-includes/blocks.php @@ -1880,7 +1880,16 @@ function resolve_pattern_blocks( $blocks ) { continue; } - $blocks_to_insert = parse_blocks( $pattern['content'] ); + $blocks_to_insert = parse_blocks( trim( $pattern['content'] ) ); + + // For single-root patterns, add the pattern name to make this a pattern instance in the editor. + if ( count( $blocks_to_insert ) === 1 ) { + $blocks_to_insert[0]['attrs']['metadata'] = array( + 'patternName' => $slug, + 'name' => $pattern['title'], + ); + } + $seen_refs[ $slug ] = true; $prev_inner_content = $inner_content; $inner_content = null; diff --git a/tests/phpunit/tests/blocks/resolvePatternBlocks.php b/tests/phpunit/tests/blocks/resolvePatternBlocks.php index b2e6fa6463f7d..fc697a92236bc 100644 --- a/tests/phpunit/tests/blocks/resolvePatternBlocks.php +++ b/tests/phpunit/tests/blocks/resolvePatternBlocks.php @@ -30,12 +30,37 @@ public function set_up() { 'description' => 'Recursive pattern.', ) ); + register_block_pattern( + 'core/single-root', + array( + 'title' => 'Single Root Pattern', + 'content' => 'Single root content', + 'description' => 'A single root pattern.', + ) + ); + register_block_pattern( + 'core/with-attrs', + array( + 'title' => 'Pattern With Attrs', + 'content' => 'Content', + 'description' => 'A pattern with existing attributes.', + ) + ); + register_block_pattern( + 'core/nested-single', + array( + 'title' => 'Nested Pattern', + 'content' => 'Nested content', + 'description' => 'A nested single root pattern.', + ) + ); } public function tear_down() { unregister_block_pattern( 'core/test' ); unregister_block_pattern( 'core/recursive' ); - + unregister_block_pattern( 'core/single-root' ); + unregister_block_pattern( 'core/with-attrs' ); parent::tear_down(); } @@ -67,6 +92,12 @@ public function data_should_resolve_pattern_blocks_as_expected() { 'recursive pattern' => array( '', 'Recursive' ), // Resolves the pattern within a block. 'pattern within a block' => array( 'BeforeAfter', 'BeforeHelloWorldAfter' ), + // Resolves the single-root pattern and adds metadata. + 'single-root pattern' => array( '', 'Single root content' ), + // Existing attributes are preserved when adding metadata. + 'existing attributes preserved' => array( '', 'Content' ), + // Resolves the nested single-root pattern and adds metadata. + 'nested single-root pattern' => array( '', 'Nested contentSingle root content' ), ); } } From a7001d3bfd9d564192ccc8ea3016d70ce51d6431 Mon Sep 17 00:00:00 2001 From: Ramon Date: Fri, 24 Oct 2025 12:45:33 +1100 Subject: [PATCH 2/9] Add metadata support for single-root patterns in `resolve_pattern_blocks` function Enhances the `resolve_pattern_blocks` function to include additional metadata attributes such as `description` and `categories` for single-root patterns. Updates unit tests to validate the new functionality, including sanitization of potentially harmful characters in pattern attributes. --- src/wp-includes/blocks.php | 18 +++++- .../tests/blocks/resolvePatternBlocks.php | 59 ++++++++++++++++--- 2 files changed, 68 insertions(+), 9 deletions(-) diff --git a/src/wp-includes/blocks.php b/src/wp-includes/blocks.php index 05d6fe92acb3f..57b4b53d45aeb 100644 --- a/src/wp-includes/blocks.php +++ b/src/wp-includes/blocks.php @@ -1844,6 +1844,7 @@ function traverse_and_serialize_block( $block, $pre_callback = null, $post_callb * Replaces patterns in a block tree with their content. * * @since 6.6.0 + * @since n.e.x.t Adds metadata to attributes of single-pattern container blocks. * * @param array $blocks An array blocks. * @@ -1884,10 +1885,23 @@ function resolve_pattern_blocks( $blocks ) { // For single-root patterns, add the pattern name to make this a pattern instance in the editor. if ( count( $blocks_to_insert ) === 1 ) { - $blocks_to_insert[0]['attrs']['metadata'] = array( + $metadata = array( 'patternName' => $slug, - 'name' => $pattern['title'], ); + + if ( ! empty( $pattern['title'] ) ) { + $metadata['name'] = sanitize_text_field( $pattern['title'] ); + } + + if ( ! empty( $pattern['description'] ) ) { + $metadata['description'] = sanitize_text_field( $pattern['description'] ); + } + + if ( ! empty( $pattern['categories'] ) && is_array( $pattern['categories'] ) ) { + $metadata['categories'] = array_map( 'sanitize_text_field', $pattern['categories'] ); + } + + $blocks_to_insert[0]['attrs']['metadata'] = $metadata; } $seen_refs[ $slug ] = true; diff --git a/tests/phpunit/tests/blocks/resolvePatternBlocks.php b/tests/phpunit/tests/blocks/resolvePatternBlocks.php index fc697a92236bc..8168e6d558fd4 100644 --- a/tests/phpunit/tests/blocks/resolvePatternBlocks.php +++ b/tests/phpunit/tests/blocks/resolvePatternBlocks.php @@ -36,6 +36,22 @@ public function set_up() { 'title' => 'Single Root Pattern', 'content' => 'Single root content', 'description' => 'A single root pattern.', + 'categories' => array( 'text' ), + ) + ); + register_block_pattern( + 'core/single-root-with-forbidden-chars-in-attrs', + array( + 'title' => 'Single Root Pattern', + 'content' => 'Single root content', + 'description' => 'A single root pattern.', + 'categories' => array( + 'text', + 'bad\'); DROP TABLE wp_posts;--', + '', + "evil\x00null\nbyte", + 'category with html tags', + ), ) ); register_block_pattern( @@ -52,6 +68,7 @@ public function set_up() { 'title' => 'Nested Pattern', 'content' => 'Nested content', 'description' => 'A nested single root pattern.', + 'categories' => array( 'featured' ), ) ); } @@ -60,7 +77,9 @@ public function tear_down() { unregister_block_pattern( 'core/test' ); unregister_block_pattern( 'core/recursive' ); unregister_block_pattern( 'core/single-root' ); + unregister_block_pattern( 'core/single-root-with-forbidden-chars-in-attrs' ); unregister_block_pattern( 'core/with-attrs' ); + unregister_block_pattern( 'core/nested-single' ); parent::tear_down(); } @@ -85,19 +104,45 @@ public function test_should_resolve_pattern_blocks_as_expected( $blocks, $expect public function data_should_resolve_pattern_blocks_as_expected() { return array( // Works without attributes, leaves the block as is. - 'pattern with no slug attribute' => array( '', '' ), + 'pattern with no slug attribute' => array( + '', + '', + ), // Resolves the pattern. - 'test pattern' => array( '', 'HelloWorld' ), + 'test pattern' => array( + '', + 'HelloWorld', + ), // Skips recursive patterns. - 'recursive pattern' => array( '', 'Recursive' ), + 'recursive pattern' => array( + '', + 'Recursive', + ), // Resolves the pattern within a block. - 'pattern within a block' => array( 'BeforeAfter', 'BeforeHelloWorldAfter' ), + 'pattern within a block' => array( + 'BeforeAfter', + 'BeforeHelloWorldAfter', + ), // Resolves the single-root pattern and adds metadata. - 'single-root pattern' => array( '', 'Single root content' ), + 'single-root pattern' => array( + '', + 'Single root content', + ), // Existing attributes are preserved when adding metadata. - 'existing attributes preserved' => array( '', 'Content' ), + 'existing attributes preserved' => array( + '', + 'Content', + ), // Resolves the nested single-root pattern and adds metadata. - 'nested single-root pattern' => array( '', 'Nested contentSingle root content' ), + 'nested single-root pattern' => array( + '', + 'Nested contentSingle root content', + ), + // Sanitizes fields. + 'sanitized pattern attrs' => array( + '', + 'Single root content', + ), ); } } From a372d36d2d9d86e8c7b73f28bd4f3b54a623dadb Mon Sep 17 00:00:00 2001 From: Ramon Date: Wed, 5 Nov 2025 16:48:02 +1100 Subject: [PATCH 3/9] Enhance metadata handling in `resolve_pattern_blocks` function Refines the logic for merging metadata in single-root patterns, ensuring that existing metadata is preserved and new attributes such as `name`, `description`, and `categories` are correctly integrated. Updates unit tests to validate the new behavior, including a test for preserving existing metadata. --- src/wp-includes/blocks.php | 33 ++++++++++--------- .../tests/blocks/resolvePatternBlocks.php | 12 +++++++ 2 files changed, 30 insertions(+), 15 deletions(-) diff --git a/src/wp-includes/blocks.php b/src/wp-includes/blocks.php index 57b4b53d45aeb..59baa4ba75461 100644 --- a/src/wp-includes/blocks.php +++ b/src/wp-includes/blocks.php @@ -1883,22 +1883,25 @@ function resolve_pattern_blocks( $blocks ) { $blocks_to_insert = parse_blocks( trim( $pattern['content'] ) ); - // For single-root patterns, add the pattern name to make this a pattern instance in the editor. + /* + * For single-root patterns, add the pattern name to make this a pattern instance in the editor. + * If the pattern has metadata, merge it with the existing metadata. + */ if ( count( $blocks_to_insert ) === 1 ) { - $metadata = array( - 'patternName' => $slug, - ); - - if ( ! empty( $pattern['title'] ) ) { - $metadata['name'] = sanitize_text_field( $pattern['title'] ); - } - - if ( ! empty( $pattern['description'] ) ) { - $metadata['description'] = sanitize_text_field( $pattern['description'] ); - } - - if ( ! empty( $pattern['categories'] ) && is_array( $pattern['categories'] ) ) { - $metadata['categories'] = array_map( 'sanitize_text_field', $pattern['categories'] ); + $block_metadata = $blocks_to_insert[0]['attrs']['metadata'] ?? array(); + $metadata = array( 'patternName' => $slug ); + + foreach ( array( + 'name' => 'title', + 'description' => 'description', + 'categories' => 'categories', + ) as $key => $pattern_key ) { + $value = $pattern[ $pattern_key ] ?? $block_metadata[ $key ] ?? null; + if ( $value ) { + $metadata[ $key ] = 'categories' === $key && is_array( $value ) + ? array_map( 'sanitize_text_field', $value ) + : sanitize_text_field( $value ); + } } $blocks_to_insert[0]['attrs']['metadata'] = $metadata; diff --git a/tests/phpunit/tests/blocks/resolvePatternBlocks.php b/tests/phpunit/tests/blocks/resolvePatternBlocks.php index 8168e6d558fd4..40ad6ce9d4c2e 100644 --- a/tests/phpunit/tests/blocks/resolvePatternBlocks.php +++ b/tests/phpunit/tests/blocks/resolvePatternBlocks.php @@ -71,6 +71,13 @@ public function set_up() { 'categories' => array( 'featured' ), ) ); + register_block_pattern( + 'core/existing-metadata', + array( + 'title' => 'Existing Metadata Pattern', + 'content' => 'Existing metadata content', + ) + ); } public function tear_down() { @@ -143,6 +150,11 @@ public function data_should_resolve_pattern_blocks_as_expected() { '', 'Single root content', ), + // Metadata is merged with existing metadata and existing metadata is preserved. + 'existing metadata preserved' => array( + '', + 'Existing metadata content', + ), ); } } From 8cf12add51beef1f6cdd8d7370c82b06bc1eb59c Mon Sep 17 00:00:00 2001 From: Ramon Date: Wed, 5 Nov 2025 16:52:16 +1100 Subject: [PATCH 4/9] LINTO --- tests/phpunit/tests/blocks/resolvePatternBlocks.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/phpunit/tests/blocks/resolvePatternBlocks.php b/tests/phpunit/tests/blocks/resolvePatternBlocks.php index 40ad6ce9d4c2e..0b4f1143e5870 100644 --- a/tests/phpunit/tests/blocks/resolvePatternBlocks.php +++ b/tests/phpunit/tests/blocks/resolvePatternBlocks.php @@ -74,8 +74,8 @@ public function set_up() { register_block_pattern( 'core/existing-metadata', array( - 'title' => 'Existing Metadata Pattern', - 'content' => 'Existing metadata content', + 'title' => 'Existing Metadata Pattern', + 'content' => 'Existing metadata content', ) ); } @@ -151,7 +151,7 @@ public function data_should_resolve_pattern_blocks_as_expected() { 'Single root content', ), // Metadata is merged with existing metadata and existing metadata is preserved. - 'existing metadata preserved' => array( + 'existing metadata preserved' => array( '', 'Existing metadata content', ), From fbcce00dd4d2cb1ff682a3fcf1a561263d8bb010 Mon Sep 17 00:00:00 2001 From: Ramon Date: Wed, 5 Nov 2025 16:53:47 +1100 Subject: [PATCH 5/9] Remove the block pattern 'core/existing-metadata' from the unit test teardown in `resolvePatternBlocks` test suite. --- tests/phpunit/tests/blocks/resolvePatternBlocks.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/phpunit/tests/blocks/resolvePatternBlocks.php b/tests/phpunit/tests/blocks/resolvePatternBlocks.php index 0b4f1143e5870..a2969ff2b5b82 100644 --- a/tests/phpunit/tests/blocks/resolvePatternBlocks.php +++ b/tests/phpunit/tests/blocks/resolvePatternBlocks.php @@ -87,6 +87,7 @@ public function tear_down() { unregister_block_pattern( 'core/single-root-with-forbidden-chars-in-attrs' ); unregister_block_pattern( 'core/with-attrs' ); unregister_block_pattern( 'core/nested-single' ); + unregister_block_pattern( 'core/existing-metadata' ); parent::tear_down(); } From 884ce23e787bb11fcac3cae43e95151cd95284b8 Mon Sep 17 00:00:00 2001 From: Ramon Date: Mon, 10 Nov 2025 14:56:32 +1100 Subject: [PATCH 6/9] Refactor metadata merging in `resolve_pattern_blocks` function Improves the handling of metadata for single-root patterns by ensuring that existing metadata is preserved while integrating new attributes such as `name`, `description`, and `categories`. Updates unit tests to reflect the changes in metadata structure, ensuring accurate representation of merged metadata. --- src/wp-includes/blocks.php | 25 +++++++++++++------ .../tests/blocks/resolvePatternBlocks.php | 2 +- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/src/wp-includes/blocks.php b/src/wp-includes/blocks.php index 59baa4ba75461..f7883d6e2ec44 100644 --- a/src/wp-includes/blocks.php +++ b/src/wp-includes/blocks.php @@ -1883,28 +1883,37 @@ function resolve_pattern_blocks( $blocks ) { $blocks_to_insert = parse_blocks( trim( $pattern['content'] ) ); + $blocks_to_insert = parse_blocks( trim( $pattern['content'] ) ); + /* - * For single-root patterns, add the pattern name to make this a pattern instance in the editor. - * If the pattern has metadata, merge it with the existing metadata. - */ + * For single-root patterns, add the pattern name to make this a pattern instance in the editor. + * If the pattern has metadata, merge it with the existing metadata. + */ if ( count( $blocks_to_insert ) === 1 ) { - $block_metadata = $blocks_to_insert[0]['attrs']['metadata'] ?? array(); - $metadata = array( 'patternName' => $slug ); + $block_metadata = $blocks_to_insert[0]['attrs']['metadata'] ?? array(); + $block_metadata['patternName'] = $slug; + /* + * Merge pattern metadata with existing block metadata. + * Pattern metadata takes precedence, but existing block metadata + * is preserved as a fallback when the pattern doesn't define that field. + * Only the defined fields (name, description, categories) are updated; + * other metadata keys are preserved. + */ foreach ( array( - 'name' => 'title', + 'name' => 'title', // 'title' is the field in the pattern object 'name' is the field in the block metadata. 'description' => 'description', 'categories' => 'categories', ) as $key => $pattern_key ) { $value = $pattern[ $pattern_key ] ?? $block_metadata[ $key ] ?? null; if ( $value ) { - $metadata[ $key ] = 'categories' === $key && is_array( $value ) + $block_metadata[ $key ] = 'categories' === $key && is_array( $value ) ? array_map( 'sanitize_text_field', $value ) : sanitize_text_field( $value ); } } - $blocks_to_insert[0]['attrs']['metadata'] = $metadata; + $blocks_to_insert[0]['attrs']['metadata'] = $block_metadata; } $seen_refs[ $slug ] = true; diff --git a/tests/phpunit/tests/blocks/resolvePatternBlocks.php b/tests/phpunit/tests/blocks/resolvePatternBlocks.php index a2969ff2b5b82..f882f65cde455 100644 --- a/tests/phpunit/tests/blocks/resolvePatternBlocks.php +++ b/tests/phpunit/tests/blocks/resolvePatternBlocks.php @@ -154,7 +154,7 @@ public function data_should_resolve_pattern_blocks_as_expected() { // Metadata is merged with existing metadata and existing metadata is preserved. 'existing metadata preserved' => array( '', - 'Existing metadata content', + 'Existing metadata content', ), ); } From d4cf16ae6bf5f2dacd272c1bec0c46289d61536c Mon Sep 17 00:00:00 2001 From: Ramon Date: Mon, 10 Nov 2025 14:57:35 +1100 Subject: [PATCH 7/9] Update block metadata versioning in `blocks.php` to reflect changes in single-pattern container blocks for version 7.0.0. --- src/wp-includes/blocks.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wp-includes/blocks.php b/src/wp-includes/blocks.php index f7883d6e2ec44..a7ba4ec6c7e83 100644 --- a/src/wp-includes/blocks.php +++ b/src/wp-includes/blocks.php @@ -1844,7 +1844,7 @@ function traverse_and_serialize_block( $block, $pre_callback = null, $post_callb * Replaces patterns in a block tree with their content. * * @since 6.6.0 - * @since n.e.x.t Adds metadata to attributes of single-pattern container blocks. + * @since 7.0.0 Adds metadata to attributes of single-pattern container blocks. * * @param array $blocks An array blocks. * From 5e54013e342090bd0782819e6bccdadf52191da1 Mon Sep 17 00:00:00 2001 From: Ramon Date: Mon, 10 Nov 2025 15:23:35 +1100 Subject: [PATCH 8/9] Add custom metadata support for block patterns in unit tests Introduces a new block pattern 'core/with-custom-metadata' with custom metadata keys. Updates the unit tests to ensure that custom metadata is preserved when resolving patterns, enhancing the test coverage for metadata handling in block patterns. --- .../phpunit/tests/blocks/resolvePatternBlocks.php | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/phpunit/tests/blocks/resolvePatternBlocks.php b/tests/phpunit/tests/blocks/resolvePatternBlocks.php index f882f65cde455..4b28db4613f00 100644 --- a/tests/phpunit/tests/blocks/resolvePatternBlocks.php +++ b/tests/phpunit/tests/blocks/resolvePatternBlocks.php @@ -78,6 +78,15 @@ public function set_up() { 'content' => 'Existing metadata content', ) ); + register_block_pattern( + 'core/with-custom-metadata', + array( + 'title' => 'Pattern With Custom Metadata', + 'content' => 'Content with custom metadata', + 'description' => 'A pattern with custom metadata keys.', + 'categories' => array( 'test' ), + ) + ); } public function tear_down() { @@ -88,6 +97,7 @@ public function tear_down() { unregister_block_pattern( 'core/with-attrs' ); unregister_block_pattern( 'core/nested-single' ); unregister_block_pattern( 'core/existing-metadata' ); + unregister_block_pattern( 'core/with-custom-metadata' ); parent::tear_down(); } @@ -156,6 +166,11 @@ public function data_should_resolve_pattern_blocks_as_expected() { '', 'Existing metadata content', ), + // Custom metadata keys are preserved when resolving patterns. + 'custom metadata preserved' => array( + '', + 'Content with custom metadata', + ), ); } } From 44c4c8fa6e386847a499ee871fd76d5051873eb4 Mon Sep 17 00:00:00 2001 From: Ramon Date: Wed, 26 Nov 2025 15:46:06 +1100 Subject: [PATCH 9/9] Refactor comments in `resolve_pattern_blocks` function for clarity Improves the documentation within the `resolve_pattern_blocks` function by enhancing comments related to metadata handling for single-root patterns. This change aims to provide clearer guidance on the merging process of pattern metadata with existing block metadata. --- src/wp-includes/blocks.php | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/src/wp-includes/blocks.php b/src/wp-includes/blocks.php index a7ba4ec6c7e83..dff2ada10b6eb 100644 --- a/src/wp-includes/blocks.php +++ b/src/wp-includes/blocks.php @@ -1883,23 +1883,21 @@ function resolve_pattern_blocks( $blocks ) { $blocks_to_insert = parse_blocks( trim( $pattern['content'] ) ); - $blocks_to_insert = parse_blocks( trim( $pattern['content'] ) ); - /* - * For single-root patterns, add the pattern name to make this a pattern instance in the editor. - * If the pattern has metadata, merge it with the existing metadata. - */ + * For single-root patterns, add the pattern name to make this a pattern instance in the editor. + * If the pattern has metadata, merge it with the existing metadata. + */ if ( count( $blocks_to_insert ) === 1 ) { $block_metadata = $blocks_to_insert[0]['attrs']['metadata'] ?? array(); $block_metadata['patternName'] = $slug; /* - * Merge pattern metadata with existing block metadata. - * Pattern metadata takes precedence, but existing block metadata - * is preserved as a fallback when the pattern doesn't define that field. - * Only the defined fields (name, description, categories) are updated; - * other metadata keys are preserved. - */ + * Merge pattern metadata with existing block metadata. + * Pattern metadata takes precedence, but existing block metadata + * is preserved as a fallback when the pattern doesn't define that field. + * Only the defined fields (name, description, categories) are updated; + * other metadata keys are preserved. + */ foreach ( array( 'name' => 'title', // 'title' is the field in the pattern object 'name' is the field in the block metadata. 'description' => 'description', @@ -1907,7 +1905,7 @@ function resolve_pattern_blocks( $blocks ) { ) as $key => $pattern_key ) { $value = $pattern[ $pattern_key ] ?? $block_metadata[ $key ] ?? null; if ( $value ) { - $block_metadata[ $key ] = 'categories' === $key && is_array( $value ) + $block_metadata[ $key ] = is_array( $value ) ? array_map( 'sanitize_text_field', $value ) : sanitize_text_field( $value ); }