Skip to content

Commit dccbe16

Browse files
authored
Merge branch 'WordPress:trunk' into trunk
2 parents 8225ac3 + 5f14502 commit dccbe16

11 files changed

Lines changed: 306 additions & 46 deletions

File tree

src/wp-includes/abilities-api.php

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
* - Define permission checks and execution callbacks.
1616
* - Organize abilities into logical categories.
1717
* - Validate inputs and outputs using JSON Schema.
18-
* - Expose abilities through the REST API.
18+
* - Expose abilities to clients such as the REST API.
1919
*
2020
* ## Working with Abilities
2121
*
@@ -48,7 +48,7 @@
4848
* 'required' => true,
4949
* ),
5050
* 'meta' => array(
51-
* 'show_in_rest' => true,
51+
* 'public' => true,
5252
* ),
5353
* )
5454
* );
@@ -118,10 +118,10 @@
118118
* 'execute_callback' => 'my_plugin_analyze_text',
119119
* 'permission_callback' => 'my_plugin_can_analyze_text',
120120
* 'meta' => array(
121-
* 'annotations' => array(
121+
* 'annotations' => array(
122122
* 'readonly' => true,
123123
* ),
124-
* 'show_in_rest' => true,
124+
* 'public' => true,
125125
* ),
126126
* )
127127
* );
@@ -208,16 +208,24 @@
208208
* return current_user_can( 'edit_posts' );
209209
* }
210210
*
211-
* ### REST API Integration
211+
* ### Client Exposure
212212
*
213-
* Abilities can be exposed through the REST API by setting `show_in_rest`
214-
* to `true` in the meta configuration:
213+
* Set the high-level `public` flag to make an ability available to clients
214+
* such as the REST API, MCP, or AI agents:
215215
*
216216
* 'meta' => array(
217-
* 'show_in_rest' => true,
217+
* 'public' => true,
218218
* ),
219219
*
220-
* This allows abilities to be invoked via HTTP requests to the WordPress REST API.
220+
* The `public` flag seeds the default for each per-channel flag. For the REST
221+
* API it seeds `show_in_rest`, which lets the ability be invoked via HTTP
222+
* requests. Set a per-channel flag directly to override that default. For
223+
* example, keep a public ability out of the REST API:
224+
*
225+
* 'meta' => array(
226+
* 'public' => true,
227+
* 'show_in_rest' => false,
228+
* ),
221229
*
222230
* @since 6.9.0
223231
*
@@ -264,10 +272,10 @@
264272
* @type bool|null $idempotent Optional. If true, calling the ability repeatedly with the same arguments
265273
* will have no additional effect on its environment.
266274
* }
267-
* @type bool $public Optional. Whether the ability is intended to be publicly
268-
* available to clients. When true, channel-specific exposure
269-
* flags such as `$show_in_rest` default to true. An explicitly
270-
* set channel flag always takes precedence.
275+
* @type bool $public Optional. Whether the ability is meant to be available to
276+
* clients such as the REST API, MCP, or AI agents. Seeds
277+
* the default for per-channel flags like `$show_in_rest`.
278+
* Defaults to false.
271279
* @type bool $show_in_rest Optional. Whether to expose this ability in the REST API.
272280
* When true, the ability can be invoked via HTTP requests.
273281
* Default is the value of `$public` when set, false otherwise.

