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
126 changes: 113 additions & 13 deletions src/wp-includes/abilities-api/class-wp-ability.php
Original file line number Diff line number Diff line change
Expand Up @@ -436,22 +436,41 @@ public function get_meta_item( string $key, $default_value = null ) {
* the value of that key. If the input schema does not define a `default`, or if the input schema is empty,
* this method returns null. If input is provided, it is returned as-is.
*
* The {@see 'wp_ability_normalize_input'} filter fires after the built-in default-value handling,
* allowing plugins to transform the result.
*
* @since 6.9.0
* @since 7.1.0 Added the `wp_ability_normalize_input` filter.
*
* @param mixed $input Optional. The raw input provided for the ability. Default `null`.
* @return mixed The same input, or the default from schema, or `null` if default not set.
* @return mixed The normalized input, or a `WP_Error` if a filter returned one.
*/
public function normalize_input( $input = null ) {
if ( null !== $input ) {
return $input;
}

$input_schema = $this->get_input_schema();
if ( ! empty( $input_schema ) && array_key_exists( 'default', $input_schema ) ) {
return $input_schema['default'];
if ( null === $input ) {
$input_schema = $this->get_input_schema();
if ( ! empty( $input_schema ) && array_key_exists( 'default', $input_schema ) ) {
$input = $input_schema['default'];
}
}

return null;
/**
* Filters the normalized input for an ability.
*
* Fires after `normalize_input()` has applied any default value declared in the input schema,
* giving plugins a chance to adjust the input before it is consumed downstream. Common uses
* include defaulting beyond what JSON Schema can express, prompt enrichment, and injecting
* caller metadata.
*
* Returning a `WP_Error` causes callers that propagate it (such as `execute()`) to halt
* before validation, permission checks, and the registered execute callback.
*
* @since 7.1.0
*
* @param mixed $input The normalized input data.
* @param string $ability_name The name of the ability.
* @param WP_Ability $ability The ability instance.
*/
return apply_filters( 'wp_ability_normalize_input', $input, $this->name, $this );
}

/**
Expand Down Expand Up @@ -531,7 +550,11 @@ protected function invoke_callback( callable $callback, $input = null ) {
* Please note that input is not automatically validated against the input schema.
* Use `validate_input()` method to validate input before calling this method if needed.
*
* The {@see 'wp_ability_permission_result'} filter fires after the registered
* `permission_callback` returns, allowing plugins to override the result.
*
* @since 6.9.0
* @since 7.1.0 Added the `wp_ability_permission_result` filter.
*
* @see validate_input()
*
Expand All @@ -547,27 +570,71 @@ public function check_permissions( $input = null ) {
);
}

return $this->invoke_callback( $this->permission_callback, $input );
$permission = $this->invoke_callback( $this->permission_callback, $input );

/**
* Filters the result of an ability's permission check.
*
* Fires after the registered `permission_callback` returns. Plugins can use this to layer
* additional authorization rules on top of the ability's own permission logic — for example,
* multi-factor authorization gates or temporary permission elevation for trusted contexts.
*
* Filters can return `true` to grant, `false` to deny, or a `WP_Error` to deny with a specific
* error code and message. The filter receives whatever the `permission_callback` produced.
*
* @since 7.1.0
*
* @param bool|WP_Error $permission The permission result returned by `permission_callback`.
* @param string $ability_name The name of the ability.
* @param mixed $input The input data for the permission check.
* @param WP_Ability $ability The ability instance.
*/
return apply_filters( 'wp_ability_permission_result', $permission, $this->name, $input, $this );
}

/**
* Executes the ability callback.
*
* The {@see 'wp_ability_execute_result'} filter fires before this method returns, allowing
* plugins to transform the result produced by the registered `execute_callback`.
*
* @since 6.9.0
* @since 7.1.0 Added the `wp_ability_execute_result` filter.
*
* @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( $input = null ) {
if ( ! is_callable( $this->execute_callback ) ) {
return new WP_Error(
$result = new WP_Error(
'ability_invalid_execute_callback',
/* translators: %s ability name. */
sprintf( __( 'Ability "%s" does not have a valid execute callback.' ), esc_html( $this->name ) )
);
} else {
$result = $this->invoke_callback( $this->execute_callback, $input );
}

return $this->invoke_callback( $this->execute_callback, $input );
/**
* Filters the result returned by an ability's execute callback.
*
* Fires after the registered execute callback runs. Plugins can use this to transform the
* result — response formatting, stripping internal metadata, content safety filtering,
* response enrichment, or recovering from a failure by returning a successful value.
*
* The filter receives whatever the registered callback produced, including a `WP_Error`
* if execution failed. Filters may pass the `WP_Error` through unchanged, override it with
* a recovered result, or convert a successful result into a `WP_Error`.
*
* @since 7.1.0
*
* @param mixed $result The result returned by the registered `execute_callback`,
* or a `WP_Error` if execution failed.
* @param string $ability_name The name of the ability.
* @param mixed $input The normalized input data.
* @param WP_Ability $ability The ability instance.
*/
return apply_filters( 'wp_ability_execute_result', $result, $this->name, $input, $this );
}

/**
Expand Down Expand Up @@ -605,12 +672,45 @@ protected function validate_output( $output ) {
* Before returning the return value, it also validates the output.
*
* @since 6.9.0
* @since 7.1.0 Added the `wp_pre_execute_ability` filter.
*
* @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( $input = null ) {
$input = $this->normalize_input( $input );
/**
* Filters whether to short-circuit ability execution.
*
* Returning a value other than the received default bypasses the rest of `execute()` —
* input normalization, input validation, permission checks, the registered execute callback,
* output validation, and the surrounding actions — and the value is returned to the caller
* as-is. Useful for cached responses, rate limiting, maintenance mode, and test mocking.
*
* To continue with normal execution, return `$pre` unchanged. This preserves `null` as a
* valid short-circuit result.
*
* Because validation is bypassed, callers that short-circuit are responsible for the
* integrity of any value they consume from `$input`.
*
* @since 7.1.0
*
* @param mixed $pre The pre-computed result. Return this value unchanged to continue execution.
* Default internal sentinel object.
* @param string $ability_name The name of the ability.
* @param mixed $input The raw input passed to `execute()`.
* @param WP_Ability $ability The ability instance.
*/
$pre_execute_sentinel = new stdClass();
$pre = apply_filters( 'wp_pre_execute_ability', $pre_execute_sentinel, $this->name, $input, $this );
if ( $pre !== $pre_execute_sentinel ) {
return $pre;
}

$input = $this->normalize_input( $input );
if ( is_wp_error( $input ) ) {
return $input;
}

$is_valid = $this->validate_input( $input );
if ( is_wp_error( $is_valid ) ) {
return $is_valid;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,8 +158,17 @@ public function check_ability_permissions( $request ) {
return $is_valid;
}

$input = $this->get_input_from_request( $request );
$input = $ability->normalize_input( $input );
$input = $this->get_input_from_request( $request );
$input = $ability->normalize_input( $input );
if ( is_wp_error( $input ) ) {
$error_data = $input->get_error_data();
if ( ! is_array( $error_data ) || ! isset( $error_data['status'] ) ) {
$input->add_data( array( 'status' => 400 ) );
}

return $input;
}

$is_valid = $ability->validate_input( $input );
if ( is_wp_error( $is_valid ) ) {
$is_valid->add_data( array( 'status' => 400 ) );
Expand Down
Loading
Loading