Commit 89639c4cc1 for wordpress.org
commit 89639c4cc13a8740442a1314b734bd22ab4dd354
Author: gziolo <gziolo@git.wordpress.org>
Date: Mon Oct 19 11:15:50 2026 +0000
Abilities API: Allow declaring required capabilities
Add `required_capabilities` to ability registration and expose it in REST responses. Check these capabilities before permission callbacks, allowing the callback to be omitted when capabilities are declared. Add `has_required_capabilities` filtering to `wp_get_abilities()` and the REST collection endpoint without running permission callbacks.
Developed in: https://github.com/WordPress/wordpress-develop/pull/14335
Props ovidiu-galatan.
Fixes #66325.
Built from https://develop.svn.wordpress.org/trunk@64500
git-svn-id: http://core.svn.wordpress.org/trunk@63650 1a063a9b-81f0-0310-95a4-ce76da25c4cd
diff --git a/wp-includes/abilities-api.php b/wp-includes/abilities-api.php
index abd3684273..36c1af8a07 100644
--- a/wp-includes/abilities-api.php
+++ b/wp-includes/abilities-api.php
@@ -29,25 +29,23 @@
* wp_register_ability(
* 'my-plugin/export-users',
* array(
- * 'label' => __( 'Export Users', 'my-plugin' ),
- * 'description' => __( 'Exports user data to CSV format.', 'my-plugin' ),
- * 'category' => 'data-export',
- * 'execute_callback' => 'my_plugin_export_users',
- * 'permission_callback' => function(): bool {
- * return current_user_can( 'export' );
- * },
- * 'input_schema' => array(
+ * 'label' => __( 'Export Users', 'my-plugin' ),
+ * 'description' => __( 'Exports user data to CSV format.', 'my-plugin' ),
+ * 'category' => 'data-export',
+ * 'execute_callback' => 'my_plugin_export_users',
+ * 'required_capabilities' => array( 'export' ),
+ * 'input_schema' => array(
* 'type' => 'string',
* 'enum' => array( 'subscriber', 'contributor', 'author', 'editor', 'administrator' ),
* 'description' => __( 'Limits the export to users with this role.', 'my-plugin' ),
* 'required' => false,
* ),
- * 'output_schema' => array(
+ * 'output_schema' => array(
* 'type' => 'string',
* 'description' => __( 'User data in CSV format.', 'my-plugin' ),
* 'required' => true,
* ),
- * 'meta' => array(
+ * 'meta' => array(
* 'public' => true,
* ),
* )
@@ -234,6 +232,7 @@ declare( strict_types = 1 );
* @since 6.9.0
* @since 7.1.0 Added the `public` meta argument.
* @since 7.2.0 The `category` argument is now optional and defaults to `uncategorized`.
+ * @since 7.2.0 Added the `required_capabilities` argument.
*
* @see WP_Abilities_Registry::register()
* @see wp_register_ability_category()
@@ -245,27 +244,34 @@ declare( strict_types = 1 );
* @param array<string, mixed> $args {
* An associative array of arguments for configuring the ability.
*
- * @type string $label Required. The human-readable label for the ability.
- * @type string $description Required. A detailed description of what the ability does
- * and when it should be used.
- * @type string $category Optional. The ability category slug this ability belongs to.
- * Defaults to `uncategorized`. Custom categories must be registered
- * via `wp_register_ability_category()` before registering the ability.
- * @type callable $execute_callback Required. A callback function to execute when the ability is invoked.
- * Receives optional mixed input data and must return either a result
- * value (any type) or a `WP_Error` object on failure.
- * @type callable $permission_callback Required. A callback function to check permissions before execution.
- * Receives optional mixed input data (same as `execute_callback`) and
- * must return `true`/`false` for simple checks, or `WP_Error` for
- * detailed error responses.
- * @type array<string, mixed> $input_schema Optional. JSON Schema definition for validating the ability's input.
- * Must be a valid JSON Schema object defining the structure and
- * constraints for input data. Used for automatic validation and
- * API documentation.
- * @type array<string, mixed> $output_schema Optional. JSON Schema definition for the ability's output.
- * Describes the structure of successful return values from
- * `execute_callback`. Used for documentation and validation.
- * @type array<string, mixed> $meta {
+ * @type string $label Required. The human-readable label for the ability.
+ * @type string $description Required. A detailed description of what the ability does
+ * and when it should be used.
+ * @type string $category Optional. The ability category slug this ability belongs to.
+ * Defaults to `uncategorized`. Custom categories must be registered
+ * via `wp_register_ability_category()` before registering the ability.
+ * @type callable $execute_callback Required. A callback function to execute when the ability is invoked.
+ * Receives optional mixed input data and must return either a result
+ * value (any type) or a `WP_Error` object on failure.
+ * @type callable $permission_callback Required unless `$required_capabilities` lists at least one capability.
+ * A callback function to check permissions before execution.
+ * Receives optional mixed input data (same as `execute_callback`) and
+ * must return `true`/`false` for simple checks, or `WP_Error` for
+ * detailed error responses. To let everyone run the ability,
+ * use `__return_true`. The `public` meta flag doesn't affect
+ * this. It only controls where the ability is shown.
+ * @type list<string> $required_capabilities Optional. Capabilities the current user must have to run the
+ * ability. Each one is checked with `current_user_can()` before
+ * `permission_callback` runs. Use it for checks that don't depend
+ * on the input. Default empty array.
+ * @type array<string, mixed> $input_schema Optional. JSON Schema definition for validating the ability's input.
+ * Must be a valid JSON Schema object defining the structure and
+ * constraints for input data. Used for automatic validation and
+ * API documentation.
+ * @type array<string, mixed> $output_schema Optional. JSON Schema definition for the ability's output.
+ * Describes the structure of successful return values from
+ * `execute_callback`. Used for documentation and validation.
+ * @type array<string, mixed> $meta {
* Optional. Additional metadata for the ability.
*
* @type array<string, bool|null> $annotations {
@@ -477,26 +483,32 @@ function wp_get_ability( string $name ): ?WP_Ability {
*
* @since 6.9.0
* @since 7.1.0 Added the `$args` parameter for filtering support.
+ * @since 7.2.0 Added the `has_required_capabilities` argument.
*
* @see WP_Abilities_Registry::get_all_registered()
*
* @param array $args {
* Optional. Arguments to filter the returned abilities. Default empty array (returns all).
*
- * @type string $category Filter by category slug. Only abilities whose category
- * exactly matches the given slug are included.
- * @type string $namespace Filter by ability namespace prefix. Pass the namespace
- * without a trailing slash, e.g. `'woocommerce'` matches
- * `'woocommerce/create-order'`.
- * @type array $meta Filter by meta key/value pairs. All conditions must
- * match (AND logic). Supports nested arrays for structured
- * meta, e.g. `array( 'mcp' => array( 'public' => true ) )`.
- * @type callable $item_include_callback Optional. A callback invoked per ability after declarative
- * filters. Receives a WP_Ability instance, returns bool.
- * Return true to include, false to exclude.
- * @type callable $result_callback Optional. A callback invoked once on the full matched
- * array. Receives WP_Ability[], must return WP_Ability[].
- * Use for sorting, slicing, or reshaping the result.
+ * @type string $category Filter by category slug. Only abilities whose category
+ * exactly matches the given slug are included.
+ * @type string $namespace Filter by ability namespace prefix. Pass the namespace
+ * without a trailing slash, e.g. `'woocommerce'` matches
+ * `'woocommerce/create-order'`.
+ * @type array $meta Filter by meta key/value pairs. All conditions must
+ * match (AND logic). Supports nested arrays for structured
+ * meta, e.g. `array( 'mcp' => array( 'public' => true ) )`.
+ * @type bool $has_required_capabilities Whether to leave out abilities whose required capabilities
+ * the current user doesn't have. Abilities without required
+ * capabilities are always included. Permission callbacks
+ * and filters don't run, so an included ability can still
+ * deny execution. Default false.
+ * @type callable $item_include_callback Optional. A callback invoked per ability after declarative
+ * filters. Receives a WP_Ability instance, returns bool.
+ * Return true to include, false to exclude.
+ * @type callable $result_callback Optional. A callback invoked once on the full matched
+ * array. Receives WP_Ability[], must return WP_Ability[].
+ * Use for sorting, slicing, or reshaping the result.
* }
* @return WP_Ability[] An array of registered WP_Ability instances matching the given args,
* keyed by ability name. Returns an empty array if no abilities are
@@ -511,11 +523,12 @@ function wp_get_abilities( array $args = array() ): array {
$abilities = $registry->get_all_registered();
- $category = isset( $args['category'] ) && is_string( $args['category'] ) ? $args['category'] : '';
- $namespace = isset( $args['namespace'] ) && is_string( $args['namespace'] ) ? rtrim( $args['namespace'], '/' ) . '/' : '';
- $meta = isset( $args['meta'] ) && is_array( $args['meta'] ) ? $args['meta'] : array();
- $item_include_callback = isset( $args['item_include_callback'] ) && is_callable( $args['item_include_callback'] ) ? $args['item_include_callback'] : null;
- $result_callback = isset( $args['result_callback'] ) && is_callable( $args['result_callback'] ) ? $args['result_callback'] : null;
+ $category = isset( $args['category'] ) && is_string( $args['category'] ) ? $args['category'] : '';
+ $namespace = isset( $args['namespace'] ) && is_string( $args['namespace'] ) ? rtrim( $args['namespace'], '/' ) . '/' : '';
+ $meta = isset( $args['meta'] ) && is_array( $args['meta'] ) ? $args['meta'] : array();
+ $has_required_capabilities = ! empty( $args['has_required_capabilities'] );
+ $item_include_callback = isset( $args['item_include_callback'] ) && is_callable( $args['item_include_callback'] ) ? $args['item_include_callback'] : null;
+ $result_callback = isset( $args['result_callback'] ) && is_callable( $args['result_callback'] ) ? $args['result_callback'] : null;
$matched = array();
@@ -535,6 +548,15 @@ function wp_get_abilities( array $args = array() ): array {
continue;
}
+ // Step 1d: Leave out abilities whose required capabilities the current user doesn't have.
+ if ( $has_required_capabilities ) {
+ foreach ( $ability->get_required_capabilities() as $capability ) {
+ if ( ! current_user_can( $capability ) ) {
+ continue 2;
+ }
+ }
+ }
+
// Step 2: Caller-scoped per-item callback.
$include = true;
if ( null !== $item_include_callback ) {
diff --git a/wp-includes/abilities-api/class-wp-abilities-registry.php b/wp-includes/abilities-api/class-wp-abilities-registry.php
index 90a2349a89..6790ed8535 100644
--- a/wp-includes/abilities-api/class-wp-abilities-registry.php
+++ b/wp-includes/abilities-api/class-wp-abilities-registry.php
@@ -42,6 +42,7 @@ final class WP_Abilities_Registry {
* @since 6.9.0
* @since 7.1.0 Added the `public` meta argument.
* @since 7.2.0 The `category` argument is now optional and defaults to `uncategorized`.
+ * @since 7.2.0 Added the `required_capabilities` argument.
*
* @see wp_register_ability()
*
@@ -59,6 +60,9 @@ final class WP_Abilities_Registry {
* Receives optional mixed input and returns mixed result or WP_Error.
* @type callable $permission_callback A callback function to check permissions before execution.
* Receives optional mixed input and returns bool or WP_Error.
+ * Required unless `$required_capabilities` lists at least one capability.
+ * @type list<string> $required_capabilities Optional. Capabilities the current user must have to run the
+ * ability. They are checked before `$permission_callback`.
* @type array<string, mixed> $input_schema Optional. JSON Schema definition for the ability's input.
* @type array<string, mixed> $output_schema Optional. JSON Schema definition for the ability's output.
* @type array<string, mixed> $meta {
@@ -115,6 +119,7 @@ final class WP_Abilities_Registry {
* @since 6.9.0
* @since 7.1.0 Added the `public` meta argument.
* @since 7.2.0 The `category` argument is now optional and defaults to `uncategorized`.
+ * @since 7.2.0 Added the `required_capabilities` argument.
*
* @param array<string, mixed> $args {
* An associative array of arguments for the ability.
@@ -127,6 +132,9 @@ final class WP_Abilities_Registry {
* Receives optional mixed input and returns mixed result or WP_Error.
* @type callable $permission_callback A callback function to check permissions before execution.
* Receives optional mixed input and returns bool or WP_Error.
+ * Required unless `$required_capabilities` lists at least one capability.
+ * @type list<string> $required_capabilities Optional. Capabilities the current user must have to run the
+ * ability. They are checked before `$permission_callback`.
* @type array<string, mixed> $input_schema Optional. JSON Schema definition for the ability's input.
* @type array<string, mixed> $output_schema Optional. JSON Schema definition for the ability's output.
* @type array<string, mixed> $meta {
diff --git a/wp-includes/abilities-api/class-wp-ability.php b/wp-includes/abilities-api/class-wp-ability.php
index 88853f6496..c7e32ad8ef 100644
--- a/wp-includes/abilities-api/class-wp-ability.php
+++ b/wp-includes/abilities-api/class-wp-ability.php
@@ -123,6 +123,14 @@ class WP_Ability {
*/
protected $permission_callback;
+ /**
+ * The optional capabilities the current user must have to run the ability.
+ *
+ * @since 7.2.0
+ * @var list<string>
+ */
+ protected $required_capabilities = array();
+
/**
* The optional ability metadata.
*
@@ -140,6 +148,7 @@ class WP_Ability {
*
* @since 6.9.0
* @since 7.1.0 Added the `public` meta argument.
+ * @since 7.2.0 Added the `required_capabilities` argument.
*
* @see wp_register_ability()
*
@@ -154,6 +163,9 @@ class WP_Ability {
* Receives optional mixed input and returns mixed result or WP_Error.
* @type callable $permission_callback A callback function to check permissions before execution.
* Receives optional mixed input and returns bool or WP_Error.
+ * Required unless `$required_capabilities` lists at least one capability.
+ * @type list<string> $required_capabilities Optional. Capabilities the current user must have to run the
+ * ability. They are checked before `$permission_callback`.
* @type array<string, mixed> $input_schema Optional. JSON Schema definition for the ability's input.
* @type array<string, mixed> $output_schema Optional. JSON Schema definition for the ability's output.
* @type array<string, mixed> $meta {
@@ -211,6 +223,7 @@ class WP_Ability {
*
* @since 6.9.0
* @since 7.1.0 Added the `public` meta argument.
+ * @since 7.2.0 Added the `required_capabilities` argument.
*
* @see WP_Abilities_Registry::register()
*
@@ -224,6 +237,9 @@ class WP_Ability {
* Receives optional mixed input and returns mixed result or WP_Error.
* @type callable $permission_callback A callback function to check permissions before execution.
* Receives optional mixed input and returns bool or WP_Error.
+ * Required unless `$required_capabilities` lists at least one capability.
+ * @type list<string> $required_capabilities Optional. Capabilities the current user must have to run the
+ * ability. They are checked before `$permission_callback`.
* @type array<string, mixed> $input_schema Optional. JSON Schema definition for the ability's input. Required if ability accepts an input.
* @type array<string, mixed> $output_schema Optional. JSON Schema definition for the ability's output.
* @type array<string, mixed> $meta {
@@ -257,6 +273,9 @@ class WP_Ability {
* Receives optional mixed input and returns mixed result or WP_Error.
* @type callable $permission_callback A callback function to check permissions before execution.
* Receives optional mixed input and returns bool or WP_Error.
+ * Required unless `$required_capabilities` lists at least one capability.
+ * @type list<string> $required_capabilities Optional. Capabilities the current user must have to run the
+ * ability. They are checked before `$permission_callback`.
* @type array<string, mixed> $input_schema Optional. JSON Schema definition for the ability's input.
* @type array<string, mixed> $output_schema Optional. JSON Schema definition for the ability's output.
* @type array<string, mixed> $meta {
@@ -307,13 +326,30 @@ class WP_Ability {
);
}
- // If we are not overriding `ability_class` parameter during instantiation, then we need to validate the permission_callback.
- if ( get_class( $this ) === self::class && ( empty( $args['permission_callback'] ) || ! is_callable( $args['permission_callback'] ) ) ) {
+ if ( isset( $args['required_capabilities'] ) && ! self::is_valid_capability_list( $args['required_capabilities'] ) ) {
throw new InvalidArgumentException(
- __( 'The ability properties must provide a valid `permission_callback` function.' )
+ __( 'The ability properties should provide a valid `required_capabilities` list of non-empty strings.' )
);
}
+ /*
+ * Validate the permission_callback, unless a custom `ability_class` is used.
+ * It can be left out when the ability declares required capabilities.
+ */
+ if ( get_class( $this ) === self::class ) {
+ if ( isset( $args['permission_callback'] ) ) {
+ if ( ! is_callable( $args['permission_callback'] ) ) {
+ throw new InvalidArgumentException(
+ __( 'The ability properties must provide a valid `permission_callback` function.' )
+ );
+ }
+ } elseif ( empty( $args['required_capabilities'] ) ) {
+ throw new InvalidArgumentException(
+ __( 'The ability properties must provide a valid `permission_callback` function or a non-empty `required_capabilities` list.' )
+ );
+ }
+ }
+
// Optional args only need to be of the correct type if they are present.
if ( isset( $args['input_schema'] ) && ! is_array( $args['input_schema'] ) ) {
throw new InvalidArgumentException(
@@ -351,6 +387,8 @@ class WP_Ability {
);
}
+ $args['required_capabilities'] ??= array();
+
// Set defaults for optional meta.
$args['meta'] = wp_parse_args(
$args['meta'] ?? array(),
@@ -453,6 +491,17 @@ class WP_Ability {
return $this->meta;
}
+ /**
+ * Retrieves the capabilities the current user must have to run the ability.
+ *
+ * @since 7.2.0
+ *
+ * @return list<string> The required capabilities. Empty when the ability declares none.
+ */
+ public function get_required_capabilities(): array {
+ return $this->required_capabilities;
+ }
+
/**
* Retrieves a specific metadata item for the ability.
*
@@ -576,6 +625,27 @@ class WP_Ability {
return true;
}
+ /**
+ * Checks whether a value is a list of non-empty capability strings.
+ *
+ * @since 7.2.0
+ *
+ * @param mixed $capabilities The value to check.
+ * @return bool Whether the value is a valid list of capabilities.
+ */
+ private static function is_valid_capability_list( $capabilities ): bool {
+ if ( ! is_array( $capabilities ) || ! array_is_list( $capabilities ) ) {
+ return false;
+ }
+
+ foreach ( $capabilities as $capability ) {
+ if ( ! is_string( $capability ) || '' === trim( $capability ) ) {
+ return false;
+ }
+ }
+ return true;
+ }
+
/**
* Invokes a callable, ensuring the input is passed through only if the input schema is defined.
*
@@ -613,11 +683,19 @@ class WP_Ability {
* Please note that input is not automatically validated against the input schema.
* Use `validate_input()` method to validate input before calling this method if needed.
*
- * The {@see 'wp_ability_permission_result'} filter fires after the registered
- * `permission_callback` returns, allowing plugins to override the result.
+ * The checks run in this order:
+ *
+ * 1. When the ability declares `required_capabilities`, each one is checked with
+ * `current_user_can()`. The check fails right away if one is missing.
+ * 2. When all capabilities pass and the ability has a `permission_callback`, it runs
+ * with the given input.
+ *
+ * The {@see 'wp_ability_permission_result'} filter fires on the final result, allowing
+ * plugins to override it.
*
* @since 6.9.0
* @since 7.1.0 Added the `wp_ability_permission_result` filter.
+ * @since 7.2.0 Added the `required_capabilities` check.
*
* @see validate_input()
*
@@ -625,30 +703,50 @@ class WP_Ability {
* @return bool|WP_Error Whether the ability has the necessary permission.
*/
public function check_permissions( $input = null ) {
- if ( ! is_callable( $this->permission_callback ) ) {
+ // A permission callback that is set must be callable.
+ $is_invalid_callback = null !== $this->permission_callback && ! is_callable( $this->permission_callback );
+
+ // Without a permission callback, the ability must declare required capabilities.
+ $is_missing_check = null === $this->permission_callback && empty( $this->required_capabilities );
+
+ if ( $is_invalid_callback || $is_missing_check ) {
return new WP_Error(
'ability_invalid_permission_callback',
/* translators: %s ability name. */
- sprintf( __( 'Ability "%s" does not have a valid permission callback.' ), $this->name )
+ sprintf( __( 'Ability "%s" must provide a valid permission callback, or omit it and declare required capabilities.' ), $this->name )
);
}
- $permission = $this->invoke_callback( $this->permission_callback, $input );
+ // Both checks must pass. A check that isn't set is skipped.
+ $permission = true;
+ foreach ( $this->required_capabilities as $capability ) {
+ if ( ! current_user_can( $capability ) ) {
+ $permission = false;
+ break;
+ }
+ }
+
+ if ( $permission && null !== $this->permission_callback ) {
+ $permission = $this->invoke_callback( $this->permission_callback, $input );
+ }
/**
* Filters the result of an ability's permission check.
*
- * Fires after the registered `permission_callback` returns. Plugins can use this to layer
- * additional authorization rules on top of the ability's own permission logic — for example,
+ * Fires after the required capabilities check and the `permission_callback`. Plugins can use this
+ * to layer additional authorization rules on top of the ability's own permission logic — for example,
* multi-factor authorization gates or temporary permission elevation for trusted contexts.
*
- * Filters can return `true` to grant, `false` to deny, or a `WP_Error` to deny with a specific
- * error code and message. The filter receives whatever the `permission_callback` produced.
- * Any other return value is coerced to `false`.
+ * The filter receives `false` when a required capability is missing. Otherwise, it receives the
+ * result of `permission_callback`, or `true` when there is none. Filters can return `true` to grant,
+ * `false` to deny, or a `WP_Error` to deny with a specific error code and message. Any other return
+ * value is coerced to `false`.
*
* @since 7.1.0
+ * @since 7.2.0 Also fires with `false` when a required capability is missing.
*
- * @param bool|WP_Error $permission The permission result returned by `permission_callback`.
+ * @param bool|WP_Error $permission The result of the required capabilities check and the
+ * `permission_callback`.
* @param string $ability_name The name of the ability.
* @param mixed $input The input data for the permission check.
* @param WP_Ability $ability The ability instance.
diff --git a/wp-includes/abilities.php b/wp-includes/abilities.php
index a6a9041817..5b2e84ab3b 100644
--- a/wp-includes/abilities.php
+++ b/wp-includes/abilities.php
@@ -112,10 +112,10 @@ function wp_register_core_abilities(): void {
wp_register_ability(
'core/get-site-info',
array(
- 'label' => __( 'Get Site Information' ),
- 'description' => __( 'Returns site information configured in WordPress. By default returns all fields, or optionally a filtered subset.' ),
- 'category' => $category_site,
- 'input_schema' => array(
+ 'label' => __( 'Get Site Information' ),
+ 'description' => __( 'Returns site information configured in WordPress. By default returns all fields, or optionally a filtered subset.' ),
+ 'category' => $category_site,
+ 'input_schema' => array(
'type' => 'object',
'properties' => array(
'fields' => array(
@@ -130,12 +130,12 @@ function wp_register_core_abilities(): void {
'additionalProperties' => false,
'default' => array(),
),
- 'output_schema' => array(
+ 'output_schema' => array(
'type' => 'object',
'properties' => $site_info_properties,
'additionalProperties' => false,
),
- 'execute_callback' => static function ( $input = array() ) use ( $site_info_fields ): array {
+ 'execute_callback' => static function ( $input = array() ) use ( $site_info_fields ): array {
$input = is_array( $input ) ? $input : array();
$requested_fields = ! empty( $input['fields'] ) ? $input['fields'] : $site_info_fields;
@@ -150,10 +150,8 @@ function wp_register_core_abilities(): void {
return $result;
},
- 'permission_callback' => static function (): bool {
- return current_user_can( 'manage_options' );
- },
- 'meta' => array(
+ 'required_capabilities' => array( 'manage_options' ),
+ 'meta' => array(
'annotations' => array(
'readonly' => true,
'destructive' => false,
@@ -316,10 +314,10 @@ function wp_register_core_abilities(): void {
wp_register_ability(
'core/get-environment-info',
array(
- 'label' => __( 'Get Environment Info' ),
- 'description' => __( 'Returns core details about the site\'s runtime context for diagnostics and compatibility (environment, PHP runtime, database server info, WordPress version). By default returns all fields, or optionally a filtered subset.' ),
- 'category' => $category_site,
- 'input_schema' => array(
+ 'label' => __( 'Get Environment Info' ),
+ 'description' => __( 'Returns core details about the site\'s runtime context for diagnostics and compatibility (environment, PHP runtime, database server info, WordPress version). By default returns all fields, or optionally a filtered subset.' ),
+ 'category' => $category_site,
+ 'input_schema' => array(
'type' => 'object',
'properties' => array(
'fields' => array(
@@ -334,12 +332,12 @@ function wp_register_core_abilities(): void {
'additionalProperties' => false,
'default' => array(),
),
- 'output_schema' => array(
+ 'output_schema' => array(
'type' => 'object',
'properties' => $environment_info_properties,
'additionalProperties' => false,
),
- 'execute_callback' => static function ( $input = array() ) use ( $environment_info_fields ): array {
+ 'execute_callback' => static function ( $input = array() ) use ( $environment_info_fields ): array {
global $wpdb;
/** @var array{ fields?: string[] } $input */
@@ -360,10 +358,8 @@ function wp_register_core_abilities(): void {
return array_intersect_key( $all, array_flip( $requested_fields ) );
},
- 'permission_callback' => static function (): bool {
- return current_user_can( 'manage_options' );
- },
- 'meta' => array(
+ 'required_capabilities' => array( 'manage_options' ),
+ 'meta' => array(
'annotations' => array(
'readonly' => true,
'destructive' => false,
diff --git a/wp-includes/abilities/class-wp-abilities-settings.php b/wp-includes/abilities/class-wp-abilities-settings.php
index 953eb92ef8..3beb82570c 100644
--- a/wp-includes/abilities/class-wp-abilities-settings.php
+++ b/wp-includes/abilities/class-wp-abilities-settings.php
@@ -81,18 +81,16 @@ final class WP_Abilities_Settings {
wp_register_ability(
'core/settings-get',
array(
- 'label' => __( 'Get Settings' ),
- 'description' => __( 'Returns WordPress settings as a flat map of setting name to value. By default returns all settings exposed to abilities, or optionally a subset filtered by settings group, by setting name, or both. A setting whose value does not match its schema is left out. Requires an authenticated user who can manage options.' ),
- 'category' => self::CATEGORY,
- 'input_schema' => $this->get_settings_get_input_schema( $groups, array_map( 'strval', array_keys( $this->exposed_settings ) ) ),
- 'output_schema' => $this->get_settings_get_output_schema(),
- 'execute_callback' => function ( $input = array() ): array {
+ 'label' => __( 'Get Settings' ),
+ 'description' => __( 'Returns WordPress settings as a flat map of setting name to value. By default returns all settings exposed to abilities, or optionally a subset filtered by settings group, by setting name, or both. A setting whose value does not match its schema is left out. Requires an authenticated user who can manage options.' ),
+ 'category' => self::CATEGORY,
+ 'input_schema' => $this->get_settings_get_input_schema( $groups, array_map( 'strval', array_keys( $this->exposed_settings ) ) ),
+ 'output_schema' => $this->get_settings_get_output_schema(),
+ 'execute_callback' => function ( $input = array() ): array {
return $this->execute_settings_get( $input );
},
- 'permission_callback' => function (): bool {
- return $this->check_permission();
- },
- 'meta' => array(
+ 'required_capabilities' => array( 'manage_options' ),
+ 'meta' => array(
'annotations' => array(
'readonly' => true,
'destructive' => false,
@@ -161,20 +159,6 @@ final class WP_Abilities_Settings {
return $result;
}
- /**
- * Permission callback for the settings abilities.
- *
- * The settings abilities read site options, so they are gated on the `manage_options`
- * capability regardless of the input.
- *
- * @since 7.2.0
- *
- * @return bool True if the current user can manage options.
- */
- private function check_permission(): bool {
- return current_user_can( 'manage_options' );
- }
-
/**
* Builds the input schema for the `core/settings-get` ability: optional filters by group and/or name.
*
diff --git a/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-list-controller.php b/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-list-controller.php
index 5547e7e002..da2bb71fc5 100644
--- a/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-list-controller.php
+++ b/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-list-controller.php
@@ -103,6 +103,10 @@ class WP_REST_Abilities_V1_List_Controller extends WP_REST_Controller {
$query_args['meta'] = array_merge( $request['meta'], $query_args['meta'] );
}
+ if ( ! empty( $request['has_required_capabilities'] ) ) {
+ $query_args['has_required_capabilities'] = true;
+ }
+
$abilities = wp_get_abilities( $query_args );
$page = $request['page'];
@@ -204,13 +208,14 @@ class WP_REST_Abilities_V1_List_Controller extends WP_REST_Controller {
*/
public function prepare_item_for_response( $ability, $request ) {
$data = array(
- 'name' => $ability->get_name(),
- 'label' => $ability->get_label(),
- 'description' => $ability->get_description(),
- 'category' => $ability->get_category(),
- 'input_schema' => wp_prepare_json_schema_for_client( $ability->get_input_schema() ),
- 'output_schema' => wp_prepare_json_schema_for_client( $ability->get_output_schema() ),
- 'meta' => $ability->get_meta(),
+ 'name' => $ability->get_name(),
+ 'label' => $ability->get_label(),
+ 'description' => $ability->get_description(),
+ 'category' => $ability->get_category(),
+ 'required_capabilities' => $ability->get_required_capabilities(),
+ 'input_schema' => wp_prepare_json_schema_for_client( $ability->get_input_schema() ),
+ 'output_schema' => wp_prepare_json_schema_for_client( $ability->get_output_schema() ),
+ 'meta' => $ability->get_meta(),
);
$context = $request['context'] ?? 'view';
@@ -245,6 +250,7 @@ class WP_REST_Abilities_V1_List_Controller extends WP_REST_Controller {
*
* @since 6.9.0
* @since 7.1.0 Added the `meta.public` property.
+ * @since 7.2.0 Added the `required_capabilities` property.
*
* @return array<string, mixed> Item schema data.
*/
@@ -254,43 +260,52 @@ class WP_REST_Abilities_V1_List_Controller extends WP_REST_Controller {
'title' => 'ability',
'type' => 'object',
'properties' => array(
- 'name' => array(
+ 'name' => array(
'description' => __( 'Unique identifier for the ability.' ),
'type' => 'string',
'context' => array( 'view', 'edit', 'embed' ),
'readonly' => true,
),
- 'label' => array(
+ 'label' => array(
'description' => __( 'Display label for the ability.' ),
'type' => 'string',
'context' => array( 'view', 'edit', 'embed' ),
'readonly' => true,
),
- 'description' => array(
+ 'description' => array(
'description' => __( 'Description of the ability.' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
- 'category' => array(
+ 'category' => array(
'description' => __( 'Ability category this ability belongs to.' ),
'type' => 'string',
'context' => array( 'view', 'edit', 'embed' ),
'readonly' => true,
),
- 'input_schema' => array(
+ 'required_capabilities' => array(
+ 'description' => __( 'Capabilities the current user must have to run the ability.' ),
+ 'type' => 'array',
+ 'items' => array(
+ 'type' => 'string',
+ ),
+ 'context' => array( 'view', 'edit' ),
+ 'readonly' => true,
+ ),
+ 'input_schema' => array(
'description' => __( 'JSON Schema for the ability input.' ),
'type' => 'object',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
- 'output_schema' => array(
+ 'output_schema' => array(
'description' => __( 'JSON Schema for the ability output.' ),
'type' => 'object',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
- 'meta' => array(
+ 'meta' => array(
'description' => __( 'Meta information about the ability.' ),
'type' => 'object',
'properties' => array(
@@ -332,38 +347,39 @@ class WP_REST_Abilities_V1_List_Controller extends WP_REST_Controller {
*
* @since 6.9.0
* @since 7.1.0 Added the `namespace` and `meta` parameters and the `rest_abilities_collection_params` filter.
+ * @since 7.2.0 Added the `has_required_capabilities` parameter.
*
* @return array<string, mixed> Collection parameters.
*/
public function get_collection_params(): array {
$query_params = array(
- 'context' => $this->get_context_param( array( 'default' => 'view' ) ),
- 'page' => array(
+ 'context' => $this->get_context_param( array( 'default' => 'view' ) ),
+ 'page' => array(
'description' => __( 'Current page of the collection.' ),
'type' => 'integer',
'default' => 1,
'minimum' => 1,
),
- 'per_page' => array(
+ 'per_page' => array(
'description' => __( 'Maximum number of items to be returned in result set.' ),
'type' => 'integer',
'default' => 50,
'minimum' => 1,
'maximum' => 100,
),
- 'category' => array(
+ 'category' => array(
'description' => __( 'Limit results to abilities in specific ability category.' ),
'type' => 'string',
'sanitize_callback' => 'sanitize_key',
'validate_callback' => 'rest_validate_request_arg',
),
- 'namespace' => array(
+ 'namespace' => array(
'description' => __( 'Limit results to abilities in a specific namespace.' ),
'type' => 'string',
'sanitize_callback' => 'sanitize_key',
'validate_callback' => 'rest_validate_request_arg',
),
- 'meta' => array(
+ 'meta' => array(
'description' => __( 'Limit results to abilities matching all of the given meta fields.' ),
'type' => 'object',
'properties' => array(
@@ -390,6 +406,11 @@ class WP_REST_Abilities_V1_List_Controller extends WP_REST_Controller {
),
'additionalProperties' => true,
),
+ 'has_required_capabilities' => array(
+ 'description' => __( 'Limit results to abilities whose required capabilities the current user has. Abilities without required capabilities are always included. Permission callbacks don\'t run, so an included ability can still deny execution.' ),
+ 'type' => 'boolean',
+ 'default' => false,
+ ),
);
/**
diff --git a/wp-includes/version.php b/wp-includes/version.php
index f92988eca7..4c97486fbf 100644
--- a/wp-includes/version.php
+++ b/wp-includes/version.php
@@ -16,7 +16,7 @@
*
* @global string $wp_version
*/
-$wp_version = '7.2-alpha-64499';
+$wp_version = '7.2-alpha-64500';
/**
* Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.