src/wp-includes/abilities-api/class-wp-abilities-registry.php

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -71,10 +71,10 @@ final class WP_Abilities_Registry {
7171
* @type bool|null $idempotent Optional. If true, calling the ability repeatedly with the same arguments
7272
* will have no additional effect on its environment.
7373
* }
74-
* @type bool $public Optional. Whether the ability is intended to be publicly
75-
* available to clients. When true, channel-specific exposure
76-
* flags such as `$show_in_rest` default to true. An explicitly
77-
* set channel flag always takes precedence.
74+
* @type bool $public Optional. Whether the ability is meant to be available
75+
* to clients such as the REST API, MCP, or AI agents.
76+
* Seeds the default for per-channel flags like
77+
* `$show_in_rest`. Defaults to false.
7878
* @type bool $show_in_rest Optional. Whether to expose this ability in the REST API.
7979
* Default is the value of `$public` when set, false otherwise.
8080
* }
@@ -125,9 +125,10 @@ public function register( string $name, array $args ): ?WP_Ability {
125125
* Optional. Additional metadata for the ability.
126126
*
127127
* @type array<string, bool|string> $annotations Optional. Annotation metadata for the ability.
128-
* @type bool $public Optional. Whether the ability is intended to be
129-
* publicly available to clients. Seeds the default for
130-
* channel-specific exposure flags like `$show_in_rest`.
128+
* @type bool $public Optional. Whether the ability is meant to be
129+
* available to clients such as the REST API, MCP, or AI
130+
* agents. Seeds the default for per-channel flags like
131+
* `$show_in_rest`. Defaults to false.
131132
* @type bool $show_in_rest Optional. Whether to expose this ability in the REST API.
132133
* Default is the value of `$public` when set, false otherwise.
133134
* }

src/wp-includes/abilities-api/class-wp-ability.php

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,14 @@ class WP_Ability {
2828
*/
2929
protected const DEFAULT_SHOW_IN_REST = false;
3030

31+
/**
32+
* The default value for the `public` meta.
33+
*
34+
* @since 7.1.0
35+
* @var bool
36+
*/
37+
protected const DEFAULT_PUBLIC = false;
38+
3139
/**
3240
* The default ability annotations.
3341
* They are not guaranteed to provide a faithful description of ability behavior.
@@ -160,10 +168,10 @@ class WP_Ability {
160168
* @type bool|null $idempotent Optional. If true, calling the ability repeatedly with the same arguments
161169
* will have no additional effect on its environment.
162170
* }
163-
* @type bool $public Optional. Whether the ability is intended to be publicly
164-
* available to clients. When true, channel-specific exposure
165-
* flags such as `$show_in_rest` default to true. An explicitly
166-
* set channel flag always takes precedence.
171+
* @type bool $public Optional. Whether the ability is meant to be available
172+
* to clients such as the REST API, MCP, or AI agents.
173+
* Seeds the default for per-channel flags like
174+
* `$show_in_rest`. Defaults to false.
167175
* @type bool $show_in_rest Optional. Whether to expose this ability in the REST API.
168176
* Default is the value of `$public` when set, false otherwise.
169177
* }
@@ -229,10 +237,10 @@ public function __construct( string $name, array $args ) {
229237
* @type bool|null $idempotent Optional. If true, calling the ability repeatedly with the same arguments
230238
* will have no additional effect on its environment.
231239
* }
232-
* @type bool $public Optional. Whether the ability is intended to be publicly
233-
* available to clients. When true, channel-specific exposure
234-
* flags such as `$show_in_rest` default to true. An explicitly
235-
* set channel flag always takes precedence.
240+
* @type bool $public Optional. Whether the ability is meant to be available
241+
* to clients such as the REST API, MCP, or AI agents.
242+
* Seeds the default for per-channel flags like
243+
* `$show_in_rest`. Defaults to false.
236244
* @type bool $show_in_rest Optional. Whether to expose this ability in the REST API.
237245
* Default is the value of `$public` when set, false otherwise.
238246
* }
@@ -262,8 +270,9 @@ public function __construct( string $name, array $args ) {
262270
* @type bool|null $idempotent If true, calling the ability repeatedly with the same arguments
263271
* will have no additional effect on its environment.
264272
* }
265-
* @type bool $public Whether the ability is intended to be publicly available
266-
* to clients. Only present when provided during registration.
273+
* @type bool $public Whether the ability is meant to be available to clients
274+
* such as the REST API, MCP, or AI agents. Defaults to
275+
* false.
267276
* @type bool $show_in_rest Whether to expose this ability in the REST API.
268277
* }
269278
* }
@@ -348,17 +357,18 @@ protected function prepare_properties( array $args ): array {
348357
)
349358
);
350359

360+
$args['meta']['annotations'] = wp_parse_args(
361+
$args['meta']['annotations'],
362+
static::$default_annotations
363+
);
364+
351365
/*
352366
* Resolve `show_in_rest` from most specific to least specific: an explicit
353367
* `show_in_rest` value wins, then the high-level `public` flag seeds the
354368
* default, then the built-in default applies.
355369
*/
356370
$args['meta']['show_in_rest'] = $args['meta']['show_in_rest'] ?? $args['meta']['public'] ?? self::DEFAULT_SHOW_IN_REST;
357-
358-
$args['meta']['annotations'] = wp_parse_args(
359-
$args['meta']['annotations'],
360-
static::$default_annotations
361-
);
371+
$args['meta']['public'] = $args['meta']['public'] ?? self::DEFAULT_PUBLIC;
362372

363373
return $args;
364374
}

