WordPress.org

Make WordPress Core

Ticket #38583: 38583-settings.diff

File 38583-settings.diff, 10.9 KB (added by joehoyle, 2 years ago)
  • src/wp-includes/rest-api/endpoints/class-wp-rest-controller.php

    diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-controller.php
    index 78c3567..2fb5221 100644
    a b abstract class WP_REST_Controller { 
    545545                                $endpoint_args[ $field_id ]['required'] = true;
    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 ];
    551551                                }
  • src/wp-includes/rest-api/endpoints/class-wp-rest-settings-controller.php

    diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-settings-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-settings-controller.php
    index 1618e35..21e467f 100644
    a b class WP_REST_Settings_Controller extends WP_REST_Controller { 
    119119         * @return mixed The prepared value.
    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
    141131        /**
    class WP_REST_Settings_Controller extends WP_REST_Controller { 
    148138         */
    149139        public function update_item( $request ) {
    150140                $options = $this->get_registered_options();
     141
    151142                $params  = $request->get_params();
    152143
    153144                foreach ( $options as $name => $args ) {
    class WP_REST_Settings_Controller extends WP_REST_Controller { 
    187178                                 *
    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 )
    198189                                        );
    class WP_REST_Settings_Controller extends WP_REST_Controller { 
    253244                         * Whitelist the supported types for settings, as we don't want invalid types
    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                        }
    259250
  • tests/phpunit/tests/rest-api/rest-settings-controller.php

    diff --git a/tests/phpunit/tests/rest-api/rest-settings-controller.php b/tests/phpunit/tests/rest-api/rest-settings-controller.php
    index 853a013..b4d7c68 100644
    a b class WP_Test_REST_Settings_Controller extends WP_Test_REST_Controller_Testcase 
    127127                unregister_setting( 'somegroup', 'mycustomsetting' );
    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 ) {
    132219                        case 'mycustomsetting1':
    class WP_Test_REST_Settings_Controller extends WP_Test_REST_Controller_Testcase 
    215302                $response = $this->server->dispatch( $request );
    216303                $data = $response->get_data();
    217304                $this->assertEquals( null, $data['mycustomsettinginrest'] );
     305                unregister_setting( 'somegroup', 'mycustomsetting' );
    218306        }
    219307
    220308
    class WP_Test_REST_Settings_Controller extends WP_Test_REST_Controller_Testcase 
    242330                return false;
    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 );
    247420