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
119 changes: 119 additions & 0 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 false|array<string, string> $deprecated {
* Optional. Deprecation details. Set to an array to mark the ability as deprecated. At least one
* supported detail must be provided. Deprecated abilities remain available by exact name and can
* be explicitly included or excluded from discovery through meta filtering. Default false.
Comment on lines +297 to +300

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason to default this to false? Considering we support WP7.4+, IMO much better to make this nullable, so a signature can strict-type it as : ?array since union return types aren't supported until 8.0.

Suggested change
* @type false|array<string, string> $deprecated {
* Optional. Deprecation details. Set to an array to mark the ability as deprecated. At least one
* supported detail must be provided. Deprecated abilities remain available by exact name and can
* be explicitly included or excluded from discovery through meta filtering. Default false.
* @type null|array<string, string> $deprecated {
* Optional. Deprecation details. Set to an array to mark the ability as deprecated. At least one
* supported detail must be provided. Deprecated abilities remain available by exact name and can
* be explicitly included or excluded from discovery through meta filtering. Default null.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unless it's an important "WordPress way" thing, I agree with returning null instead of false.

*
* @type string $since Optional. 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 @@ -413,6 +434,83 @@ function wp_get_ability( string $name ): ?WP_Ability {
return $registry->get_registered( $name );
}

/**
* Marks an ability as deprecated and informs when it has been used.
*
* There is a {@see 'deprecated_ability_run'} hook that will be called that can be used
* to get the backtrace up to what code executed the deprecated ability.
*
* The current behavior is to trigger a user error if `WP_DEBUG` is true.
*
* @since 7.2.0
*
* @param string $ability_name The ability that was executed.
* @param string $version Optional. The version of the ability provider that deprecated the ability.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we decide we do need an _deprecated_ability(), let's take inspiration from the other _deprecated_*() functions and make $version required.

Suggested change
* @param string $version Optional. The version of the ability provider that deprecated the ability.
* @param string $version The version of the ability provider that deprecated the ability.

@JasonTheAdams JasonTheAdams Sep 7, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the importance of including a version here outside of consistency with other deprecation methods? Do we like that it's required there?

@justlevine justlevine Sep 8, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A version tells the end user when the behaviour they were previously relying on changed, which makes it easier for them to find out what changed (this is helpful for humans, but a requirement for AI where the training data is often outdated).

Unlike a replacement (there might not be one) or a message (might not need one), there's always a version where the change is made, so it's low-effort to add, which makes the friction of needing to explicitly pass an empty string (if you really don't want to disclose what version you deprecated something) a good thing.

Lastly, going from non-optional arg to optional is a non-breaking change, so if in the future there's a concrete reason to knock down any Chesterton Fences that determined $version should be a requirement elsewhere, we can always change to $version = '' later. We can go from optional to non-optional if the method is private (not even protected, let alone a global as it is now).

* Default empty string.
* @param string $replacement Optional. The ability that should be used instead. Default empty string.
* @param string $message Optional. Additional migration guidance. Default empty string.
*/
function _deprecated_ability( string $ability_name, string $version = '', string $replacement = '', string $message = '' ): void {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Why do we need a _deprecated_ability() at all? Is there
    • a reason not to map to the existing _deprecated_function() and _deprecated_argument()?
    • a reason expose it to the global namespace instead of keeping it as a private implementation detail of WP_Ability? Or phrased differently: when would userland code call _deprecated_ability() directly?
  2. If we do need a specific, global _deprecated_ability() function, I'd recommend we colocate it in wp-includes/functions.php with the other _deprecated_*() functions.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with the location, since they're all in the same place. I think having a distinct method makes sense as we're calling Abilities a primitive, so I think having a primitive deprecation function makes sense.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think having a distinct method makes sense as we're calling Abilities a primitive, so I think having a primitive deprecation function makes sense.

Can you clarify this point? The function as written and implemented is should never be called by the end user, but is only meant to be called internally by our WP_Ability class. The existence of the class (or a private method on it) doesn't make it any less of a primitive.

So we if don't want anyone calling the function directly:

  1. Why would we pollute the global namespace with it?
  2. Why would we make it self-descriptive to a bunch of functions that are meant to be called by the end user?

(Prolly also worthwhile to note that there's no _deprecated_block() function for the same reasons as above; deprecations are defined via args)

/**
* Fires when a deprecated ability is executed.
*
* @since 7.2.0
*
* @param string $ability_name The ability that was executed.
* @param string $replacement The ability that should be used as a replacement.
* @param string $version The version of the ability provider that deprecated the ability.
* @param string $message Additional migration guidance.
*/
do_action( 'deprecated_ability_run', $ability_name, $replacement, $version, $message );

/**
* Filters whether to trigger an error for deprecated abilities.
*
* @since 7.2.0
*
* @param bool $trigger Whether to trigger the error for deprecated abilities. Default true.
*/
if ( WP_DEBUG && apply_filters( 'deprecated_ability_trigger_error', true ) ) {
if ( $version ) {
if ( $replacement ) {
$notice = sprintf(
/* translators: 1: Ability name, 2: Version number, 3: Alternative ability name. */
__( 'Ability %1$s is <strong>deprecated</strong> since version %2$s! Use %3$s instead.' ),
$ability_name,
$version,
$replacement
);
} else {
$notice = sprintf(
/* translators: 1: Ability name, 2: Version number. */
__( 'Ability %1$s is <strong>deprecated</strong> since version %2$s with no alternative available.' ),
$ability_name,
$version
);
}
} elseif ( $replacement ) {
$notice = sprintf(
/* translators: 1: Ability name, 2: Alternative ability name. */
__( 'Ability %1$s is <strong>deprecated</strong>! Use %2$s instead.' ),
$ability_name,
$replacement
);
} else {
$notice = sprintf(
/* translators: %s: Ability name. */
__( 'Ability %s is <strong>deprecated</strong> with no alternative available.' ),
$ability_name
);
}

if ( $message ) {
$notice .= ' ' . $message;
}

wp_trigger_error( '', $notice, E_USER_DEPRECATED );
}
}

/**
* Retrieves registered abilities, optionally filtered by the given arguments.
*
Expand All @@ -436,6 +534,17 @@ function wp_get_ability( string $name ): ?WP_Ability {
* // All abilities (unchanged behaviour).
* $abilities = wp_get_abilities();
*
* // Exclude deprecated abilities explicitly.
* $abilities = wp_get_abilities( array(
* 'meta' => array( 'deprecated' => false ),
* ) );
*
* // Return only deprecated abilities. Passing `true` matches any deprecated
* // ability. An array of details narrows the results further.
* $abilities = wp_get_abilities( array(
* 'meta' => array( 'deprecated' => true ),
* ) );
*
* // Filter by category.
* $abilities = wp_get_abilities( array( 'category' => 'content' ) );
*
Expand Down Expand Up @@ -475,6 +584,7 @@ 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 support for filtering by the `deprecated` meta property.
*
* @see WP_Abilities_Registry::get_all_registered()
*
Expand Down Expand Up @@ -515,6 +625,15 @@ function wp_get_abilities( array $args = array() ): 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;

/*
* Normalize the `deprecated` meta filter shorthand. Stored values are `false`
* or an array of details, so `true` becomes an empty set of conditions that
* matches any deprecated ability.
*/
if ( isset( $meta['deprecated'] ) && true === $meta['deprecated'] ) {
$meta['deprecated'] = array();
}

Comment on lines 627 to +636

@justlevine justlevine Sep 6, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noting:

  • The Fable-generated doc block is neither helpful (more than half of it is self-documenting), nor follows either 80ch or 120ch standards for breaks.
  • The Fable-generated code block right below it doesn't adhere to the code-patterns used right above it for normalization either.

If this is behaviorally intentional, I'd make it an implementation detail of _wp_get_abilities_match_meta(), to keep the functionality self-contained. E.g.:

function _wp_get_abilities_match_meta( array $meta, array $conditions ): bool {
	foreach ( $conditions as $key => $value ) {
		if ( ! array_key_exists( $key, $meta ) ) {
			return false;
		}
		
		// Support `deprecated: true` as a shorthand to match all deprecated abilities.
		if (  'deprecated' === `$key` && true === $value ) {
			$value = [];
		} 

		... rest of function

However, since those are telltale signs of AI generated-code lacking intentionality, I'm unclear as to how much of the rest of the implementation and use of _wp_get_abilities_match_meta() is intentional design vs AI-driven.

Behaviorally, I'd assume the following holistic shape when querying for abilities:

  1. By default, deprecated abilities are excluded.
  2. Users can $args['include_deprecated'] = true if they want to get ALL abilities (per their filters), including those that have been deprecated.
  3. There is no practical need to filter for only deprecated abilities. If someone wants to do that, they can array_filter( $abilities_including_deprecated, 'my_only_deprecated_abilities_callback' ) on the results themselves, or even use the item_include_callback.
  4. There is definitely no need to explicitly support filtering by a specific meta['deprecated']['message', 'version', 'replacement'] value. If someone really wants to do that, they can do it for free (without us growing the API) using the existing meta filter, or again with item_include_callback or by manually filtering the results.

If there's human-led reasons for the current approach, I'd love to hear them and dive in to the discrepancies. If it's just "AI Slop" and lacking human intentionality, then 👆 is what I recommend we align to.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cc: @gziolo (updated, hope that makes my concerns + intent clearer)

$matched = array();

foreach ( $abilities as $name => $ability ) {
Expand Down
20 changes: 20 additions & 0 deletions 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 false|array<string, string> $deprecated {
* Optional. Deprecation details. Set to an array to mark the ability as deprecated. At least one
* supported detail must be provided. Deprecated abilities remain available by exact name and can
* be explicitly included or excluded from discovery through meta filtering. Default false.
*
* @type string $since Optional. 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.
* }
Comment on lines +82 to +90

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Per #10507 (comment),

Suggested change
* @type false|array<string, string> $deprecated {
* Optional. Deprecation details. Set to an array to mark the ability as deprecated. At least one
* supported detail must be provided. Deprecated abilities remain available by exact name and can
* be explicitly included or excluded from discovery through meta filtering. Default false.
*
* @type string $since Optional. 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 null|array<string, string> $deprecated {
* Optional. Deprecation details. Set to an array to mark the ability as deprecated. At least one
* supported detail must be provided. Deprecated abilities remain available by exact name and can
* be explicitly included or excluded from discovery through meta filtering. 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 false|array<string, string> $deprecated {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

* Optional. Deprecation details. Set to an array to mark the ability as deprecated. At least one
* supported detail must be provided. Deprecated abilities remain available by exact name and can
* be explicitly included or excluded from discovery through meta filtering. Default false.
*
* @type string $since Optional. 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
109 changes: 95 additions & 14 deletions src/wp-includes/abilities-api/class-wp-ability.php
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ class WP_Ability {
*
* @since 6.9.0
* @since 7.1.0 Added the `public` meta argument.
* @since 7.2.0 Added the `deprecated` meta property.
*
* @see wp_register_ability()
*
Expand Down Expand Up @@ -169,11 +170,20 @@ 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 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.
* @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 false|array<string, string> $deprecated {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

* Optional. Deprecation details. Set to an array to mark the ability as deprecated. At least one
* supported detail must be provided. Deprecated abilities remain available by exact name and can
* be explicitly included or excluded from discovery through meta filtering. Default false.
*
* @type string $since Optional. 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 @@ -211,6 +221,7 @@ public function __construct( string $name, array $args ) {
*
* @since 6.9.0
* @since 7.1.0 Added the `public` meta argument.
* @since 7.2.0 Added support for the `deprecated` meta property.
*
* @see WP_Abilities_Registry::register()
*
Expand Down Expand Up @@ -239,11 +250,20 @@ 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 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.
* @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 false|array<string, string> $deprecated {
* Optional. Deprecation details. Set to an array to mark the ability as deprecated. At least one
* supported detail must be provided. Deprecated abilities remain available by exact name and can
* be explicitly included or excluded from discovery through meta filtering. Default false.
*
* @type string $since Optional. 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 @@ -272,10 +292,17 @@ 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 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.
* @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 false|array<string, string> $deprecated {
* Deprecation details, or false when the ability is not deprecated.
*
* @type string $since Optional. Version of the ability provider that deprecated the ability.
* @type string $replacement Optional. Namespaced ability to use instead.
Comment thread
gziolo marked this conversation as resolved.
* @type string $message Optional. Additional migration guidance.
* }
* @type bool $show_in_rest Whether to expose this ability in the REST API.
* }
* }
* @throws InvalidArgumentException if an argument is invalid.
Expand Down Expand Up @@ -351,14 +378,57 @@ protected function prepare_properties( array $args ): array {
);
}

if ( isset( $args['meta']['deprecated'] ) && false !== $args['meta']['deprecated'] ) {
if ( ! is_array( $args['meta']['deprecated'] ) ) {
throw new InvalidArgumentException(
__( 'The ability meta should provide `deprecated` as false or an array of deprecation details.' )
);
}

$has_deprecation_details = false;
foreach ( array( 'since', 'replacement', 'message' ) as $key ) {
if ( ! array_key_exists( $key, $args['meta']['deprecated'] ) ) {
continue;
}

if ( ! is_string( $args['meta']['deprecated'][ $key ] ) || '' === $args['meta']['deprecated'][ $key ] ) {
throw new InvalidArgumentException(
sprintf(
/* translators: %s: Deprecation metadata key. */
__( 'The ability deprecation `%s` value should be a non-empty string.' ),
$key
)
);
}

if ( 'replacement' === $key && ! preg_match( '/^[a-z0-9-]+\/[a-z0-9-]+$/', $args['meta']['deprecated'][ $key ] ) ) {
throw new InvalidArgumentException(
__( 'The ability deprecation `replacement` value should be a namespaced ability name, i.e. "my-plugin/my-ability". It can only contain lowercase alphanumeric characters, dashes and the forward slash.' )
);
}
Comment on lines +404 to +408

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please revert this. Even beyond #10507 (comment), it's a waste of regex. At least a ! wp_has_ability() would be nonspeculative, but even that is IMO overkill for a deprecation array.


$has_deprecation_details = true;
}

if ( ! $has_deprecation_details ) {
throw new InvalidArgumentException(
__( 'The ability deprecation details should provide at least one of `since`, `replacement`, or `message`.' )
);
}
}

// Set defaults for optional meta.
$args['meta'] = wp_parse_args(
$args['meta'] ?? array(),
array(
'annotations' => static::$default_annotations,
'deprecated' => false,
)
);

// Treat a null `deprecated` value as unset.
$args['meta']['deprecated'] = $args['meta']['deprecated'] ?? false;

$args['meta']['annotations'] = wp_parse_args(
$args['meta']['annotations'],
static::$default_annotations
Expand Down Expand Up @@ -767,6 +837,7 @@ protected function validate_output( $output ) {
* @since 6.9.0
* @since 7.1.0 Added the `wp_ability_invoked` action.
* @since 7.1.0 Added the `wp_pre_execute_ability` filter.
* @since 7.2.0 Added deprecation notices for abilities with the `deprecated` meta property.
*
* @param mixed $input Optional. The input data for the ability. Default `null`.
* @return mixed|WP_Error The result of the ability execution, or WP_Error on failure.
Expand All @@ -787,6 +858,16 @@ public function execute( $input = null ) {
*/
do_action( 'wp_ability_invoked', $this->name, $input, $this );

$deprecated = $this->get_meta_item( 'deprecated', false );

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Should the deprecation come before the do_action() call too? I'm leaning toward yes, in line with do_action_deprecated() and my general assumption regarding WP lifecycle.

  2. Bigger question, is ::execute() the right time to log the deprecation, or should it be when it's retrieved from the registry, somewhere else instead/additionally?

    I'm not entirely sure where the best place would be, just that ::execute() feels too late: Abilities are a developer API, which means that the developer should be warned that they're reaching for a deprecated ability when they reach for it (the ability itself, if trying to introspect the input/output schema, when perms are run, or something), and not just downstream when their users are executing it. Ideally we'd find the single earliest spot of ensured interaction over repeat warnings in the same lifecycle. 🤔

if ( is_array( $deprecated ) ) {
_deprecated_ability(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Per #10507 (comment), I'm seeing no reason why _deprecated_ability()can't just beprivate function log_deprecated_ability(): void` instead of polluting the global namespace with something that seems should only be called by us in this part of the lifecycle.

$this->name,
$deprecated['since'] ?? '',
$deprecated['replacement'] ?? '',
$deprecated['message'] ?? ''
);
}

$pre_execute_sentinel = new WP_Filter_Sentinel();

/**
Expand Down
Loading
Loading