Skip to content

Commit fb74380

Browse files
authored
Merge branch 'trunk' into update/version-0.2.0-preps
2 parents cda71b3 + 80f47ee commit fb74380

File tree

7 files changed

+100
-56
lines changed

7 files changed

+100
-56
lines changed

docs/1.intro.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
# 1. Introduction & Overview
22

3-
**What is the Abilities API?**
3+
## What is the Abilities API?
44

55
The WordPress Abilities API provides a standardized way to register and discover distinct units of functionality within a WordPress site. These units, called "Abilities", represent specific actions or capabilities that components can perform, with clearly defined inputs, outputs, and permissions.
66

77
It acts as a central registry, making it easier for different parts of WordPress, third-party plugins, themes, and external systems (like AI agents) to understand and interact with the capabilities available on a specific site.
88

9-
**Core Concepts**
9+
## Core Concepts
1010

1111
- **Ability:** A distinct piece of functionality with a unique name following the `namespace/ability-name` pattern. Each ability has a human-readable name and description, input/output definitions (using JSON Schema), optional permissions, and an associated callback function for execution. Each registered Ability is an instance of the `WP_Ability` class.
1212
- **Registry:** A central, singleton object (`WP_Abilities_Registry`) that holds all registered abilities. It provides methods for registering, unregistering, finding, and querying abilities.
@@ -15,7 +15,7 @@ It acts as a central registry, making it easier for different parts of WordPress
1515
- **Permission Callback:** An optional function that determines if the current user can execute a specific ability.
1616
- **Namespace:** The first part of an ability name (before the slash), typically matching the plugin or component name that registers the ability.
1717

18-
**Goals and Benefits**
18+
## Goals and Benefits
1919

2020
- **Standardization:** Provides a single, consistent way to expose site capabilities.
2121
- **Discoverability:** Makes functionality easily discoverable by AI systems and automation tools.
@@ -24,15 +24,15 @@ It acts as a central registry, making it easier for different parts of WordPress
2424
- **Extensibility:** Simple registration pattern allows any plugin or theme to expose their capabilities.
2525
- **AI-Friendly:** Machine-readable format enables intelligent automation and AI agent interactions.
2626

27-
**Use Cases**
27+
## Use Cases
2828

2929
- **AI Integration:** Allow AI agents to discover and interact with site capabilities.
3030
- **Plugin Interoperability:** Enable plugins to discover and use each other's functionality.
3131
- **Automation Tools:** Provide programmatic access to site features.
3232
- **API Documentation:** Self-documenting capabilities with schema validation.
3333
- **Developer Tools:** Standardized way to expose plugin functionality.
3434

35-
**Registration Example**
35+
## Registration Example
3636

3737
```php
3838
add_action( 'abilities_api_init', 'my_plugin_register_ability');

docs/2.getting-started.md

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@ wp plugin install https://github.com/WordPress/abilities-api/releases/latest/dow
2222
"$schema": "https://schemas.wp.org/trunk/wp-env.json",
2323
// ... other config ...
2424
"plugins": [
25-
"WordPress/abilities-api",
25+
"WordPress/abilities-api"
2626
// ... other plugins ...
27-
],
27+
]
2828
// ... more config ...
2929
}
3030
```
@@ -97,13 +97,13 @@ The below example is for a plugin implementation, but it could also be adapted f
9797
```php
9898
<?php
9999

100-
// 1. Define a callback function for your ability
100+
// 1. Define a callback function for your ability.
101101
function my_plugin_get_site_title( array $input = array() ): string {
102102
return get_bloginfo( 'name' );
103103
}
104104

105-
// 2. Register the ability when the Abilities API is initialized
106-
// Using abilities_api_init ensures the API is fully loaded
105+
// 2. Register the ability when the Abilities API is initialized.
106+
// Using `abilities_api_init` ensures the API is fully loaded.
107107
add_action( 'abilities_api_init', 'my_plugin_register_abilities' );
108108

109109
function my_plugin_register_abilities() {
@@ -127,16 +127,24 @@ function my_plugin_register_abilities() {
127127
) );
128128
}
129129

130-
// 3. Later, you can retrieve and execute the ability
130+
// 3. Later, you can retrieve and execute the ability.
131131
add_action( 'admin_init', 'my_plugin_use_ability' );
132132

133133
function my_plugin_use_ability() {
134134
$ability = wp_get_ability( 'my-plugin/get-site-title' );
135+
if ( ! $ability ) {
136+
// Ability not found.
137+
return;
138+
}
135139

136-
if ( $ability && $ability->has_permission() ) {
137-
$site_title = $ability->execute();
138-
// $site_title now holds the result of get_bloginfo('name')
139-
// error_log( 'Site Title: ' . $site_title );
140+
$site_title = $ability->execute();
141+
if ( is_wp_error( $site_title ) ) {
142+
// Handle execution error
143+
error_log( 'Execution error: ' . $site_title->get_error_message() );
144+
return;
140145
}
146+
147+
// `$site_title` now holds the result of `get_bloginfo( 'name' )`.
148+
echo 'Site Title: ' . esc_html( $site_title );
141149
}
142150
```

