Skip to content

Commit 4cedbfc

Browse files
committed
Patterns: Add the pattern name to pattern blocks when they are converted
Modifies the `resolve_pattern_blocks` function to include metadata for single-root patterns, allowing the pattern `name`, `description`, categories` metadata to be stored in the block attributes. This enables identification of patterns within templates in the block editor for the purposes of the pattern editing functionality. Props ramonopoly, andrewserong, talldanwp, westonruter, scruffian, huzaifaalmesbah, audrasjb. Fixes #64123. git-svn-id: https://develop.svn.wordpress.org/trunk@61617 602fd350-edb4-49c9-b593-d223f7449a82
1 parent cd290e3 commit 4cedbfc

2 files changed

Lines changed: 143 additions & 6 deletions

File tree

src/wp-includes/blocks.php

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1839,6 +1839,7 @@ function traverse_and_serialize_block( $block, $pre_callback = null, $post_callb
18391839
* Replaces patterns in a block tree with their content.
18401840
*
18411841
* @since 6.6.0
1842+
* @since 7.0.0 Adds metadata to attributes of single-pattern container blocks.
18421843
*
18431844
* @param array $blocks An array blocks.
18441845
*
@@ -1875,7 +1876,39 @@ function resolve_pattern_blocks( $blocks ) {
18751876
continue;
18761877
}
18771878

1878-
$blocks_to_insert = parse_blocks( $pattern['content'] );
1879+
$blocks_to_insert = parse_blocks( trim( $pattern['content'] ) );
1880+
1881+
/*
1882+
* For single-root patterns, add the pattern name to make this a pattern instance in the editor.
1883+
* If the pattern has metadata, merge it with the existing metadata.
1884+
*/
1885+
if ( count( $blocks_to_insert ) === 1 ) {
1886+
$block_metadata = $blocks_to_insert[0]['attrs']['metadata'] ?? array();
1887+
$block_metadata['patternName'] = $slug;
1888+
1889+
/*
1890+
* Merge pattern metadata with existing block metadata.
1891+
* Pattern metadata takes precedence, but existing block metadata
1892+
* is preserved as a fallback when the pattern doesn't define that field.
1893+
* Only the defined fields (name, description, categories) are updated;
1894+
* other metadata keys are preserved.
1895+
*/
1896+
foreach ( array(
1897+
'name' => 'title', // 'title' is the field in the pattern object 'name' is the field in the block metadata.
1898+
'description' => 'description',
1899+
'categories' => 'categories',
1900+
) as $key => $pattern_key ) {
1901+
$value = $pattern[ $pattern_key ] ?? $block_metadata[ $key ] ?? null;
1902+
if ( $value ) {
1903+
$block_metadata[ $key ] = is_array( $value )
1904+
? array_map( 'sanitize_text_field', $value )
1905+
: sanitize_text_field( $value );
1906+
}
1907+
}
1908+
1909+
$blocks_to_insert[0]['attrs']['metadata'] = $block_metadata;
1910+
}
1911+
18791912
$seen_refs[ $slug ] = true;
18801913
$prev_inner_content = $inner_content;
18811914
$inner_content = null;

tests/phpunit/tests/blocks/resolvePatternBlocks.php

Lines changed: 109 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,74 @@ public function set_up() {
3030
'description' => 'Recursive pattern.',
3131
)
3232
);
33+
register_block_pattern(
34+
'core/single-root',
35+
array(
36+
'title' => 'Single Root Pattern',
37+
'content' => '<!-- wp:paragraph -->Single root content<!-- /wp:paragraph -->',
38+
'description' => 'A single root pattern.',
39+
'categories' => array( 'text' ),
40+
)
41+
);
42+
register_block_pattern(
43+
'core/single-root-with-forbidden-chars-in-attrs',
44+
array(
45+
'title' => 'Single Root Pattern<script>alert("XSS")</script>',
46+
'content' => '<!-- wp:paragraph -->Single root content<!-- /wp:paragraph -->',
47+
'description' => 'A single root pattern.<script>alert("XSS")</script><img src=x onerror=alert(1)>',
48+
'categories' => array(
49+
'text<script>alert("XSS")</script>',
50+
'bad\'); DROP TABLE wp_posts;--',
51+
'<img src=x onerror=alert(1)>',
52+
"evil\x00null\nbyte",
53+
'category with <strong>html</strong> tags',
54+
),
55+
)
56+
);
57+
register_block_pattern(
58+
'core/with-attrs',
59+
array(
60+
'title' => 'Pattern With Attrs',
61+
'content' => '<!-- wp:paragraph {"className":"custom-class"} -->Content<!-- /wp:paragraph -->',
62+
'description' => 'A pattern with existing attributes.',
63+
)
64+
);
65+
register_block_pattern(
66+
'core/nested-single',
67+
array(
68+
'title' => 'Nested Pattern',
69+
'content' => '<!-- wp:group --><!-- wp:paragraph -->Nested content<!-- /wp:paragraph --><!-- wp:pattern {"slug":"core/single-root"} /--><!-- /wp:group -->',
70+
'description' => 'A nested single root pattern.',
71+
'categories' => array( 'featured' ),
72+
)
73+
);
74+
register_block_pattern(
75+
'core/existing-metadata',
76+
array(
77+
'title' => 'Existing Metadata Pattern',
78+
'content' => '<!-- wp:paragraph {"metadata":{"patternName":"core/existing-metadata-should-not-overwrite","description":"A existing metadata pattern.","categories":["cake"]}} -->Existing metadata content<!-- /wp:paragraph -->',
79+
)
80+
);
81+
register_block_pattern(
82+
'core/with-custom-metadata',
83+
array(
84+
'title' => 'Pattern With Custom Metadata',
85+
'content' => '<!-- wp:paragraph {"metadata":{"customKey":"customValue","anotherKey":123,"booleanKey":true}} -->Content with custom metadata<!-- /wp:paragraph -->',
86+
'description' => 'A pattern with custom metadata keys.',
87+
'categories' => array( 'test' ),
88+
)
89+
);
3390
}
3491

3592
public function tear_down() {
3693
unregister_block_pattern( 'core/test' );
3794
unregister_block_pattern( 'core/recursive' );
38-
95+
unregister_block_pattern( 'core/single-root' );
96+
unregister_block_pattern( 'core/single-root-with-forbidden-chars-in-attrs' );
97+
unregister_block_pattern( 'core/with-attrs' );
98+
unregister_block_pattern( 'core/nested-single' );
99+
unregister_block_pattern( 'core/existing-metadata' );
100+
unregister_block_pattern( 'core/with-custom-metadata' );
39101
parent::tear_down();
40102
}
41103

@@ -60,13 +122,55 @@ public function test_should_resolve_pattern_blocks_as_expected( $blocks, $expect
60122
public function data_should_resolve_pattern_blocks_as_expected() {
61123
return array(
62124
// Works without attributes, leaves the block as is.
63-
'pattern with no slug attribute' => array( '<!-- wp:pattern /-->', '<!-- wp:pattern /-->' ),
125+
'pattern with no slug attribute' => array(
126+
'<!-- wp:pattern /-->',
127+
'<!-- wp:pattern /-->',
128+
),
64129
// Resolves the pattern.
65-
'test pattern' => array( '<!-- wp:pattern {"slug":"core/test"} /-->', '<!-- wp:paragraph -->Hello<!-- /wp:paragraph --><!-- wp:paragraph -->World<!-- /wp:paragraph -->' ),
130+
'test pattern' => array(
131+
'<!-- wp:pattern {"slug":"core/test"} /-->',
132+
'<!-- wp:paragraph -->Hello<!-- /wp:paragraph --><!-- wp:paragraph -->World<!-- /wp:paragraph -->',
133+
),
66134
// Skips recursive patterns.
67-
'recursive pattern' => array( '<!-- wp:pattern {"slug":"core/recursive"} /-->', '<!-- wp:paragraph -->Recursive<!-- /wp:paragraph -->' ),
135+
'recursive pattern' => array(
136+
'<!-- wp:pattern {"slug":"core/recursive"} /-->',
137+
'<!-- wp:paragraph -->Recursive<!-- /wp:paragraph -->',
138+
),
68139
// Resolves the pattern within a block.
69-
'pattern within a block' => array( '<!-- wp:group --><!-- wp:paragraph -->Before<!-- /wp:paragraph --><!-- wp:pattern {"slug":"core/test"} /--><!-- wp:paragraph -->After<!-- /wp:paragraph --><!-- /wp:group -->', '<!-- wp:group --><!-- wp:paragraph -->Before<!-- /wp:paragraph --><!-- wp:paragraph -->Hello<!-- /wp:paragraph --><!-- wp:paragraph -->World<!-- /wp:paragraph --><!-- wp:paragraph -->After<!-- /wp:paragraph --><!-- /wp:group -->' ),
140+
'pattern within a block' => array(
141+
'<!-- wp:group --><!-- wp:paragraph -->Before<!-- /wp:paragraph --><!-- wp:pattern {"slug":"core/test"} /--><!-- wp:paragraph -->After<!-- /wp:paragraph --><!-- /wp:group -->',
142+
'<!-- wp:group --><!-- wp:paragraph -->Before<!-- /wp:paragraph --><!-- wp:paragraph -->Hello<!-- /wp:paragraph --><!-- wp:paragraph -->World<!-- /wp:paragraph --><!-- wp:paragraph -->After<!-- /wp:paragraph --><!-- /wp:group -->',
143+
),
144+
// Resolves the single-root pattern and adds metadata.
145+
'single-root pattern' => array(
146+
'<!-- wp:pattern {"slug":"core/single-root"} /-->',
147+
'<!-- wp:paragraph {"metadata":{"patternName":"core/single-root","name":"Single Root Pattern","description":"A single root pattern.","categories":["text"]}} -->Single root content<!-- /wp:paragraph -->',
148+
),
149+
// Existing attributes are preserved when adding metadata.
150+
'existing attributes preserved' => array(
151+
'<!-- wp:pattern {"slug":"core/with-attrs"} /-->',
152+
'<!-- wp:paragraph {"className":"custom-class","metadata":{"patternName":"core/with-attrs","name":"Pattern With Attrs","description":"A pattern with existing attributes."}} -->Content<!-- /wp:paragraph -->',
153+
),
154+
// Resolves the nested single-root pattern and adds metadata.
155+
'nested single-root pattern' => array(
156+
'<!-- wp:pattern {"slug":"core/nested-single"} /-->',
157+
'<!-- wp:group {"metadata":{"patternName":"core/nested-single","name":"Nested Pattern","description":"A nested single root pattern.","categories":["featured"]}} --><!-- wp:paragraph -->Nested content<!-- /wp:paragraph --><!-- wp:paragraph {"metadata":{"patternName":"core/single-root","name":"Single Root Pattern","description":"A single root pattern.","categories":["text"]}} -->Single root content<!-- /wp:paragraph --><!-- /wp:group -->',
158+
),
159+
// Sanitizes fields.
160+
'sanitized pattern attrs' => array(
161+
'<!-- wp:pattern {"slug":"core/single-root-with-forbidden-chars-in-attrs"} /-->',
162+
'<!-- wp:paragraph {"metadata":{"patternName":"core/single-root-with-forbidden-chars-in-attrs","name":"Single Root Pattern","description":"A single root pattern.","categories":["text","bad\'); DROP TABLE wp_posts;\u002d\u002d","","evil\u0000null byte","category with html tags"]}} -->Single root content<!-- /wp:paragraph -->',
163+
),
164+
// Metadata is merged with existing metadata and existing metadata is preserved.
165+
'existing metadata preserved' => array(
166+
'<!-- wp:pattern {"slug":"core/existing-metadata"} /-->',
167+
'<!-- wp:paragraph {"metadata":{"patternName":"core/existing-metadata","description":"A existing metadata pattern.","categories":["cake"],"name":"Existing Metadata Pattern"}} -->Existing metadata content<!-- /wp:paragraph -->',
168+
),
169+
// Custom metadata keys are preserved when resolving patterns.
170+
'custom metadata preserved' => array(
171+
'<!-- wp:pattern {"slug":"core/with-custom-metadata"} /-->',
172+
'<!-- wp:paragraph {"metadata":{"customKey":"customValue","anotherKey":123,"booleanKey":true,"patternName":"core/with-custom-metadata","name":"Pattern With Custom Metadata","description":"A pattern with custom metadata keys.","categories":["test"]}} -->Content with custom metadata<!-- /wp:paragraph -->',
173+
),
70174
);
71175
}
72176
}

0 commit comments

Comments
 (0)