Skip to content
Closed
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
26 changes: 26 additions & 0 deletions src/wp-includes/abilities-api/class-wp-ability.php
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,31 @@ public function get_meta_item( string $key, $default_value = null ) {
return array_key_exists( $key, $this->meta ) ? $this->meta[ $key ] : $default_value;
}

/**
* Normalizes the input for the ability, applying the default value from the input schema when needed.
*
* When no input is provided and the input schema is defined with a top-level `default` key, this method returns
* 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.
*
* @since 6.9.0
*
* @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.
*/
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'];
}

return null;
}

/**
* Validates input data against the input schema.
*
Expand Down Expand Up @@ -536,6 +561,7 @@ protected function validate_output( $output ) {
* @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 );
$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 @@ -159,6 +159,7 @@ public function check_ability_permissions( $request ) {
}

$input = $this->get_input_from_request( $request );
$input = $ability->normalize_input( $input );
$is_valid = $ability->validate_input( $input );
if ( is_wp_error( $is_valid ) ) {
$is_valid->add_data( array( 'status' => 400 ) );
Expand Down
60 changes: 51 additions & 9 deletions tests/phpunit/tests/rest-api/wpRestAbilitiesV1RunController.php
Original file line number Diff line number Diff line change
Expand Up @@ -921,12 +921,11 @@ public function test_ability_without_annotations_defaults_to_post_method(): void
}

/**
* Test edge case with empty input for both GET and POST methods.
* Test edge case with empty input for GET method.
*
* @ticket 64098
*/
public function test_empty_input_handling(): void {
// Registers abilities for empty input testing.
public function test_empty_input_handling_get_method(): void {
wp_register_ability(
'test/read-only-empty',
array(
Expand All @@ -946,6 +945,55 @@ public function test_empty_input_handling(): void {
)
);

// Tests GET with no input parameter.
$get_request = new WP_REST_Request( 'GET', '/wp-abilities/v1/abilities/test/read-only-empty/run' );
$get_response = $this->server->dispatch( $get_request );
$this->assertEquals( 200, $get_response->get_status() );
$this->assertTrue( $get_response->get_data()['input_was_empty'] );
}

/**
* Test edge case with empty input for GET method, and normalized input using schema.
*
* @ticket 64098
*/
public function test_empty_input_handling_get_method_with_normalized_input(): void {
wp_register_ability(
'test/read-only-empty-array',
array(
'label' => 'Read-only Empty Array',
'description' => 'Read-only with inferred empty array input from schema.',
'category' => 'general',
'input_schema' => array(
'type' => 'array',
'default' => array(),
),
'execute_callback' => static function ( $input ) {
return is_array( $input ) && empty( $input );
},
'permission_callback' => '__return_true',
'meta' => array(
'annotations' => array(
'readonly' => true,
),
'show_in_rest' => true,
),
)
);

// Tests GET with no input parameter.
$get_request = new WP_REST_Request( 'GET', '/wp-abilities/v1/abilities/test/read-only-empty-array/run' );
$get_response = $this->server->dispatch( $get_request );
$this->assertEquals( 200, $get_response->get_status() );
$this->assertTrue( $get_response->get_data() );
}

/**
* Test edge case with empty input for POST method.
*
* @ticket 64098
*/
public function test_empty_input_handling_post_method(): void {
wp_register_ability(
'test/regular-empty',
array(
Expand All @@ -962,12 +1010,6 @@ public function test_empty_input_handling(): void {
)
);

// Tests GET with no input parameter.
$get_request = new WP_REST_Request( 'GET', '/wp-abilities/v1/abilities/test/read-only-empty/run' );
$get_response = $this->server->dispatch( $get_request );
$this->assertEquals( 200, $get_response->get_status() );
$this->assertTrue( $get_response->get_data()['input_was_empty'] );

// Tests POST with no body.
$post_request = new WP_REST_Request( 'POST', '/wp-abilities/v1/abilities/test/regular-empty/run' );
$post_request->set_header( 'Content-Type', 'application/json' );
Expand Down
Loading