docs/3.registering-abilities.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
### 3. Registering Abilities (`wp_register_ability`)
1+
# 3. Registering Abilities (`wp_register_ability`)
22

33
The primary way to add functionality to the Abilities API is by using the `wp_register_ability()` function, typically hooked into the `abilities_api_init` action.
44

5-
**Function Signature**
5+
## Function Signature
66

77
```php
88
wp_register_ability( string $id, array $args ): ?\WP_Ability
@@ -12,7 +12,7 @@ wp_register_ability( string $id, array $args ): ?\WP_Ability
1212
- `$args` (`array`): An array of arguments defining the ability configuration.
1313
- **Return:** (`?\WP_Ability`) An instance of the registered ability if it was successfully registered, `null` on failure (e.g., invalid arguments, duplicate ID).
1414

15-
**Parameters Explained**
15+
## Parameters Explained
1616

1717
The `$args` array accepts the following keys:
1818

@@ -29,17 +29,17 @@ The `$args` array accepts the following keys:
2929
- If the input does not validate against the input schema, the permission callback will not be called, and a `WP_Error` will be returned instead.
3030
- `meta` (`array`, **Optional**): An associative array for storing arbitrary additional metadata about the ability.
3131

32-
**Ability ID Convention**
32+
## Ability ID Convention
3333

3434
The `$id` parameter must follow the pattern `namespace/ability-name`:
3535

3636
- **Format:** Must contain only lowercase alphanumeric characters (`a-z`, `0-9`), hyphens (`-`), and one forward slash (`/`) for namespacing.
3737
- **Convention:** Use your plugin slug as the namespace, like `my-plugin/ability-name`.
3838
- **Examples:** `my-plugin/update-settings`, `woocommerce/get-product`, `contact-form/send-message`, `analytics/track-event`
3939

40-
**Code Examples**
40+
## Code Examples
4141

42-
**1. Registering a Simple Data Retrieval Ability**
42+
### Registering a Simple Data Retrieval Ability
4343

4444
```php
4545
add_action( 'abilities_api_init', 'my_plugin_register_site_info_ability' );
@@ -82,7 +82,7 @@ function my_plugin_register_site_info_ability() {
8282
}
8383
```
8484

85-
**2. Registering an Ability with Input Parameters**
85+
### Registering an Ability with Input Parameters
8686

8787
```php
8888
add_action( 'abilities_api_init', 'my_plugin_register_update_option_ability' );
@@ -136,7 +136,7 @@ function my_plugin_register_update_option_ability() {
136136
}
137137
```
138138

139-
**3. Registering an Ability with Plugin Dependencies**
139+
### Registering an Ability with Plugin Dependencies
140140

141141
```php
142142
add_action( 'abilities_api_init', 'my_plugin_register_woo_stats_ability' );
@@ -194,7 +194,7 @@ function my_plugin_register_woo_stats_ability() {
194194
}
195195
```
196196

197-
**4. Registering an Ability That May Fail**
197+
### Registering an Ability That May Fail
198198

199199
```php
200200
add_action( 'abilities_api_init', 'my_plugin_register_send_email_ability' );

docs/4.using-abilities.md

Lines changed: 20 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
### 4. Using Abilities (`wp_get_ability`, `wp_get_abilities`)
1+
# 4. Using Abilities (`wp_get_ability`, `wp_get_abilities`)
22

33
Once abilities are registered, they can be retrieved and executed using global functions from the Abilities API.
44

5-
**Getting a Specific Ability (`wp_get_ability`)**
5+
## Getting a Specific Ability (`wp_get_ability`)
66

77
To get a single ability object by its name (namespace/ability-name):
88

@@ -20,7 +20,7 @@ $site_info_ability = wp_get_ability( 'my-plugin/get-site-info' );
2020

