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
20 changes: 16 additions & 4 deletions src/wp-includes/abilities-api.php
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,7 @@
* @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 `categories` argument.
* @since 7.2.0 Added the `required_capabilities` argument.
*
* @see WP_Abilities_Registry::register()
Expand All @@ -297,9 +298,11 @@
* @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.
* @type string $category Optional. The primary 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 list<string> $categories Optional. List of ability category slugs this ability belongs to.
* Each custom category must be registered via `wp_register_ability_category()`.
* @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.
Expand Down Expand Up @@ -544,15 +547,18 @@ 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.
* @since 7.2.0 Added the `categories` argument and updated `category` to match any assigned category.
*
* @see WP_Abilities_Registry::get_all_registered()
*
* @param array $args {
* Optional. Arguments to filter the returned abilities. Default empty array.
* Omitted filters do not restrict the results.
*
* @type string $category Optional. Filter by category slug. Only abilities whose category
* exactly matches the given slug are included.
* @type string $category Optional. Filter by category slug. Only abilities that belong to
* the given category are included.
* @type list<string> $categories Optional. Filter by category slugs. Only abilities that belong to
* at least one of the given categories are included.
* @type string $namespace Optional. Filter by ability namespace prefix. Pass the namespace
* without a trailing slash, e.g. `'woocommerce'` matches
* `'woocommerce/create-order'`.
Expand Down Expand Up @@ -585,6 +591,7 @@ function wp_get_abilities( array $args = array() ): array {
$abilities = $registry->get_all_registered();

$category = isset( $args['category'] ) && is_string( $args['category'] ) ? $args['category'] : '';
$categories = isset( $args['categories'] ) && is_array( $args['categories'] ) ? array_filter( $args['categories'], 'is_string' ) : array();
$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'] );
Expand All @@ -595,7 +602,12 @@ function wp_get_abilities( array $args = array() ): array {

foreach ( $abilities as $name => $ability ) {
// Step 1a: Filter by category.
if ( '' !== $category && $ability->get_category() !== $category ) {
if ( '' !== $category && ! $ability->has_category( $category ) ) {
continue;
}

// Step 1a2: Filter by categories.
if ( ! empty( $categories ) && empty( array_intersect( $categories, $ability->get_categories() ) ) ) {
continue;
}

Expand Down
90 changes: 67 additions & 23 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 `categories` argument and support for multiple categories.
* @since 7.2.0 Added the `required_capabilities` argument.
*
* @see wp_register_ability()
Expand All @@ -54,8 +55,9 @@ final class WP_Abilities_Registry {
*
* @type string $label The human-readable label for the ability.
* @type string $description A detailed description of what the ability does.
* @type string $category Optional. The ability category slug this ability belongs to.
* @type string $category Optional. The primary ability category slug this ability belongs to.
* Defaults to `uncategorized`.
* @type list<string> $categories Optional. The list of ability category slugs this ability belongs to.
* @type callable $execute_callback A callback function to execute when the ability is invoked.
* Receives optional mixed input and returns mixed result or WP_Error.
* @type callable $permission_callback A callback function to check permissions before execution.
Expand Down Expand Up @@ -120,15 +122,17 @@ 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 `categories` argument.
* @since 7.2.0 Added the `required_capabilities` argument.
*
* @param array<string, mixed> $args {
* An associative array of arguments for the ability.
*
* @type string $label The human-readable label for the ability.
* @type string $description A detailed description of what the ability does.
* @type string $category Optional. The ability category slug this ability belongs to.
* @type string $category Optional. The primary ability category slug this ability belongs to.
* Defaults to `uncategorized`.
* @type list<string> $categories Optional. The list of ability category slugs this ability belongs to.
* @type callable $execute_callback A callback function to execute when the ability is invoked.
* Receives optional mixed input and returns mixed result or WP_Error.
* @type callable $permission_callback A callback function to check permissions before execution.
Expand Down Expand Up @@ -156,31 +160,71 @@ public function register( string $name, array $args ): ?WP_Ability {
*/
$args = apply_filters( 'wp_register_ability_args', $args, $name );

if ( ! array_key_exists( 'category', $args ) ) {
$args['category'] = 'uncategorized';
if ( array_key_exists( 'category', $args ) ) {
if ( ! is_string( $args['category'] ) ) {
_doing_it_wrong(
__METHOD__,
__( 'Ability category must be a string.' ),
'6.9.0'
);
return null;
}
}

if ( ! is_string( $args['category'] ) ) {
_doing_it_wrong(
__METHOD__,
__( 'Ability category must be a string.' ),
'6.9.0'
);
return null;
if ( array_key_exists( 'categories', $args ) ) {
if ( ! is_array( $args['categories'] ) ) {
_doing_it_wrong(
__METHOD__,
__( 'Ability categories must be an array of strings.' ),
'7.2.0'
);
return null;
}

foreach ( $args['categories'] as $cat ) {
if ( ! is_string( $cat ) || '' === trim( $cat ) ) {
_doing_it_wrong(
__METHOD__,
__( 'Ability categories must contain only non-empty strings.' ),
'7.2.0'
);
return null;
}
}
}

if ( ! wp_has_ability_category( $args['category'] ) ) {
_doing_it_wrong(
__METHOD__,
sprintf(
/* translators: %1$s: ability category slug, %2$s: ability name */
__( 'Ability category "%1$s" is not registered. Please register the ability category before assigning it to ability "%2$s".' ),
esc_html( $args['category'] ),
esc_html( $name )
),
'6.9.0'
);
return null;
if ( isset( $args['category'] ) && isset( $args['categories'] ) ) {
$categories = array_merge( array( $args['category'] ), $args['categories'] );
} elseif ( isset( $args['category'] ) ) {
$categories = array( $args['category'] );
} elseif ( isset( $args['categories'] ) ) {
$categories = $args['categories'];
} else {
$categories = array( 'uncategorized' );
}

if ( empty( $categories ) ) {
$categories = array( 'uncategorized' );
}

$categories = array_values( array_unique( $categories ) );
$args['categories'] = $categories;
$args['category'] = $categories[0];

foreach ( $categories as $cat ) {
if ( ! wp_has_ability_category( $cat ) ) {
_doing_it_wrong(
__METHOD__,
sprintf(
/* translators: %1$s: ability category slug, %2$s: ability name */
__( 'Ability category "%1$s" is not registered. Please register the ability category before assigning it to ability "%2$s".' ),
esc_html( $cat ),
esc_html( $name )
),
'6.9.0'
);
return null;
}
}

// The class is only used to instantiate the ability, and is not a property of the ability itself.
Expand Down
81 changes: 74 additions & 7 deletions src/wp-includes/abilities-api/class-wp-ability.php
Original file line number Diff line number Diff line change
Expand Up @@ -84,13 +84,21 @@ class WP_Ability {
protected $description;

/**
* The ability category.
* The primary ability category.
*
* @since 6.9.0
* @var string
*/
protected $category;

/**
* The ability categories.
*
* @since 7.2.0
* @var list<string>
*/
protected $categories = array();

/**
* The optional ability input schema.
*
Expand Down Expand Up @@ -158,7 +166,8 @@ class WP_Ability {
*
* @type string $label The human-readable label for the ability.
* @type string $description A detailed description of what the ability does.
* @type string $category The ability category slug this ability belongs to.
* @type string $category Optional. The primary ability category slug this ability belongs to.
* @type list<string> $categories Optional. The list of ability category slugs this ability belongs to.
* @type callable $execute_callback A callback function to execute when the ability is invoked.
* Receives optional mixed input and returns mixed result or WP_Error.
* @type callable $permission_callback A callback function to check permissions before execution.
Expand Down Expand Up @@ -233,7 +242,8 @@ public function __construct( string $name, array $args ) {
*
* @type string $label The human-readable label for the ability.
* @type string $description A detailed description of what the ability does.
* @type string $category The ability category slug this ability belongs to.
* @type string $category Optional. The primary ability category slug this ability belongs to.
* @type list<string> $categories Optional. The list of ability category slugs this ability belongs to.
* @type callable $execute_callback A callback function to execute when the ability is invoked.
* Receives optional mixed input and returns mixed result or WP_Error.
* @type callable $permission_callback A callback function to check permissions before execution.
Expand Down Expand Up @@ -270,7 +280,8 @@ public function __construct( string $name, array $args ) {
*
* @type string $label The human-readable label for the ability.
* @type string $description A detailed description of what the ability does.
* @type string $category The ability category slug this ability belongs to.
* @type string $category The primary ability category slug this ability belongs to.
* @type list<string> $categories The list of ability category slugs this ability belongs to.
* @type callable $execute_callback A callback function to execute when the ability is invoked.
* Receives optional mixed input and returns mixed result or WP_Error.
* @type callable $permission_callback A callback function to check permissions before execution.
Expand Down Expand Up @@ -316,12 +327,44 @@ protected function prepare_properties( array $args ): array {
);
}

if ( empty( $args['category'] ) || ! is_string( $args['category'] ) ) {
$categories = array();

if ( isset( $args['category'] ) ) {
if ( ! is_string( $args['category'] ) || '' === trim( $args['category'] ) ) {
throw new InvalidArgumentException(
__( 'The ability properties must contain a `category` string.' )
);
}
$categories[] = trim( $args['category'] );
}

if ( isset( $args['categories'] ) ) {
if ( ! is_array( $args['categories'] ) || empty( $args['categories'] ) ) {
throw new InvalidArgumentException(
__( 'The ability properties `categories` must be a non-empty array of strings.' )
);
}

foreach ( $args['categories'] as $cat ) {
if ( ! is_string( $cat ) || '' === trim( $cat ) ) {
throw new InvalidArgumentException(
__( 'The ability properties `categories` must contain only non-empty strings.' )
);
}
$categories[] = trim( $cat );
}
}

if ( empty( $categories ) ) {
throw new InvalidArgumentException(
__( 'The ability properties must contain a `category` string.' )
);
}

$categories = array_values( array_unique( $categories ) );
$args['categories'] = $categories;
$args['category'] = $categories[0];

// If we are not overriding `ability_class` parameter during instantiation, then we need to validate the execute_callback.
if ( get_class( $this ) === self::class && ( empty( $args['execute_callback'] ) || ! is_callable( $args['execute_callback'] ) ) ) {
throw new InvalidArgumentException(
Expand Down Expand Up @@ -451,16 +494,40 @@ public function get_description(): string {
}

/**
* Retrieves the ability category for the ability.
* Retrieves the primary ability category for the ability.
*
* @since 6.9.0
*
* @return string The ability category for the ability.
* @return string The primary ability category for the ability.
*/
public function get_category(): string {
return $this->category;
}

/**
* Retrieves all ability categories for the ability.
*
* @since 7.2.0
*
* @return list<string> The ability category slugs for the ability.
*/
public function get_categories(): array {
return $this->categories;
}

/**
* Checks whether the ability belongs to a specific category.
*
* @since 7.2.0
*
* @param string $category The ability category slug to check.
* @return bool True if the ability belongs to the category, false otherwise.
*/
public function has_category( string $category ): bool {
return in_array( $category, $this->categories, true );
}


/**
* Retrieves the input schema for the ability.
*
Expand Down
Loading
Loading