Skip to content
Merged
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
51 changes: 26 additions & 25 deletions docs/3.registering-abilities.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,33 +8,34 @@ The primary way to add functionality to the Abilities API is by using the `wp_re
wp_register_ability( string $id, array $args ): ?\WP_Ability
```

- `$id` (`string`): A unique identifier for the ability.
- `$args` (`array`): An array of arguments defining the ability configuration.
- **Return:** (`?\WP_Ability`) An instance of the registered ability if it was successfully registered, `null` on failure (e.g., invalid arguments, duplicate ID).
- `$id` (`string`): A unique identifier for the ability.
- `$args` (`array`): An array of arguments defining the ability configuration.
- **Return:** (`?\WP_Ability`) An instance of the registered ability if it was successfully registered, `null` on failure (e.g., invalid arguments, duplicate ID).

**Parameters Explained**

The `$args` array accepts the following keys:

- `label` (`string`, **Required**): A human-readable name for the ability. Used for display purposes. Should be translatable.
- `description` (`string`, **Required**): A detailed description of what the ability does, its purpose, and its parameters or return values. This is crucial for AI agents to understand how and when to use the ability. Should be translatable.
- `input_schema` (`array`, **Required**): A JSON Schema definition describing the expected input parameters for the ability's execute callback. Used for validation and documentation.
- `output_schema` (`array`, **Required**): A JSON Schema definition describing the expected format of the data returned by the ability. Used for validation and documentation.
- `execute_callback` (`callable`, **Required**): The PHP function or method to execute when this ability is called.
- The callback receives one argument: an associative array of validated input parameters.
- The callback should return the result of the ability's operation or return a `WP_Error` object on failure.
- `permission_callback` (`callable`|`null`, **Optional**): A callback function to check if the current user has permission to execute this ability.
- Should return `true` if the user has permission, or a `WP_Error` object otherwise.
- If not provided, the ability will only validate input parameters before execution.
- This defaults to `true` if not set.
- `meta` (`array`, **Optional**): An associative array for storing arbitrary additional metadata about the ability.
- `label` (`string`, **Required**): A human-readable name for the ability. Used for display purposes. Should be translatable.
- `description` (`string`, **Required**): A detailed description of what the ability does, its purpose, and its parameters or return values. This is crucial for AI agents to understand how and when to use the ability. Should be translatable.
- `input_schema` (`array`, **Required**): A JSON Schema definition describing the expected input parameters for the ability's execute callback. Used for validation and documentation.
- `output_schema` (`array`, **Required**): A JSON Schema definition describing the expected format of the data returned by the ability. Used for validation and documentation.
- `execute_callback` (`callable`, **Required**): The PHP function or method to execute when this ability is called.
- The callback receives one optional argument: it can have any type as defined in the input schema (e.g., `array`, `object`, `string`, etc.).
- The callback should return the result of the ability's operation or return a `WP_Error` object on failure.
- `permission_callback` (`callable`|`null`, **Optional**): A callback function to check if the current user has permission to execute this ability.
- Should return `true` if the user has permission, or a `WP_Error` object otherwise.
- If not provided, the ability will only validate input parameters before execution.
- This defaults to `true` if not set.
- `meta` (`array`, **Optional**): An associative array for storing arbitrary additional metadata about the ability.

**Ability ID Convention**

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

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

**Code Examples**