2121
if ( $site_info_ability ) {
2222
// Ability exists and is registered
23-
$result = $site_info_ability->execute();
23+
$site_info = $site_info_ability->execute();
2424
if ( is_wp_error( $site_info ) ) {
2525
// Handle WP_Error
2626
echo 'Error: ' . $site_info->get_error_message();
@@ -33,7 +33,7 @@ if ( $site_info_ability ) {
3333
}
3434
```
3535

36-
**Getting All Registered Abilities (`wp_get_abilities`)**
36+
## Getting All Registered Abilities (`wp_get_abilities`)
3737

3838
To get an array of all registered abilities:
3939

@@ -56,7 +56,7 @@ foreach ( $all_abilities as $name => $ability ) {
5656
}
5757
```
5858

59-
**Executing an Ability (`$ability->execute()`)**
59+
## Executing an Ability (`$ability->execute()`)
6060

6161
Once you have a `WP_Ability` object (usually from `wp_get_ability`), you execute it using the `execute()` method.
6262

@@ -124,9 +124,9 @@ if ( $ability ) {
124124
}
125125
```
126126

127-
**Checking Permissions (`$ability->has_permission()`)**
127+
## Checking Permissions (`$ability->check_permissions()`)
128128

129-
Before executing an ability, you can check if the current user has permission:
129+
You can check if the current user has permissions to execute the ability, also without executing it. The `check_permissions()` method returns either `true`, `false`, or a `WP_Error` object. `true` means permission is granted, `false` means the user simply lacks permission, and a `WP_Error` return value typically indicates a failure in the permission check process (such as an internal error or misconfiguration). You must use `is_wp_error()` to handle errors properly and distinguish between permission denial and actual errors:
130130

131131
```php
132132
$ability = wp_get_ability( 'my-plugin/update-option' );
@@ -136,26 +136,23 @@ if ( $ability ) {
136136
'option_value' => 'New Site Name',
137137
);
138138

139-
// Check permission before execution
140-
if ( $ability->has_permission( $input ) ) {
141-
$result = $ability->execute( $input );
142-
if ( is_wp_error( $result ) ) {
143-
// Handle WP_Error
144-
echo 'Error: ' . $result->get_error_message();
145-
} else {
146-
// Use $result
147-
if ( $result['success'] ) {
148-
echo 'Option updated successfully!';
149-
echo 'Previous value: ' . $result['previous_value'];
150-
}
151-
}
139+
// Check permission before execution - always use is_wp_error() first
140+
$has_permissions = $ability->check_permissions( $input );
141+
if ( true === $has_permissions ) {
142+
// Permissions granted – safe to execute.
143+
echo 'You have permissions to execute this ability.';
152144
} else {
153-
echo 'You do not have permission to execute this ability.';
145+
// Don't leak permission errors to unauthenticated users.
146+
if ( is_wp_error( $has_permissions ) ) {
147+
error_log( 'Permissions check failed: ' . $has_permissions->get_error_message() );
148+
}
149+
150+
echo 'You do not have permissions to execute this ability.';
154151
}
155152
}
156153
```
157154

158-
**Inspecting Ability Properties**
155+
## Inspecting Ability Properties
159156

160157
The `WP_Ability` class provides several getter methods to inspect ability properties:
161158

@@ -182,7 +179,7 @@ if ( $ability ) {
182179
}
183180
```
184181

185-
**Error Handling Patterns**
182+
## Error Handling Patterns
186183

187184
The Abilities API uses several error handling mechanisms:
188185

includes/abilities-api/class-wp-ability.php

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -312,12 +312,12 @@ protected function validate_input( $input = null ) {
312312
*
313313
* The input is validated against the input schema before it is passed to to permission callback.
314314
*
315-
* @since 0.1.0
315+
* @since N.E.X.T
316316
*
317317
* @param mixed $input Optional. The input data for permission checking. Default `null`.
318318
* @return bool|\WP_Error Whether the ability has the necessary permission.
319319
*/
320-
public function has_permission( $input = null ) {
320+
public function check_permissions( $input = null ) {
321321
$is_valid = $this->validate_input( $input );
322322
if ( is_wp_error( $is_valid ) ) {
323323
return $is_valid;
@@ -330,6 +330,24 @@ public function has_permission( $input = null ) {
330330
return call_user_func( $this->permission_callback, $input );
331331
}
332332

333+
/**
334+
* Checks whether the ability has the necessary permissions (deprecated).
335+
*
336+
* The input is validated against the input schema before it is passed to to permission callback.
337+
*
338+
* @deprecated N.E.X.T Use check_permissions() instead.
339+
* @see WP_Ability::check_permissions()
340+
*
341+
* @since 0.1.0
342+
*
343+
* @param mixed $input Optional. The input data for permission checking. Default `null`.
344+
* @return bool|\WP_Error Whether the ability has the necessary permission.
345+
*/
346+
public function has_permission( $input = null ) {
347+
_deprecated_function( __METHOD__, 'N.E.X.T', 'WP_Ability::check_permissions()' );
348+
return $this->check_permissions( $input );
349+
}
350+
333351
/**
334352
* Executes the ability callback.
335353
*
@@ -394,7 +412,7 @@ protected function validate_output( $output ) {
394412
* @return mixed|\WP_Error The result of the ability execution, or WP_Error on failure.
395413
*/
396414
public function execute( $input = null ) {
397-
$has_permissions = $this->has_permission( $input );
415+
$has_permissions = $this->check_permissions( $input );
398416
if ( true !== $has_permissions ) {
399417
if ( is_wp_error( $has_permissions ) ) {
400418
if ( 'ability_invalid_input' === $has_permissions->get_error_code() ) {

includes/rest-api/endpoints/class-wp-rest-abilities-run-controller.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ public function run_ability_permissions_check( $request ) {
163163
}
164164

165165
$input = $this->get_input_from_request( $request );
166-
if ( ! $ability->has_permission( $input ) ) {
166+
if ( ! $ability->check_permissions( $input ) ) {
167167
return new \WP_Error(
168168
'rest_ability_cannot_execute',
169169
__( 'Sorry, you are not allowed to execute this ability.' ),

tests/unit/abilities-api/wpRegisterAbility.php

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ public function test_register_valid_ability(): void {
134134
$this->assertSame( self::$test_ability_args['output_schema'], $result->get_output_schema() );
135135
$this->assertSame( self::$test_ability_args['meta'], $result->get_meta() );
136136
$this->assertTrue(
137-
$result->has_permission(
137+
$result->check_permissions(
138138
array(
139139
'a' => 2,
140140
'b' => 3,
@@ -164,7 +164,7 @@ public function test_register_ability_no_permissions(): void {
164164
$result = wp_register_ability( self::$test_ability_name, self::$test_ability_args );
165165

166166
$this->assertFalse(
167-
$result->has_permission(
167+
$result->check_permissions(
168168
array(
169169
'a' => 2,
170170
'b' => 3,
@@ -289,7 +289,7 @@ public function test_permission_callback_no_input_schema_match(): void {
289289

290290
$result = wp_register_ability( self::$test_ability_name, self::$test_ability_args );
291291

292-
$actual = $result->has_permission(
292+
$actual = $result->check_permissions(
293293
array(
294294
'a' => 2,
295295
'b' => 3,
@@ -308,6 +308,27 @@ public function test_permission_callback_no_input_schema_match(): void {
308308
);
309309
}
310310

311+
/**
312+
* Tests that deprecated has_permission() method still works.
313+
*
314+
* @expectedDeprecated WP_Ability::has_permission
315+
*/
316+
public function test_has_permission_deprecated_coverage(): void {
317+
do_action( 'abilities_api_init' );
318+
319+
$result = wp_register_ability( self::$test_ability_name, self::$test_ability_args );
320+
321+
// Test that deprecated method still works
322+
$this->assertTrue(
323+
$result->has_permission(
324+
array(
325+
'a' => 2,
326+
'b' => 3,
327+
)
328+
)
329+
);
330+
}
331+
311332
/**
312333
* Tests permission callback receiving input for contextual permission checks.
313334
*/
@@ -325,7 +346,7 @@ public function test_permission_callback_receives_input(): void {
325346

326347
// Test with a > b (should be allowed)
327348
$this->assertTrue(
328-
$result->has_permission(
349+
$result->check_permissions(
329350
array(
330351
'a' => 5,
331352
'b' => 3,
@@ -342,7 +363,7 @@ public function test_permission_callback_receives_input(): void {
342363

343364
// Test with a < b (should be denied)
344365
$this->assertFalse(
345-
$result->has_permission(
366+
$result->check_permissions(
346367
array(
347368
'a' => 2,
348369
'b' => 8,

0 commit comments

Comments
 (0)