WordPress.org

Make WordPress Core

Changeset 41758


Ignore:
Timestamp:
10/05/17 00:18:44 (2 months ago)
Author:
kadamwhite
Message:

REST API: Support objects in settings schema.

Enables register_setting to accept an object as its schema value, allowing settings to accept non-scalar values through the REST API.
This whitelists the added type in the settings controller, and passes properties from argument registration into the validation functions.

Props joehoyle.
See #38583.

Location:
trunk
Files:
3 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/rest-api/endpoints/class-wp-rest-controller.php

    r41591 r41758  
    546546            } 
    547547 
    548             foreach ( array( 'type', 'format', 'enum', 'items' ) as $schema_prop ) { 
     548            foreach ( array( 'type', 'format', 'enum', 'items', 'properties' ) as $schema_prop ) { 
    549549                if ( isset( $params[ $schema_prop ] ) ) { 
    550550                    $endpoint_args[ $field_id ][ $schema_prop ] = $params[ $schema_prop ]; 
  • trunk/src/wp-includes/rest-api/endpoints/class-wp-rest-settings-controller.php

    r41731 r41758  
    120120     */ 
    121121    protected function prepare_value( $value, $schema ) { 
    122         // If the value is not a scalar, it's not possible to cast it to anything. 
    123         if ( ! is_scalar( $value ) ) { 
     122        // If the value is not valid by the schema, set the value to null. Null 
     123        // values are specifcally non-destructive so this will not cause overwriting 
     124        // the current invalid value to null. 
     125        if ( is_wp_error( rest_validate_value_from_schema( $value, $schema ) ) ) { 
    124126            return null; 
    125127        } 
    126  
    127         switch ( $schema['type'] ) { 
    128             case 'string': 
    129                 return (string) $value; 
    130             case 'integer': 
    131                 return (int) $value; 
    132             case 'number': 
    133                 return (float) $value; 
    134             case 'boolean': 
    135                 return (bool) $value; 
    136             default: 
    137                 return null; 
    138         } 
     128        return rest_sanitize_value_from_schema( $value, $schema ); 
    139129    } 
    140130 
     
    149139    public function update_item( $request ) { 
    150140        $options = $this->get_registered_options(); 
     141 
    151142        $params  = $request->get_params(); 
    152143 
     
    188179                 * To protect clients from accidentally including the null 
    189180                 * values from a response object in a request, we do not allow 
    190                  * options with non-scalar values to be updated to null. 
     181                 * options with values that don't pass validation to be updated to null. 
    191182                 * Without this added protection a client could mistakenly 
    192                  * delete all options that have non-scalar values from the 
     183                 * delete all options that have invalid values from the 
    193184                 * database. 
    194185                 */ 
    195                 if ( ! is_scalar( get_option( $args['option_name'], false ) ) ) { 
     186                if ( is_wp_error( rest_validate_value_from_schema( get_option( $args['option_name'], false ), $args['schema'] ) ) ) { 
    196187                    return new WP_Error( 
    197188                        'rest_invalid_stored_value', sprintf( __( 'The %s property has an invalid stored value, and cannot be updated to null.' ), $name ), array( 'status' => 500 ) 
     
    254245             * to be updated with arbitrary values that we can't do decent sanitizing for. 
    255246             */ 
    256             if ( ! in_array( $rest_args['schema']['type'], array( 'number', 'integer', 'string', 'boolean' ), true ) ) { 
     247            if ( ! in_array( $rest_args['schema']['type'], array( 'number', 'integer', 'string', 'boolean', 'array', 'object' ), true ) ) { 
    257248                continue; 
    258249            } 
  • trunk/tests/phpunit/tests/rest-api/rest-settings-controller.php

    r41228 r41758  
    128128    } 
    129129 
     130    public function test_get_item_with_custom_array_setting() { 
     131        wp_set_current_user( self::$administrator ); 
     132 
     133        register_setting( 'somegroup', 'mycustomsetting', array( 
     134            'show_in_rest' => array( 
     135                'schema' => array( 
     136                    'type'    => 'array', 
     137                    'items'   => array( 
     138                        'type' => 'integer', 
     139                    ), 
     140                ), 
     141            ), 
     142            'type'         => 'array', 
     143        ) ); 
     144 
     145        // Array is cast to correct types. 
     146        update_option( 'mycustomsetting', array( '1', '2' ) ); 
     147        $request = new WP_REST_Request( 'GET', '/wp/v2/settings' ); 
     148        $response = $this->server->dispatch( $request ); 
     149        $data = $response->get_data(); 
     150        $this->assertEquals( array( 1, 2 ), $data['mycustomsetting'] ); 
     151 
     152        // Empty array works as expected. 
     153        update_option( 'mycustomsetting', array() ); 
     154        $request = new WP_REST_Request( 'GET', '/wp/v2/settings' ); 
     155        $response = $this->server->dispatch( $request ); 
     156        $data = $response->get_data(); 
     157        $this->assertEquals( array(), $data['mycustomsetting'] ); 
     158 
     159        // Invalid value 
     160        update_option( 'mycustomsetting', array( array( 1 ) ) ); 
     161        $request = new WP_REST_Request( 'GET', '/wp/v2/settings' ); 
     162        $response = $this->server->dispatch( $request ); 
     163        $data = $response->get_data(); 
     164        $this->assertEquals( null, $data['mycustomsetting'] ); 
     165 
     166        // No option value 
     167        delete_option( 'mycustomsetting' ); 
     168        $request = new WP_REST_Request( 'GET', '/wp/v2/settings' ); 
     169        $response = $this->server->dispatch( $request ); 
     170        $data = $response->get_data(); 
     171        $this->assertEquals( null, $data['mycustomsetting'] ); 
     172 
     173        unregister_setting( 'somegroup', 'mycustomsetting' ); 
     174    } 
     175 
     176    public function test_get_item_with_custom_object_setting() { 
     177        wp_set_current_user( self::$administrator ); 
     178 
     179        register_setting( 'somegroup', 'mycustomsetting', array( 
     180            'show_in_rest' => array( 
     181                'schema' => array( 
     182                    'type'    => 'object', 
     183                    'properties' => array( 
     184                        'a' => array( 
     185                            'type' => 'integer', 
     186                        ), 
     187                    ), 
     188                ), 
     189            ), 
     190            'type'         => 'object', 
     191        ) ); 
     192 
     193        // Object is cast to correct types. 
     194        update_option( 'mycustomsetting', array( 'a' => '1' ) ); 
     195        $request = new WP_REST_Request( 'GET', '/wp/v2/settings' ); 
     196        $response = $this->server->dispatch( $request ); 
     197        $data = $response->get_data(); 
     198        $this->assertEquals( array( 'a' => 1 ), $data['mycustomsetting'] ); 
     199 
     200        // Empty array works as expected. 
     201        update_option( 'mycustomsetting', array() ); 
     202        $request = new WP_REST_Request( 'GET', '/wp/v2/settings' ); 
     203        $response = $this->server->dispatch( $request ); 
     204        $data = $response->get_data(); 
     205        $this->assertEquals( array(), $data['mycustomsetting'] ); 
     206 
     207        // Invalid value 
     208        update_option( 'mycustomsetting', array( 'a' => 1, 'b' => 2 ) ); 
     209        $request = new WP_REST_Request( 'GET', '/wp/v2/settings' ); 
     210        $response = $this->server->dispatch( $request ); 
     211        $data = $response->get_data(); 
     212        $this->assertEquals( array( 'a' => 1 ), $data['mycustomsetting'] ); 
     213 
     214        unregister_setting( 'somegroup', 'mycustomsetting' ); 
     215    } 
     216 
    130217    public function get_setting_custom_callback( $result, $name, $args ) { 
    131218        switch ( $name ) { 
     
    216303        $data = $response->get_data(); 
    217304        $this->assertEquals( null, $data['mycustomsettinginrest'] ); 
     305        unregister_setting( 'somegroup', 'mycustomsetting' ); 
    218306    } 
    219307 
     
    243331    } 
    244332 
     333    public function test_update_item_with_array() { 
     334        register_setting( 'somegroup', 'mycustomsetting', array( 
     335            'show_in_rest' => array( 
     336                'schema' => array( 
     337                    'type'  => 'array', 
     338                    'items' => array( 
     339                        'type' => 'integer', 
     340                    ), 
     341                ), 
     342            ), 
     343            'type'         => 'array', 
     344        ) ); 
     345 
     346        // We have to re-register the route, as the args changes based off registered settings. 
     347        $this->server->override_by_default = true; 
     348        $this->endpoint->register_routes(); 
     349        wp_set_current_user( self::$administrator ); 
     350 
     351        $request = new WP_REST_Request( 'PUT', '/wp/v2/settings' ); 
     352        $request->set_param( 'mycustomsetting', array( '1', '2' ) ); 
     353        $response = $this->server->dispatch( $request ); 
     354        $data = $response->get_data(); 
     355        $this->assertEquals( array( 1, 2 ), $data['mycustomsetting'] ); 
     356        $this->assertEquals( array( 1, 2 ), get_option( 'mycustomsetting' ) ); 
     357 
     358        // Setting an empty array. 
     359        $request = new WP_REST_Request( 'PUT', '/wp/v2/settings' ); 
     360        $request->set_param( 'mycustomsetting', array() ); 
     361        $response = $this->server->dispatch( $request ); 
     362        $data = $response->get_data(); 
     363        $this->assertEquals( array(), $data['mycustomsetting'] ); 
     364        $this->assertEquals( array(), get_option( 'mycustomsetting' ) ); 
     365 
     366        // Setting an invalid array. 
     367        $request = new WP_REST_Request( 'PUT', '/wp/v2/settings' ); 
     368        $request->set_param( 'mycustomsetting', array( 'invalid' ) ); 
     369        $response = $this->server->dispatch( $request ); 
     370 
     371        $this->assertErrorResponse( 'rest_invalid_param', $response, 400 ); 
     372        unregister_setting( 'somegroup', 'mycustomsetting' ); 
     373    } 
     374 
     375    public function test_update_item_with_object() { 
     376        register_setting( 'somegroup', 'mycustomsetting', array( 
     377            'show_in_rest' => array( 
     378                'schema' => array( 
     379                    'type'       => 'object', 
     380                    'properties' => array( 
     381                        'a' => array( 
     382                            'type' => 'integer', 
     383                        ), 
     384                    ), 
     385                ), 
     386            ), 
     387            'type'         => 'object', 
     388        ) ); 
     389 
     390        // We have to re-register the route, as the args changes based off registered settings. 
     391        $this->server->override_by_default = true; 
     392        $this->endpoint->register_routes(); 
     393        wp_set_current_user( self::$administrator ); 
     394 
     395        $request = new WP_REST_Request( 'PUT', '/wp/v2/settings' ); 
     396        $request->set_param( 'mycustomsetting', array( 'a' => 1 ) ); 
     397        $response = $this->server->dispatch( $request ); 
     398        $data = $response->get_data(); 
     399        $this->assertEquals( array( 'a' => 1 ), $data['mycustomsetting'] ); 
     400        $this->assertEquals( array( 'a' => 1 ), get_option( 'mycustomsetting' ) ); 
     401 
     402        // Setting an empty object. 
     403        $request = new WP_REST_Request( 'PUT', '/wp/v2/settings' ); 
     404        $request->set_param( 'mycustomsetting', array() ); 
     405        $response = $this->server->dispatch( $request ); 
     406        $data = $response->get_data(); 
     407        $this->assertEquals( array(), $data['mycustomsetting'] ); 
     408        $this->assertEquals( array(), get_option( 'mycustomsetting' ) ); 
     409 
     410        // Setting an invalid object. 
     411        $request = new WP_REST_Request( 'PUT', '/wp/v2/settings' ); 
     412        $request->set_param( 'mycustomsetting', array( 'a' => 'invalid' ) ); 
     413        $response = $this->server->dispatch( $request ); 
     414        $this->assertErrorResponse( 'rest_invalid_param', $response, 400 ); 
     415        unregister_setting( 'somegroup', 'mycustomsetting' ); 
     416    } 
     417 
    245418    public function test_update_item_with_filter() { 
    246419        wp_set_current_user( self::$administrator ); 
Note: See TracChangeset for help on using the changeset viewer.