Expand All @@ -59,7 +60,7 @@ function my_plugin_register_site_info_ability() {
'description' => 'Site name'
),
'description' => array(
'type' => 'string',
'type' => 'string',
'description' => 'Site tagline'
),
'url' => array(
Expand Down Expand Up @@ -119,10 +120,10 @@ function my_plugin_register_update_option_ability() {
'execute_callback' => function( $input ) {
$option_name = $input['option_name'];
$new_value = $input['option_value'];

$previous_value = get_option( $option_name );
$success = update_option( $option_name, $new_value );

return array(
'success' => $success,
'previous_value' => $previous_value
Expand All @@ -144,7 +145,7 @@ function my_plugin_register_woo_stats_ability() {
if ( ! class_exists( 'WooCommerce' ) ) {
return;
}

wp_register_ability( 'my-plugin/get-woo-stats', array(
'label' => __( 'Get WooCommerce Statistics', 'my-plugin' ),
'description' => __( 'Retrieves basic WooCommerce store statistics including total orders and revenue.', 'my-plugin' ),
Expand Down Expand Up @@ -175,7 +176,7 @@ function my_plugin_register_woo_stats_ability() {
),
'execute_callback' => function( $input ) {
$period = $input['period'] ?? 'month';

// Implementation would calculate stats based on period
return array(
'total_orders' => 42,
Expand Down Expand Up @@ -238,14 +239,14 @@ function my_plugin_register_send_email_ability() {
$input['subject'],
$input['message']
);

if ( ! $sent ) {
return new \WP_Error(
'email_send_failed',
sprintf( __( 'Failed to send email' ), 'my-plugin' )
);
}

return array( 'sent' => true );
},
'permission_callback' => function() {
Expand Down
14 changes: 7 additions & 7 deletions docs/4.using-abilities.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,10 @@ Once you have a `WP_Ability` object (usually from `wp_get_ability`), you execute
/**
* Executes the ability after input validation and running a permission check.
*
* @param array $input Optional. The input data for the ability.
* @param mixed $input Optional. The input data for the ability. Defaults to `null`.
* @return mixed|WP_Error The result of the ability execution, or WP_Error on failure.
*/
// public function execute( array $input = array() )
// public function execute( $input = null )

// Example 1: Ability with no input parameters
$ability = wp_get_ability( 'my-plugin/get-site-info' );
Expand Down Expand Up @@ -135,7 +135,7 @@ if ( $ability ) {
'option_name' => 'blogname',
'option_value' => 'New Site Name',
);

// Check permission before execution
if ( $ability->has_permission( $input ) ) {
$result = $ability->execute( $input );
Expand Down Expand Up @@ -166,14 +166,14 @@ if ( $ability ) {
echo 'Name: ' . $ability->get_name() . "\n";
echo 'Label: ' . $ability->get_label() . "\n";
echo 'Description: ' . $ability->get_description() . "\n";

// Schema information
$input_schema = $ability->get_input_schema();
$output_schema = $ability->get_output_schema();

echo 'Input Schema: ' . json_encode( $input_schema, JSON_PRETTY_PRINT ) . "\n";
echo 'Output Schema: ' . json_encode( $output_schema, JSON_PRETTY_PRINT ) . "\n";

// Metadata
$meta = $ability->get_meta();
if ( ! empty( $meta ) ) {
Expand Down Expand Up @@ -210,5 +210,5 @@ if ( is_null( $result ) ) {
}

// Success - use the result
// Process $result based on the ability's output schema
// Process $result based on the ability's output schema
```
4 changes: 2 additions & 2 deletions includes/abilities-api.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@
* description?: string,
* input_schema?: array<string,mixed>,
* output_schema?: array<string,mixed>,
* execute_callback?: callable( array<string,mixed> $input): (mixed|\WP_Error),
* permission_callback?: callable( array<string,mixed> $input ): (bool|\WP_Error),
* execute_callback?: callable( mixed $input= ): (mixed|\WP_Error),
* permission_callback?: callable( mixed $input= ): (bool|\WP_Error),
* meta?: array<string,mixed>,
* ability_class?: class-string<\WP_Ability>,
* ...<string, mixed>
Expand Down
4 changes: 2 additions & 2 deletions includes/abilities-api/class-wp-abilities-registry.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@ final class WP_Abilities_Registry {
* description?: string,
* input_schema?: array<string,mixed>,
* output_schema?: array<string,mixed>,
* execute_callback?: callable( array<string,mixed> $input): (mixed|\WP_Error),
* permission_callback?: ?callable( array<string,mixed> $input ): (bool|\WP_Error),
* execute_callback?: callable( mixed $input= ): (mixed|\WP_Error),
* permission_callback?: ?callable( mixed $input= ): (bool|\WP_Error),
* meta?: array<string,mixed>,
* ability_class?: class-string<\WP_Ability>,
* ...<string, mixed>
Expand Down
44 changes: 31 additions & 13 deletions includes/abilities-api/class-wp-ability.php
Original file line number Diff line number Diff line change
Expand Up @@ -65,15 +65,15 @@ class WP_Ability {
* The ability execute callback.
*
* @since 0.1.0
* @var callable( array<string,mixed> $input): (mixed|\WP_Error)
* @var callable( mixed $input= ): (mixed|\WP_Error)
*/
protected $execute_callback;

/**
* The optional ability permission callback.
*
* @since 0.1.0
* @var ?callable( array<string,mixed> $input ): (bool|\WP_Error)
* @var ?callable( mixed $input= ): (bool|\WP_Error)
*/
protected $permission_callback = null;

Expand Down Expand Up @@ -145,8 +145,8 @@ public function __construct( string $name, array $args ) {
* description: string,
* input_schema?: array<string,mixed>,
* output_schema?: array<string,mixed>,
* execute_callback: callable( array<string,mixed> $input): (mixed|\WP_Error),
* permission_callback?: ?callable( array<string,mixed> $input ): (bool|\WP_Error),
* execute_callback: callable( mixed $input= ): (mixed|\WP_Error),
* permission_callback?: ?callable( mixed $input= ): (bool|\WP_Error),
* meta?: array<string,mixed>,
* ...<string, mixed>,
* } $args
Expand Down Expand Up @@ -269,13 +269,23 @@ public function get_meta(): array {
*
* @since 0.1.0
*
* @param array<string,mixed> $input Optional. The input data to validate.
* @param mixed $input Optional. The input data to validate. Default `null`.
* @return true|\WP_Error Returns true if valid or the WP_Error object if validation fails.
*/
protected function validate_input( array $input = array() ) {
protected function validate_input( $input = null ) {
$input_schema = $this->get_input_schema();
if ( empty( $input_schema ) ) {
return true;
if ( null === $input ) {
return true;
}
return new \WP_Error(
'ability_missing_input_schema',
sprintf(
/* translators: %s ability name. */
__( 'Ability "%s" does not define an input schema required to validate the provided input.' ),
$this->name
)
);
}

$valid_input = rest_validate_value_from_schema( $input, $input_schema, 'input' );
Expand All @@ -301,10 +311,10 @@ protected function validate_input( array $input = array() ) {
*
* @since 0.1.0
*
* @param array<string,mixed> $input Optional. The input data for permission checking.
* @param mixed $input Optional. The input data for permission checking. Default `null`.
* @return bool|\WP_Error Whether the ability has the necessary permission.
*/
public function has_permission( array $input = array() ) {
public function has_permission( $input = null ) {
$is_valid = $this->validate_input( $input );
if ( is_wp_error( $is_valid ) ) {
return $is_valid;
Expand All @@ -314,6 +324,10 @@ public function has_permission( array $input = array() ) {
return true;
}

if ( empty( $this->get_input_schema() ) ) {
return call_user_func( $this->permission_callback );
}

return call_user_func( $this->permission_callback, $input );
}

Expand All @@ -322,10 +336,10 @@ public function has_permission( array $input = array() ) {
*
* @since 0.1.0
*
* @param array<string,mixed> $input The input data for the ability.
* @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.
*/
protected function do_execute( array $input ) {
protected function do_execute( $input = null ) {
if ( ! is_callable( $this->execute_callback ) ) {
return new \WP_Error(
'ability_invalid_execute_callback',
Expand All @@ -334,6 +348,10 @@ protected function do_execute( array $input ) {
);
}

if ( empty( $this->get_input_schema() ) ) {
return call_user_func( $this->execute_callback );
}

return call_user_func( $this->execute_callback, $input );
}

Expand Down Expand Up @@ -373,10 +391,10 @@ protected function validate_output( $output ) {
*
* @since 0.1.0
*
* @param array<string,mixed> $input Optional. The input data for the ability.
* @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.
*/
public function execute( array $input = array() ) {
public function execute( $input = null ) {
$has_permissions = $this->has_permission( $input );
if ( true !== $has_permissions ) {
if ( is_wp_error( $has_permissions ) ) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -180,22 +180,18 @@ public function run_ability_permissions_check( $request ) {
* @since 0.1.0
*
* @param \WP_REST_Request<array<string,mixed>> $request The request object.
* @return array<string, mixed> The input parameters.
* @return mixed|null The input parameters.
*/
private function get_input_from_request( $request ) {
if ( 'GET' === $request->get_method() ) {
// For GET requests, look for 'input' query parameter.
$query_params = $request->get_query_params();
return isset( $query_params['input'] ) && is_array( $query_params['input'] )
? $query_params['input']
: array();
return $query_params['input'] ?? null;
}

// For POST requests, look for 'input' in JSON body.
$json_params = $request->get_json_params();
return isset( $json_params['input'] ) && is_array( $json_params['input'] )
? $json_params['input']
: array();
return $json_params['input'] ?? null;
}

/**
Expand All @@ -209,8 +205,8 @@ public function get_run_args(): array {
return array(
'input' => array(
'description' => __( 'Input parameters for the ability execution.' ),
'type' => 'object',
'default' => array(),
'type' => array( 'integer', 'number', 'boolean', 'string', 'array', 'object', 'null' ),
'default' => null,
),
);
}
Expand All @@ -230,7 +226,6 @@ public function get_run_schema(): array {
'properties' => array(
'result' => array(
'description' => __( 'The result of the ability execution.' ),
'type' => 'mixed',
'context' => array( 'view' ),
'readonly' => true,
),
Expand Down
Loading