Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 21 additions & 13 deletions src/wp-includes/abilities-api.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
* - Define permission checks and execution callbacks.
* - Organize abilities into logical categories.
* - Validate inputs and outputs using JSON Schema.
* - Expose abilities through the REST API.
* - Expose abilities to clients such as the REST API.
*
* ## Working with Abilities
*
Expand Down Expand Up @@ -48,7 +48,7 @@
* 'required' => true,
* ),
* 'meta' => array(
* 'show_in_rest' => true,
* 'public' => true,
* ),
* )
* );
Expand Down Expand Up @@ -118,10 +118,10 @@
* 'execute_callback' => 'my_plugin_analyze_text',
* 'permission_callback' => 'my_plugin_can_analyze_text',
* 'meta' => array(
* 'annotations' => array(
* 'annotations' => array(
* 'readonly' => true,
* ),
* 'show_in_rest' => true,
* 'public' => true,
* ),
* )
* );
Expand Down Expand Up @@ -208,16 +208,24 @@
* return current_user_can( 'edit_posts' );
* }
*
* ### REST API Integration
* ### Client Exposure
*
* Abilities can be exposed through the REST API by setting `show_in_rest`
* to `true` in the meta configuration:
* Set the high-level `public` flag to make an ability available to clients
* such as the REST API, MCP, or AI agents:
*
* 'meta' => array(
* 'show_in_rest' => true,
* 'public' => true,
* ),
*
* This allows abilities to be invoked via HTTP requests to the WordPress REST API.
* The `public` flag seeds the default for each per-channel flag. For the REST
* API it seeds `show_in_rest`, which lets the ability be invoked via HTTP
* requests. Set a per-channel flag directly to override that default. For
* example, keep a public ability out of the REST API:
*
* 'meta' => array(
* 'public' => true,
* 'show_in_rest' => false,
* ),
*
* @since 6.9.0
*
Expand Down Expand Up @@ -264,10 +272,10 @@
* @type bool|null $idempotent Optional. If true, calling the ability repeatedly with the same arguments
* will have no additional effect on its environment.
* }
* @type bool $public Optional. Whether the ability is intended to be publicly
* available to clients. When true, channel-specific exposure
* flags such as `$show_in_rest` default to true. An explicitly
* set channel flag always takes precedence.
* @type bool $public Optional. Whether the ability is meant to be available to
* clients such as the REST API, MCP, or AI agents. Seeds
* the default for per-channel flags like `$show_in_rest`.
* Defaults to false.
* @type bool $show_in_rest Optional. Whether to expose this ability in the REST API.
* When true, the ability can be invoked via HTTP requests.
* Default is the value of `$public` when set, false otherwise.
Expand Down
15 changes: 8 additions & 7 deletions src/wp-includes/abilities-api/class-wp-abilities-registry.php
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,10 @@ final class WP_Abilities_Registry {
* @type bool|null $idempotent Optional. If true, calling the ability repeatedly with the same arguments
* will have no additional effect on its environment.
* }
* @type bool $public Optional. Whether the ability is intended to be publicly
* available to clients. When true, channel-specific exposure
* flags such as `$show_in_rest` default to true. An explicitly
* set channel flag always takes precedence.
* @type bool $public Optional. Whether the ability is meant to be available
* to clients such as the REST API, MCP, or AI agents.
* Seeds the default for per-channel flags like
* `$show_in_rest`. Defaults to false.
* @type bool $show_in_rest Optional. Whether to expose this ability in the REST API.
* Default is the value of `$public` when set, false otherwise.
* }
Expand Down Expand Up @@ -125,9 +125,10 @@ public function register( string $name, array $args ): ?WP_Ability {
* Optional. Additional metadata for the ability.
*
* @type array<string, bool|string> $annotations Optional. Annotation metadata for the ability.
* @type bool $public Optional. Whether the ability is intended to be
* publicly available to clients. Seeds the default for
* channel-specific exposure flags like `$show_in_rest`.
* @type bool $public Optional. Whether the ability is meant to be
* available to clients such as the REST API, MCP, or AI
* agents. Seeds the default for per-channel flags like
* `$show_in_rest`. Defaults to false.
* @type bool $show_in_rest Optional. Whether to expose this ability in the REST API.
* Default is the value of `$public` when set, false otherwise.
* }
Expand Down
40 changes: 25 additions & 15 deletions src/wp-includes/abilities-api/class-wp-ability.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,14 @@ class WP_Ability {
*/
protected const DEFAULT_SHOW_IN_REST = false;

/**
* The default value for the `public` meta.
*
* @since 7.1.0
* @var bool
*/
protected const DEFAULT_PUBLIC = false;

/**
* The default ability annotations.
* They are not guaranteed to provide a faithful description of ability behavior.
Expand Down Expand Up @@ -160,10 +168,10 @@ class WP_Ability {
* @type bool|null $idempotent Optional. If true, calling the ability repeatedly with the same arguments
* will have no additional effect on its environment.
* }
* @type bool $public Optional. Whether the ability is intended to be publicly
* available to clients. When true, channel-specific exposure
* flags such as `$show_in_rest` default to true. An explicitly
* set channel flag always takes precedence.
* @type bool $public Optional. Whether the ability is meant to be available
* to clients such as the REST API, MCP, or AI agents.
* Seeds the default for per-channel flags like
* `$show_in_rest`. Defaults to false.
* @type bool $show_in_rest Optional. Whether to expose this ability in the REST API.
* Default is the value of `$public` when set, false otherwise.
* }
Expand Down Expand Up @@ -229,10 +237,10 @@ public function __construct( string $name, array $args ) {
* @type bool|null $idempotent Optional. If true, calling the ability repeatedly with the same arguments
* will have no additional effect on its environment.
* }
* @type bool $public Optional. Whether the ability is intended to be publicly
* available to clients. When true, channel-specific exposure
* flags such as `$show_in_rest` default to true. An explicitly
* set channel flag always takes precedence.
* @type bool $public Optional. Whether the ability is meant to be available
* to clients such as the REST API, MCP, or AI agents.
* Seeds the default for per-channel flags like
* `$show_in_rest`. Defaults to false.
* @type bool $show_in_rest Optional. Whether to expose this ability in the REST API.
* Default is the value of `$public` when set, false otherwise.
* }
Expand Down Expand Up @@ -262,8 +270,9 @@ public function __construct( string $name, array $args ) {
* @type bool|null $idempotent If true, calling the ability repeatedly with the same arguments
* will have no additional effect on its environment.
* }
* @type bool $public Whether the ability is intended to be publicly available
* to clients. Only present when provided during registration.
* @type bool $public Whether the ability is meant to be available to clients
* such as the REST API, MCP, or AI agents. Defaults to
* false.
* @type bool $show_in_rest Whether to expose this ability in the REST API.
* }
* }
Expand Down Expand Up @@ -348,17 +357,18 @@ protected function prepare_properties( array $args ): array {
)
);

$args['meta']['annotations'] = wp_parse_args(
$args['meta']['annotations'],
static::$default_annotations
);

/*
* Resolve `show_in_rest` from most specific to least specific: an explicit
* `show_in_rest` value wins, then the high-level `public` flag seeds the
* default, then the built-in default applies.
*/
$args['meta']['show_in_rest'] = $args['meta']['show_in_rest'] ?? $args['meta']['public'] ?? self::DEFAULT_SHOW_IN_REST;

$args['meta']['annotations'] = wp_parse_args(
$args['meta']['annotations'],
static::$default_annotations
);
$args['meta']['public'] = $args['meta']['public'] ?? self::DEFAULT_PUBLIC;

return $args;
}
Expand Down
12 changes: 6 additions & 6 deletions src/wp-includes/abilities.php
Original file line number Diff line number Diff line change
Expand Up @@ -132,12 +132,12 @@ function wp_register_core_abilities(): void {
return current_user_can( 'manage_options' );
},
'meta' => array(
'annotations' => array(
'annotations' => array(
'readonly' => true,
'destructive' => false,
'idempotent' => true,
),
'show_in_rest' => true,
'public' => true,
),
)
);
Expand Down Expand Up @@ -256,12 +256,12 @@ function wp_register_core_abilities(): void {
return is_user_logged_in();
},
'meta' => array(
'annotations' => array(
'annotations' => array(
'readonly' => true,
'destructive' => false,
'idempotent' => true,
),
'show_in_rest' => true,
'public' => true,
),
)
);
Expand Down Expand Up @@ -342,12 +342,12 @@ function wp_register_core_abilities(): void {
return current_user_can( 'manage_options' );
},
'meta' => array(
'annotations' => array(
'annotations' => array(
'readonly' => true,
'destructive' => false,
'idempotent' => true,
),
'show_in_rest' => true,
'public' => true,
),
)
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,7 @@ public function get_item_schema(): array {
'additionalProperties' => true,
),
'public' => array(
'description' => __( 'Whether the ability author intends the ability to be publicly available to clients. Only present when set by the ability author.' ),
'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.' ),
'type' => 'boolean',
),
),
Expand Down
38 changes: 34 additions & 4 deletions tests/phpunit/tests/abilities-api/wpAbility.php
Original file line number Diff line number Diff line change
Expand Up @@ -312,24 +312,54 @@ public function test_meta_explicit_show_in_rest_false_wins_over_public_true() {
}