src/wp-includes/abilities.php

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -132,12 +132,12 @@ function wp_register_core_abilities(): void {
132132
return current_user_can( 'manage_options' );
133133
},
134134
'meta' => array(
135-
'annotations' => array(
135+
'annotations' => array(
136136
'readonly' => true,
137137
'destructive' => false,
138138
'idempotent' => true,
139139
),
140-
'show_in_rest' => true,
140+
'public' => true,
141141
),
142142
)
143143
);
@@ -256,12 +256,12 @@ function wp_register_core_abilities(): void {
256256
return is_user_logged_in();
257257
},
258258
'meta' => array(
259-
'annotations' => array(
259+
'annotations' => array(
260260
'readonly' => true,
261261
'destructive' => false,
262262
'idempotent' => true,
263263
),
264-
'show_in_rest' => true,
264+
'public' => true,
265265
),
266266
)
267267
);
@@ -342,12 +342,12 @@ function wp_register_core_abilities(): void {
342342
return current_user_can( 'manage_options' );
343343
},
344344
'meta' => array(
345-
'annotations' => array(
345+
'annotations' => array(
346346
'readonly' => true,
347347
'destructive' => false,
348348
'idempotent' => true,
349349
),
350-
'show_in_rest' => true,
350+
'public' => true,
351351
),
352352
)
353353
);

src/wp-includes/icons.php

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
<?php
2+
/**
3+
* Icons API: Icon-rendering helper functions.
4+
*
5+
* @package WordPress
6+
* @subpackage Icons
7+
* @since 7.1.0
8+
*/
9+
10+
/**
11+
* Returns the SVG markup for a registered icon.
12+
*
13+
* @since 7.1.0
14+
*
15+
* @param string $name The namespaced icon name (e.g. 'core/plus',
16+
* 'core/arrow-down', 'my-plugin/custom-icon').
17+
* @param array $args {
18+
* Optional. Arguments for the icon. Default empty array.
19+
*
20+
* @type int|null $size Width and height in pixels. Pass null to leave the
21+
* SVG's intrinsic dimensions untouched. Default 24.
22+
* @type string $class Additional CSS class names. Multiple classes may be
23+
* provided as a space-separated string. Default empty string.
24+
* @type string $label Accessible label. If provided, the SVG gets
25+
* role="img" and aria-label. If omitted, the SVG
26+
* gets aria-hidden="true" and focusable="false".
27+
* Default empty string.
28+
* }
29+
* @return string SVG markup for the icon, or empty string if not found.
30+
*/
31+
function wp_get_icon( $name, $args = array() ) {
32+
$icon = WP_Icons_Registry::get_instance()->get_registered_icon( $name );
33+
if ( is_null( $icon ) ) {
34+
return '';
35+
}
36+
37+
$svg = $icon['content'];
38+
if ( empty( $svg ) ) {
39+
return '';
40+
}
41+
42+
$args = wp_parse_args(
43+
$args,
44+
array(
45+
'size' => 24,
46+
'class' => '',
47+
'label' => '',
48+
)
49+
);
50+
51+
$processor = new WP_HTML_Tag_Processor( $svg );
52+
if ( ! $processor->next_tag( 'svg' ) ) {
53+
return '';
54+
}
55+
56+
if ( is_numeric( $args['size'] ) ) {
57+
$size = absint( $args['size'] );
58+
$processor->set_attribute( 'width', (string) $size );
59+
$processor->set_attribute( 'height', (string) $size );
60+
}
61+
62+
if ( ! empty( $args['class'] ) ) {
63+
foreach ( preg_split( '/\s+/', $args['class'], -1, PREG_SPLIT_NO_EMPTY ) as $class_name ) {
64+
$processor->add_class( $class_name );
65+
}
66+
}
67+
68+
if ( ! empty( $args['label'] ) ) {
69+
$processor->set_attribute( 'role', 'img' );
70+
$processor->set_attribute( 'aria-label', $args['label'] );
71+
$processor->remove_attribute( 'aria-hidden' );
72+
$processor->remove_attribute( 'focusable' );
73+
} else {
74+
$processor->set_attribute( 'aria-hidden', 'true' );
75+
$processor->set_attribute( 'focusable', 'false' );
76+
$processor->remove_attribute( 'role' );
77+
$processor->remove_attribute( 'aria-label' );
78+
}
79+
80+
return $processor->get_updated_html();
81+
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -313,7 +313,7 @@ public function get_item_schema(): array {
313313
'additionalProperties' => true,
314314
),
315315
'public' => array(
316-
'description' => __( 'Whether the ability author intends the ability to be publicly available to clients. Only present when set by the ability author.' ),
316+
'description' => __( 'Whether the ability is meant to be available to clients such as the REST API, MCP, or AI agents. Defaults to false, but individual channel settings such as show_in_rest can override it.' ),
317317
'type' => 'boolean',
318318
),
319319
),

