Skip to content

Commit e9fdd3d

Browse files
authored
Merge branch 'trunk' into add/settings-section-id-anchors
2 parents 1fe17a0 + e12ddb3 commit e9fdd3d

File tree

6 files changed

+417
-11
lines changed

6 files changed

+417
-11
lines changed

src/wp-includes/blocks.php

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1196,6 +1196,7 @@ function apply_block_hooks_to_content( $content, $context = null, $callback = 'i
11961196
* of the block that corresponds to the post type are handled correctly.
11971197
*
11981198
* @since 6.8.0
1199+
* @since 7.0.0 Added the `$ignored_hooked_blocks_at_root` parameter.
11991200
* @access private
12001201
*
12011202
* @param string $content Serialized content.
@@ -1205,9 +1206,17 @@ function apply_block_hooks_to_content( $content, $context = null, $callback = 'i
12051206
* @param callable $callback A function that will be called for each block to generate
12061207
* the markup for a given list of blocks that are hooked to it.
12071208
* Default: 'insert_hooked_blocks'.
1209+
* @param array|null $ignored_hooked_blocks_at_root A reference to an array that will be populated
1210+
* with the ignored hooked blocks at the root level.
1211+
* Default: `null`.
12081212
* @return string The serialized markup.
12091213
*/
1210-
function apply_block_hooks_to_content_from_post_object( $content, $post = null, $callback = 'insert_hooked_blocks' ) {
1214+
function apply_block_hooks_to_content_from_post_object(
1215+
$content,
1216+
$post = null,
1217+
$callback = 'insert_hooked_blocks',
1218+
&$ignored_hooked_blocks_at_root = null
1219+
) {
12111220
// Default to the current post if no context is provided.
12121221
if ( null === $post ) {
12131222
$post = get_post();
@@ -1287,6 +1296,16 @@ function apply_block_hooks_to_content_from_post_object( $content, $post = null,
12871296
$content = apply_block_hooks_to_content( $content, $post, $callback );
12881297
remove_filter( 'hooked_block_types', $suppress_blocks_from_insertion_before_and_after_wrapper_block, PHP_INT_MAX );
12891298

1299+
if ( null !== $ignored_hooked_blocks_at_root ) {
1300+
// Check wrapper block's metadata for ignored hooked blocks at the root level, and populate the reference parameter if needed.
1301+
$wrapper_block_markup = extract_serialized_parent_block( $content );
1302+
$wrapper_block = parse_blocks( $wrapper_block_markup )[0];
1303+
1304+
if ( ! empty( $wrapper_block['attrs']['metadata']['ignoredHookedBlocks'] ) ) {
1305+
$ignored_hooked_blocks_at_root = $wrapper_block['attrs']['metadata']['ignoredHookedBlocks'];
1306+
}
1307+
}
1308+
12901309
// Finally, we need to remove the temporary wrapper block.
12911310
$content = remove_serialized_parent_block( $content );
12921311

@@ -1449,6 +1468,7 @@ function insert_hooked_blocks_and_set_ignored_hooked_blocks_metadata( &$parsed_a
14491468
*
14501469
* @since 6.6.0
14511470
* @since 6.8.0 Support non-`wp_navigation` post types.
1471+
* @since 7.0.0 Set `_wp_ignored_hooked_blocks` meta in the response for blocks hooked at the root level.
14521472
*
14531473
* @param WP_REST_Response $response The response object.
14541474
* @param WP_Post $post Post object.
@@ -1459,12 +1479,18 @@ function insert_hooked_blocks_into_rest_response( $response, $post ) {
14591479
return $response;
14601480
}
14611481

1482+
$ignored_hooked_blocks_at_root = array();
14621483
$response->data['content']['raw'] = apply_block_hooks_to_content_from_post_object(
14631484
$response->data['content']['raw'],
14641485
$post,
1465-
'insert_hooked_blocks_and_set_ignored_hooked_blocks_metadata'
1486+
'insert_hooked_blocks_and_set_ignored_hooked_blocks_metadata',
1487+
$ignored_hooked_blocks_at_root
14661488
);
14671489

1490+
if ( ! empty( $ignored_hooked_blocks_at_root ) ) {
1491+
$response->data['meta']['_wp_ignored_hooked_blocks'] = wp_json_encode( $ignored_hooked_blocks_at_root );
1492+
}
1493+
14681494
// If the rendered content was previously empty, we leave it like that.
14691495
if ( empty( $response->data['content']['rendered'] ) ) {
14701496
return $response;

src/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-list-controller.php

Lines changed: 81 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,81 @@ private function normalize_schema_empty_object_defaults( array $schema ): array
215215
return $schema;
216216
}
217217

218+
/**
219+
* WordPress-internal schema keywords to strip from REST responses.
220+
*
221+
* @since 7.0.0
222+
* @var array<string, true>
223+
*/
224+
private const INTERNAL_SCHEMA_KEYWORDS = array(
225+
'sanitize_callback' => true,
226+
'validate_callback' => true,
227+
'arg_options' => true,
228+
);
229+
230+
/**
231+
* Recursively removes WordPress-internal keywords from a schema.
232+
*
233+
* Ability schemas may include WordPress-internal properties like
234+
* `sanitize_callback`, `validate_callback`, and `arg_options` that are
235+
* used server-side but are not valid JSON Schema keywords. This method
236+
* removes those specific keys so they are not exposed in REST responses.
237+
*
238+
* @since 7.0.0
239+
*
240+
* @param array<string, mixed> $schema The schema array.
241+
* @return array<string, mixed> The schema without WordPress-internal keywords.
242+
*/
243+
private function strip_internal_schema_keywords( array $schema ): array {
244+
$schema = array_diff_key( $schema, self::INTERNAL_SCHEMA_KEYWORDS );
245+
246+
// Sub-schema maps: keys are user-defined, values are sub-schemas.
247+
// Note: 'dependencies' values can also be property-dependency arrays
248+
// (numeric arrays of strings) which are skipped via wp_is_numeric_array().
249+
foreach ( array( 'properties', 'patternProperties', 'definitions', 'dependencies' ) as $keyword ) {
250+
if ( isset( $schema[ $keyword ] ) && is_array( $schema[ $keyword ] ) ) {
251+
foreach ( $schema[ $keyword ] as $key => $child_schema ) {
252+
if ( is_array( $child_schema ) && ! wp_is_numeric_array( $child_schema ) ) {
253+
$schema[ $keyword ][ $key ] = $this->strip_internal_schema_keywords( $child_schema );
254+
}
255+
}
256+
}
257+
}
258+
259+
// Single sub-schema keywords.
260+
foreach ( array( 'not', 'additionalProperties', 'additionalItems' ) as $keyword ) {
261+
if ( isset( $schema[ $keyword ] ) && is_array( $schema[ $keyword ] ) ) {
262+
$schema[ $keyword ] = $this->strip_internal_schema_keywords( $schema[ $keyword ] );
263+
}
264+
}
265+
266+
// Items: single schema or tuple array of schemas.
267+
if ( isset( $schema['items'] ) ) {
268+
if ( wp_is_numeric_array( $schema['items'] ) ) {
269+
foreach ( $schema['items'] as $index => $item_schema ) {
270+
if ( is_array( $item_schema ) ) {
271+
$schema['items'][ $index ] = $this->strip_internal_schema_keywords( $item_schema );
272+
}
273+
}
274+
} elseif ( is_array( $schema['items'] ) ) {
275+
$schema['items'] = $this->strip_internal_schema_keywords( $schema['items'] );
276+
}
277+
}
278+
279+
// Array-of-schemas keywords.
280+
foreach ( array( 'anyOf', 'oneOf', 'allOf' ) as $keyword ) {
281+
if ( isset( $schema[ $keyword ] ) && is_array( $schema[ $keyword ] ) ) {
282+
foreach ( $schema[ $keyword ] as $index => $sub_schema ) {
283+
if ( is_array( $sub_schema ) ) {
284+
$schema[ $keyword ][ $index ] = $this->strip_internal_schema_keywords( $sub_schema );
285+
}
286+
}
287+
}
288+
}
289+
290+
return $schema;
291+
}
292+
218293
/**
219294
* Prepares an ability for response.
220295
*
@@ -230,8 +305,12 @@ public function prepare_item_for_response( $ability, $request ) {
230305
'label' => $ability->get_label(),
231306
'description' => $ability->get_description(),
232307
'category' => $ability->get_category(),
233-
'input_schema' => $this->normalize_schema_empty_object_defaults( $ability->get_input_schema() ),
234-
'output_schema' => $this->normalize_schema_empty_object_defaults( $ability->get_output_schema() ),
308+
'input_schema' => $this->strip_internal_schema_keywords(
309+
$this->normalize_schema_empty_object_defaults( $ability->get_input_schema() )
310+
),
311+
'output_schema' => $this->strip_internal_schema_keywords(
312+
$this->normalize_schema_empty_object_defaults( $ability->get_output_schema() )
313+
),
235314
'meta' => $ability->get_meta(),
236315
);
237316

tests/phpunit/tests/blocks/applyBlockHooksToContentFromPostObject.php

Lines changed: 64 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -130,21 +130,59 @@ public function test_apply_block_hooks_to_content_from_post_object_inserts_hooke
130130
$this->assertSame( $expected, $actual );
131131
}
132132

133+
/**
134+
* @ticket 65008
135+
*/
136+
public function test_apply_block_hooks_to_content_from_post_object_sets_ignored_hooked_blocks() {
137+
$ignored_hooked_blocks_at_root = array();
138+
139+
$expected = '<!-- wp:tests/hooked-block-first-child /-->' .
140+
'<!-- wp:heading {"level":1,"metadata":{"ignoredHookedBlocks":["tests/hooked-block"]}} -->' .
141+
'<h1>Hello World!</h1>' .
142+
'<!-- /wp:heading -->' .
143+
'<!-- wp:tests/hooked-block /-->';
144+
$actual = apply_block_hooks_to_content_from_post_object(
145+
self::$post->post_content,
146+
self::$post,
147+
'insert_hooked_blocks_and_set_ignored_hooked_blocks_metadata',
148+
$ignored_hooked_blocks_at_root
149+
);
150+
$this->assertSame( $expected, $actual, "Markup wasn't updated correctly." );
151+
$this->assertSame(
152+
array( 'tests/hooked-block-first-child' ),
153+
$ignored_hooked_blocks_at_root,
154+
"Hooked block added at 'first_child' position wasn't added to ignoredHookedBlocks metadata."
155+
);
156+
}
157+
133158
/**
134159
* @ticket 62716
160+
* @ticket 65008
135161
*/
136162
public function test_apply_block_hooks_to_content_from_post_object_respects_ignored_hooked_blocks_post_meta() {
137-
$expected = self::$post_with_ignored_hooked_block->post_content . '<!-- wp:tests/hooked-block /-->';
163+
$ignored_hooked_blocks_at_root = array();
164+
165+
$expected = '<!-- wp:heading {"level":1,"metadata":{"ignoredHookedBlocks":["tests/hooked-block"]}} -->' .
166+
'<h1>Hello World!</h1>' .
167+
'<!-- /wp:heading -->' .
168+
'<!-- wp:tests/hooked-block /-->';
138169
$actual = apply_block_hooks_to_content_from_post_object(
139170
self::$post_with_ignored_hooked_block->post_content,
140171
self::$post_with_ignored_hooked_block,
141-
'insert_hooked_blocks'
172+
'insert_hooked_blocks_and_set_ignored_hooked_blocks_metadata',
173+
$ignored_hooked_blocks_at_root
142174
);
143175
$this->assertSame( $expected, $actual );
176+
$this->assertSame(
177+
array( 'tests/hooked-block-first-child' ),
178+
$ignored_hooked_blocks_at_root,
179+
"Pre-existing ignored hooked block at root level wasn't reflected in metadata."
180+
);
144181
}
145182

146183
/**
147184
* @ticket 63287
185+
* @ticket 65008
148186
*/
149187
public function test_apply_block_hooks_to_content_from_post_object_does_not_insert_hooked_block_before_container_block() {
150188
$filter = function ( $hooked_block_types, $relative_position, $anchor_block_type ) {
@@ -155,31 +193,50 @@ public function test_apply_block_hooks_to_content_from_post_object_does_not_inse
155193
return $hooked_block_types;
156194
};
157195

196+
$ignored_hooked_blocks_at_root = array();
197+
158198
$expected = '<!-- wp:tests/hooked-block-first-child /-->' .
159-
self::$post->post_content .
199+
'<!-- wp:heading {"level":1,"metadata":{"ignoredHookedBlocks":["tests/hooked-block"]}} -->' .
200+
'<h1>Hello World!</h1>' .
201+
'<!-- /wp:heading -->' .
160202
'<!-- wp:tests/hooked-block /-->';
161203

162204
add_filter( 'hooked_block_types', $filter, 10, 3 );
163205
$actual = apply_block_hooks_to_content_from_post_object(
164206
self::$post->post_content,
165207
self::$post,
166-
'insert_hooked_blocks'
208+
'insert_hooked_blocks_and_set_ignored_hooked_blocks_metadata',
209+
$ignored_hooked_blocks_at_root
167210
);
168211
remove_filter( 'hooked_block_types', $filter, 10 );
169212

170-
$this->assertSame( $expected, $actual );
213+
$this->assertSame( $expected, $actual, "Hooked block added before 'core/post-content' block shouldn't be inserted." );
214+
$this->assertSame(
215+
array( 'tests/hooked-block-first-child' ),
216+
$ignored_hooked_blocks_at_root,
217+
"ignoredHookedBlocks metadata wasn't set correctly."
218+
);
171219
}
172220

173221
/**
174222
* @ticket 62716
223+
* @ticket 65008
175224
*/
176225
public function test_apply_block_hooks_to_content_from_post_object_inserts_hooked_block_if_content_contains_no_blocks() {
226+
$ignored_hooked_blocks_at_root = array();
227+
177228
$expected = '<!-- wp:tests/hooked-block-first-child /-->' . self::$post_with_non_block_content->post_content;
178229
$actual = apply_block_hooks_to_content_from_post_object(
179230
self::$post_with_non_block_content->post_content,
180231
self::$post_with_non_block_content,
181-
'insert_hooked_blocks'
232+
'insert_hooked_blocks_and_set_ignored_hooked_blocks_metadata',
233+
$ignored_hooked_blocks_at_root
234+
);
235+
$this->assertSame( $expected, $actual, "Markup wasn't updated correctly." );
236+
$this->assertSame(
237+
array( 'tests/hooked-block-first-child' ),
238+
$ignored_hooked_blocks_at_root,
239+
"Hooked block added at 'first_child' position wasn't added to ignoredHookedBlocks metadata."
182240
);
183-
$this->assertSame( $expected, $actual );
184241
}
185242
}

tests/phpunit/tests/multisite/isEmailAddressUnsafe.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
/**
44
* @group ms-required
55
* @group multisite
6+
*
7+
* @covers ::is_email_address_unsafe
68
*/
79
class Tests_Multisite_IsEmailAddressUnsafe extends WP_UnitTestCase {
810

tests/phpunit/tests/multisite/isUploadSpaceAvailable.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
*
99
* @group ms-required
1010
* @group multisite
11+
*
12+
* @covers ::is_upload_space_available
1113
*/
1214
class Tests_Multisite_IsUploadSpaceAvailable extends WP_UnitTestCase {
1315

0 commit comments

Comments
 (0)