Skip to content
Open
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
61 changes: 52 additions & 9 deletions src/wp-includes/abilities-api.php
Original file line number Diff line number Diff line change
Expand Up @@ -231,9 +231,21 @@
* 'show_in_rest' => false,
* ),
*
* Mark an ability as deprecated while keeping exact-name retrieval and execution
* available for backward compatibility:
*
* 'meta' => array(
* 'deprecated' => array(
* 'since' => '2.0.0',
* 'replacement' => 'my-plugin/new-ability',
* 'message' => __( 'The replacement supports the new data format.', 'my-plugin' ),
* ),
* ),
*
* @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 `deprecated` meta property.
*
* @see WP_Abilities_Registry::register()
* @see wp_register_ability_category()
Expand Down Expand Up @@ -282,6 +294,15 @@
* 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 null|array<string, string> $deprecated {
* Optional. Deprecation details. If set, mark the ability as deprecated. Deprecated abilities are hidden
* from discovery by default, but can be retrieved by exact name or explicitly included in discovery
* with the `include_deprecated` argument. Default null.
*
* @type string $since Version of the ability provider that deprecated the ability.
* @type string $replacement Optional. Namespaced ability to use instead.
* @type string $message Optional. Additional migration guidance.
* }
* @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 Expand Up @@ -416,13 +437,13 @@ function wp_get_ability( string $name ): ?WP_Ability {
/**
* Retrieves registered abilities, optionally filtered by the given arguments.
*
* When called without arguments, returns all registered abilities. When called
* with an $args array, returns only abilities that match every specified condition.
* When called without arguments, returns all registered abilities that are not deprecated. When called with an $args
* array, returns only abilities that match every specified condition.
*
* Filtering pipeline (executed in order):
*
* 1. Declarative filters (`category`, `namespace`, `meta`) — per-item, AND logic between
* arg types.
* 1. Declarative filters ( `category`, `include_deprecated`, `namespace`, `meta`) - per-item, AND logic between arg
* types.
* 2. `item_include_callback` — per-item, caller-scoped. Return true to include, false to exclude.
* 3. `wp_get_abilities_item_include` filter — per-item, ecosystem-scoped. Plugins can enforce
* universal inclusion rules regardless of what the caller passed.
Expand All @@ -433,9 +454,19 @@ function wp_get_ability( string $name ): ?WP_Ability {
*
* Examples:
*
* // All abilities (unchanged behaviour).
* // All (non-deprecated) abilities.
* $abilities = wp_get_abilities();
*
* // All abilities, including deprecated ones.
* $abilities = wp_get_abilities( array(
* 'include_deprecated' => true,
* ) );
*
* // Only deprecated abilities. An empty array matches any deprecation details.
* $abilities = wp_get_abilities( array(
* 'meta' => array( 'deprecated' => array() ),
* ) );
*
* // Filter by category.
* $abilities = wp_get_abilities( array( 'category' => 'content' ) );
*
Expand Down Expand Up @@ -475,20 +506,26 @@ 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 Deprecated abilities are excluded by default. Added the `include_deprecated` argument.
*
* @see WP_Abilities_Registry::get_all_registered()
*
* @param array $args {
* Optional. Arguments to filter the returned abilities. Default empty array (returns all).
* Optional. Arguments to filter the returned abilities. Default empty array (returns all abilities
* that are not deprecated).
*
* @type string $category Filter by category slug. Only abilities whose category
* exactly matches the given slug are included.
* @type bool $include_deprecated Whether to include deprecated abilities. Deprecated abilities
* retrieved this way do not trigger deprecation notices until
* they are executed. Default false.
* @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 ) )`.
* A `deprecated` condition implies `include_deprecated`.
* @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.
Expand All @@ -512,23 +549,29 @@ function wp_get_abilities( array $args = array() ): array {
$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();
$include_deprecated = ! empty( $args['include_deprecated'] ) || array_key_exists( 'deprecated', $meta );
$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();

foreach ( $abilities as $name => $ability ) {
// Step 1a: Filter by category.
// Step 1a: Exclude deprecated abilities unless requested via `include_deprecated` or a `deprecated` meta condition.
if ( ! $include_deprecated && null !== $ability->get_meta_item( 'deprecated' ) ) {
continue;
}

// Step 1b: Filter by category.
if ( '' !== $category && $ability->get_category() !== $category ) {
continue;
}

// Step 1b: Filter by namespace prefix.
// Step 1c: Filter by namespace prefix.
if ( '' !== $namespace && ! str_starts_with( $ability->get_name(), $namespace ) ) {
continue;
}

// Step 1c: Filter by meta key/value pairs (AND logic, supports nested arrays).
// Step 1d: Filter by meta key/value pairs (AND logic, supports nested arrays).
if ( ! empty( $meta ) && ! _wp_get_abilities_match_meta( $ability->get_meta(), $meta ) ) {
continue;
}
Expand Down
29 changes: 28 additions & 1 deletion src/wp-includes/abilities-api/class-wp-abilities-registry.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 `deprecated` meta property.
*
* @see wp_register_ability()
*
Expand Down Expand Up @@ -78,6 +79,15 @@ final class WP_Abilities_Registry {
* 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 null|array<string, string> $deprecated {
* Optional. Deprecation details. If set, mark the ability as deprecated. Deprecated abilities are hidden
* from discovery by default, but can be retrieved by exact name or explicitly included in discovery
* with the `include_deprecated` argument. Default null.
*
* @type string $since Version of the ability provider that deprecated the ability.
* @type string $replacement Optional. Namespaced ability to use instead.
* @type string $message Optional. Additional migration guidance.
* }
* @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 @@ -113,6 +123,7 @@ public function register( string $name, array $args ): ?WP_Ability {
* @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 `deprecated` meta property.
*
* @param array<string, mixed> $args {
* An associative array of arguments for the ability.
Expand All @@ -135,6 +146,15 @@ public function register( string $name, array $args ): ?WP_Ability {
* 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 null|array<string, string> $deprecated {
* Optional. Deprecation details. If set, mark the ability as deprecated. Deprecated abilities are hidden
* from discovery by default, but can be retrieved by exact name or explicitly included in discovery
* with the `include_deprecated` argument. Default null.
*
* @type string $since Version of the ability provider that deprecated the ability.
* @type string $replacement Optional. Namespaced ability to use instead.
* @type string $message Optional. Additional migration guidance.
* }
* @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 @@ -267,6 +287,7 @@ public function is_registered( string $name ): bool {
* Do not use this method directly. Instead, use the `wp_get_ability()` function.
*
* @since 6.9.0
* @since 7.2.0 Added deprecation notices for abilities with the `deprecated` meta property.
*
* @see wp_get_ability()
*
Expand All @@ -283,7 +304,13 @@ public function get_registered( string $name ): ?WP_Ability {
);
return null;
}
return $this->registered_abilities[ $name ];

$ability = $this->registered_abilities[ $name ];

// Check if the ability is deprecated and handle it accordingly.
$ability->_handle_ability_deprecation();

return $ability;
}

/**
Expand Down
Loading
Loading