Make WordPress Core

Changeset 61047


Ignore:
Timestamp:
10/22/2025 03:02:29 PM (3 months ago)
Author:
gziolo
Message:

Abilities API: Normalize input from schema

Without this patch REST API would require a weird empty ?input field for optional input given how the current controller works with input schema when it defines the expected shape. This patch normalizes the input for the ability, applying the default value from the input schema when needed.

Developed in https://github.com/WordPress/wordpress-develop/pull/10395.

Follow-up [61032], [61045].

Props gziolo, jorgefilipecosta, mukesh27.
Fixes #64139.

Location:
trunk
Files:
5 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/abilities-api/class-wp-ability.php

    r61032 r61047  
    402402
    403403    /**
     404     * Normalizes the input for the ability, applying the default value from the input schema when needed.
     405     *
     406     * When no input is provided and the input schema is defined with a top-level `default` key, this method returns
     407     * the value of that key. If the input schema does not define a `default`, or if the input schema is empty,
     408     * this method returns null. If input is provided, it is returned as-is.
     409     *
     410     * @since 6.9.0
     411     *
     412     * @param mixed $input Optional. The raw input provided for the ability. Default `null`.
     413     * @return mixed The same input, or the default from schema, or `null` if default not set.
     414     */
     415    public function normalize_input( $input = null ) {
     416        if ( null !== $input ) {
     417            return $input;
     418        }
     419
     420        $input_schema = $this->get_input_schema();
     421        if ( ! empty( $input_schema ) && array_key_exists( 'default', $input_schema ) ) {
     422            return $input_schema['default'];
     423        }
     424
     425        return null;
     426    }
     427
     428    /**
    404429     * Validates input data against the input schema.
    405430     *
     
    537562     */
    538563    public function execute( $input = null ) {
     564        $input    = $this->normalize_input( $input );
    539565        $is_valid = $this->validate_input( $input );
    540566        if ( is_wp_error( $is_valid ) ) {
  • trunk/src/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-categories-controller.php

    r61045 r61047  
    9494
    9595        $total_categories = count( $categories );
    96         $max_pages        = ceil( $total_categories / $per_page );
     96        $max_pages        = (int) ceil( $total_categories / $per_page );
    9797
    9898        if ( $request->get_method() === 'HEAD' ) {
  • trunk/src/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-list-controller.php

    r61032 r61047  
    112112
    113113        $total_abilities = count( $abilities );
    114         $max_pages       = ceil( $total_abilities / $per_page );
     114        $max_pages       = (int) ceil( $total_abilities / $per_page );
    115115
    116116        if ( $request->get_method() === 'HEAD' ) {
  • trunk/src/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-run-controller.php

    r61032 r61047  
    160160
    161161        $input    = $this->get_input_from_request( $request );
     162        $input    = $ability->normalize_input( $input );
    162163        $is_valid = $ability->validate_input( $input );
    163164        if ( is_wp_error( $is_valid ) ) {
  • trunk/tests/phpunit/tests/rest-api/wpRestAbilitiesV1RunController.php

    r61032 r61047  
    922922
    923923    /**
    924      * Test edge case with empty input for both GET and POST methods.
    925      *
    926      * @ticket 64098
    927      */
    928     public function test_empty_input_handling(): void {
    929         // Registers abilities for empty input testing.
     924     * Test edge case with empty input for GET method.
     925     *
     926     * @ticket 64098
     927     */
     928    public function test_empty_input_handling_get_method(): void {
    930929        wp_register_ability(
    931930            'test/read-only-empty',
     
    942941                        'readonly' => true,
    943942                    ),
    944                     'show_in_rest' => true,
    945                 ),
    946             )
    947         );
    948 
    949         wp_register_ability(
    950             'test/regular-empty',
    951             array(
    952                 'label'               => 'Regular Empty',
    953                 'description'         => 'Regular with empty input.',
    954                 'category'            => 'general',
    955                 'execute_callback'    => static function () {
    956                     return array( 'input_was_empty' => 0 === func_num_args() );
    957                 },
    958                 'permission_callback' => '__return_true',
    959                 'meta'                => array(
    960943                    'show_in_rest' => true,
    961944                ),
     
    968951        $this->assertEquals( 200, $get_response->get_status() );
    969952        $this->assertTrue( $get_response->get_data()['input_was_empty'] );
     953    }
     954
     955    /**
     956     * Test edge case with empty input for GET method, and normalized input using schema.
     957     *
     958     * @ticket 64098
     959     */
     960    public function test_empty_input_handling_get_method_with_normalized_input(): void {
     961        wp_register_ability(
     962            'test/read-only-empty-array',
     963            array(
     964                'label'               => 'Read-only Empty Array',
     965                'description'         => 'Read-only with inferred empty array input from schema.',
     966                'category'            => 'general',
     967                'input_schema'        => array(
     968                    'type'    => 'array',
     969                    'default' => array(),
     970                ),
     971                'execute_callback'    => static function ( $input ) {
     972                    return is_array( $input ) && empty( $input );
     973                },
     974                'permission_callback' => '__return_true',
     975                'meta'                => array(
     976                    'annotations'  => array(
     977                        'readonly' => true,
     978                    ),
     979                    'show_in_rest' => true,
     980                ),
     981            )
     982        );
     983
     984        // Tests GET with no input parameter.
     985        $get_request  = new WP_REST_Request( 'GET', '/wp-abilities/v1/abilities/test/read-only-empty-array/run' );
     986        $get_response = $this->server->dispatch( $get_request );
     987        $this->assertEquals( 200, $get_response->get_status() );
     988        $this->assertTrue( $get_response->get_data() );
     989    }
     990
     991    /**
     992     * Test edge case with empty input for POST method.
     993     *
     994     * @ticket 64098
     995     */
     996    public function test_empty_input_handling_post_method(): void {
     997        wp_register_ability(
     998            'test/regular-empty',
     999            array(
     1000                'label'               => 'Regular Empty',
     1001                'description'         => 'Regular with empty input.',
     1002                'category'            => 'general',
     1003                'execute_callback'    => static function () {
     1004                    return array( 'input_was_empty' => 0 === func_num_args() );
     1005                },
     1006                'permission_callback' => '__return_true',
     1007                'meta'                => array(
     1008                    'show_in_rest' => true,
     1009                ),
     1010            )
     1011        );
    9701012
    9711013        // Tests POST with no body.
Note: See TracChangeset for help on using the changeset viewer.