src/wp-settings.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,7 @@
300300
require ABSPATH . WPINC . '/class-wp-connector-registry.php';
301301
require ABSPATH . WPINC . '/connectors.php';
302302
require ABSPATH . WPINC . '/class-wp-icons-registry.php';
303+
require ABSPATH . WPINC . '/icons.php';
303304
require ABSPATH . WPINC . '/widgets.php';
304305
require ABSPATH . WPINC . '/class-wp-widget.php';
305306
require ABSPATH . WPINC . '/class-wp-widget-factory.php';

tests/phpunit/tests/abilities-api/wpAbility.php

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -312,24 +312,54 @@ public function test_meta_explicit_show_in_rest_false_wins_over_public_true() {
312312
}
313313

314314
/**
315-
* Tests that `public` metadata is not added to the stored meta when not provided.
315+
* Tests that `public` metadata defaults to false when not provided.
316316
*
317317
* @ticket 65568
318318
*/
319-
public function test_meta_public_is_not_defaulted_when_unset() {
319+
public function test_meta_public_defaults_to_false_when_unset() {
320320
$ability = new WP_Ability( self::$test_ability_name, self::$test_ability_properties );
321321

322-
$this->assertArrayNotHasKey(
322+
$this->assertArrayHasKey(
323323
'public',
324324
$ability->get_meta(),
325-
'`public` metadata should only be present when provided during registration.'
325+
'`public` metadata should always be present in the stored meta.'
326+
);
327+
$this->assertFalse(
328+
$ability->get_meta_item( 'public' ),
329+
'`public` metadata should default to false when not provided.'
326330
);
327331
$this->assertFalse(
328332
$ability->get_meta_item( 'show_in_rest' ),
329333
'`show_in_rest` metadata should still default to false.'
330334
);
331335
}
332336

337+
/**
338+
* Tests that a null `public` value is treated as unset.
339+
*
340+
* @ticket 65568
341+
*/
342+
public function test_meta_public_null_is_treated_as_unset() {
343+
$args = array_merge(
344+
self::$test_ability_properties,
345+
array(
346+
'meta' => array(
347+
'public' => null,
348+
),
349+
)
350+
);
351+
$ability = new WP_Ability( self::$test_ability_name, $args );
352+
353+
$this->assertFalse(
354+
$ability->get_meta_item( 'public' ),
355+
'A null `public` value should use the default value of false.'
356+
);
357+
$this->assertFalse(
358+
$ability->get_meta_item( 'show_in_rest' ),
359+
'`show_in_rest` should use its default value when `public` is null.'
360+
);
361+
}
362+
333363
/**
334364
* Tests that invalid `public` value throws an exception.
335365
*

tests/phpunit/tests/abilities-api/wpRegisterAbility.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,7 @@ public function test_register_valid_ability(): void {
192192
array(
193193
'annotations' => $expected_annotations,
194194
'show_in_rest' => true,
195+
'public' => false,
195196
)
196197
);
197198

0 commit comments

Comments
 (0)