/**
* Tests that `public` metadata is not added to the stored meta when not provided.
* Tests that `public` metadata defaults to false when not provided.
*
* @ticket 65568
*/
public function test_meta_public_is_not_defaulted_when_unset() {
public function test_meta_public_defaults_to_false_when_unset() {
$ability = new WP_Ability( self::$test_ability_name, self::$test_ability_properties );

$this->assertArrayNotHasKey(
$this->assertArrayHasKey(
'public',
$ability->get_meta(),
'`public` metadata should only be present when provided during registration.'
'`public` metadata should always be present in the stored meta.'
);
$this->assertFalse(
$ability->get_meta_item( 'public' ),
'`public` metadata should default to false when not provided.'
);
$this->assertFalse(
$ability->get_meta_item( 'show_in_rest' ),
'`show_in_rest` metadata should still default to false.'
);
}

/**
* Tests that a null `public` value is treated as unset.
*
* @ticket 65568
*/
public function test_meta_public_null_is_treated_as_unset() {
$args = array_merge(
self::$test_ability_properties,
array(
'meta' => array(
'public' => null,
),
)
);
$ability = new WP_Ability( self::$test_ability_name, $args );

$this->assertFalse(
$ability->get_meta_item( 'public' ),
'A null `public` value should use the default value of false.'
);
$this->assertFalse(
$ability->get_meta_item( 'show_in_rest' ),
'`show_in_rest` should use its default value when `public` is null.'
);
}

/**
* Tests that invalid `public` value throws an exception.
*
Expand Down
1 change: 1 addition & 0 deletions tests/phpunit/tests/abilities-api/wpRegisterAbility.php
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ public function test_register_valid_ability(): void {
array(
'annotations' => $expected_annotations,
'show_in_rest' => true,
'public' => false,
)
);

Expand Down
3 changes: 3 additions & 0 deletions tests/phpunit/tests/abilities-api/wpRegisterCoreAbilities.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ public function test_core_get_site_info_ability_is_registered(): void {
$ability = wp_get_ability( 'core/get-site-info' );

$this->assertInstanceOf( WP_Ability::class, $ability );
$this->assertTrue( $ability->get_meta_item( 'public', false ) );
$this->assertTrue( $ability->get_meta_item( 'show_in_rest', false ) );

$input_schema = $ability->get_input_schema();
Expand Down Expand Up @@ -203,6 +204,7 @@ public function test_core_get_user_info_ability_is_registered(): void {
$ability = wp_get_ability( 'core/get-user-info' );

$this->assertInstanceOf( WP_Ability::class, $ability );
$this->assertTrue( $ability->get_meta_item( 'public', false ) );
$this->assertTrue( $ability->get_meta_item( 'show_in_rest', false ) );

$input_schema = $ability->get_input_schema();
Expand Down Expand Up @@ -308,6 +310,7 @@ public function test_core_get_environment_info_ability_is_registered(): void {
$ability = wp_get_ability( 'core/get-environment-info' );

$this->assertInstanceOf( WP_Ability::class, $ability );
$this->assertTrue( $ability->get_meta_item( 'public', false ) );
$this->assertTrue( $ability->get_meta_item( 'show_in_rest', false ) );

$input_schema = $ability->get_input_schema();
Expand Down
Loading