diff --git a/src/wp-includes/rest-api.php b/src/wp-includes/rest-api.php
index 697a7cc..82d856a 100644
|
a
|
b
|
function rest_validate_value_from_schema( $value, $args, $param = '' ) { |
| 1106 | 1106 | } |
| 1107 | 1107 | |
| 1108 | 1108 | foreach ( $value as $property => $v ) { |
| 1109 | | if ( ! isset( $args['properties'][ $property ] ) ) { |
| 1110 | | continue; |
| 1111 | | } |
| 1112 | | $is_valid = rest_validate_value_from_schema( $v, $args['properties'][ $property ], $param . '[' . $property . ']' ); |
| 1113 | | |
| 1114 | | if ( is_wp_error( $is_valid ) ) { |
| 1115 | | return $is_valid; |
| | 1109 | if ( isset( $args['properties'][ $property ] ) ) { |
| | 1110 | $is_valid = rest_validate_value_from_schema( $v, $args['properties'][ $property ], $param . '[' . $property . ']' ); |
| | 1111 | if ( is_wp_error( $is_valid ) ) { |
| | 1112 | return $is_valid; |
| | 1113 | } |
| | 1114 | } elseif ( isset( $args['additionalProperties'] ) && false === $args['additionalProperties'] ) { |
| | 1115 | return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not a valid property of Object.' ), $property ) ); |
| 1116 | 1116 | } |
| 1117 | 1117 | } |
| 1118 | 1118 | } |
| … |
… |
function rest_sanitize_value_from_schema( $value, $args ) { |
| 1246 | 1246 | } |
| 1247 | 1247 | |
| 1248 | 1248 | foreach ( $value as $property => $v ) { |
| 1249 | | if ( ! isset( $args['properties'][ $property ] ) ) { |
| | 1249 | if ( isset( $args['properties'][ $property ] ) ) { |
| | 1250 | $value[ $property ] = rest_sanitize_value_from_schema( $v, $args['properties'][ $property ] ); |
| | 1251 | } elseif ( isset( $args['additionalProperties'] ) && false === $args['additionalProperties'] ) { |
| 1250 | 1252 | unset( $value[ $property ] ); |
| 1251 | | continue; |
| 1252 | 1253 | } |
| 1253 | | $value[ $property ] = rest_sanitize_value_from_schema( $v, $args['properties'][ $property ] ); |
| 1254 | 1254 | } |
| 1255 | 1255 | |
| 1256 | 1256 | return $value; |
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 2fb5221..f26e876 100644
|
a
|
b
|
abstract class WP_REST_Controller { |
| 545 | 545 | $endpoint_args[ $field_id ]['required'] = true; |
| 546 | 546 | } |
| 547 | 547 | |
| 548 | | foreach ( array( 'type', 'format', 'enum', 'items', 'properties' ) as $schema_prop ) { |
| | 548 | foreach ( array( 'type', 'format', 'enum', 'items', 'properties', 'additionalProperties' ) as $schema_prop ) { |
| 549 | 549 | if ( isset( $params[ $schema_prop ] ) ) { |
| 550 | 550 | $endpoint_args[ $field_id ][ $schema_prop ] = $params[ $schema_prop ]; |
| 551 | 551 | } |
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 21e467f..0a612cd 100644
|
a
|
b
|
class WP_REST_Settings_Controller extends WP_REST_Controller { |
| 248 | 248 | continue; |
| 249 | 249 | } |
| 250 | 250 | |
| | 251 | $rest_args['schema'] = $this->add_additional_properties_false_recursive( $rest_args['schema'] ); |
| | 252 | |
| 251 | 253 | $rest_options[ $rest_args['name'] ] = $rest_args; |
| 252 | 254 | } |
| 253 | 255 | |
| … |
… |
class WP_REST_Settings_Controller extends WP_REST_Controller { |
| 301 | 303 | } |
| 302 | 304 | return rest_parse_request_arg( $value, $request, $param ); |
| 303 | 305 | } |
| | 306 | |
| | 307 | /** |
| | 308 | * Recursively add additionalProperties = false to all objects in a schema. |
| | 309 | * |
| | 310 | * @since 4.9.0 |
| | 311 | * |
| | 312 | * @param array $schema The schema array. |
| | 313 | * @return array |
| | 314 | */ |
| | 315 | protected function add_additional_properties_false_recursive( $schema ) { |
| | 316 | switch ( $schema['type'] ) { |
| | 317 | case 'object': |
| | 318 | foreach ( $schema['properties'] as $key => $child_schema ) { |
| | 319 | $schema['properties'][ $key ] = $this->add_additional_properties_false_recursive( $child_schema ); |
| | 320 | } |
| | 321 | $schema['additionalProperties'] = false; |
| | 322 | break; |
| | 323 | case 'array': |
| | 324 | $schema['items'] = $this->add_additional_properties_false_recursive( $schema['items'] ); |
| | 325 | break; |
| | 326 | } |
| | 327 | |
| | 328 | return $schema; |
| | 329 | } |
| 304 | 330 | } |
diff --git a/tests/phpunit/tests/rest-api/rest-schema-sanitization.php b/tests/phpunit/tests/rest-api/rest-schema-sanitization.php
index cb224b3..b6cf1e8 100644
|
a
|
b
|
class WP_Test_REST_Schema_Sanitization extends WP_UnitTestCase { |
| 157 | 157 | ); |
| 158 | 158 | $this->assertEquals( array( 'a' => 1 ), rest_sanitize_value_from_schema( array( 'a' => 1 ), $schema ) ); |
| 159 | 159 | $this->assertEquals( array( 'a' => 1 ), rest_sanitize_value_from_schema( array( 'a' => '1' ), $schema ) ); |
| | 160 | $this->assertEquals( array( 'a' => 1, 'b' => 1 ), rest_sanitize_value_from_schema( array( 'a' => '1', 'b' => 1 ), $schema ) ); |
| | 161 | } |
| | 162 | |
| | 163 | public function test_type_object_strips_additional_properties() { |
| | 164 | $schema = array( |
| | 165 | 'type' => 'object', |
| | 166 | 'properties' => array( |
| | 167 | 'a' => array( |
| | 168 | 'type' => 'number', |
| | 169 | ), |
| | 170 | ), |
| | 171 | 'additionalProperties' => false, |
| | 172 | ); |
| | 173 | $this->assertEquals( array( 'a' => 1 ), rest_sanitize_value_from_schema( array( 'a' => 1 ), $schema ) ); |
| | 174 | $this->assertEquals( array( 'a' => 1 ), rest_sanitize_value_from_schema( array( 'a' => '1' ), $schema ) ); |
| | 175 | $this->assertEquals( array( 'a' => 1 ), rest_sanitize_value_from_schema( array( 'a' => '1', 'b' => 1 ), $schema ) ); |
| 160 | 176 | } |
| 161 | 177 | |
| 162 | 178 | public function test_type_object_nested() { |
| … |
… |
class WP_Test_REST_Schema_Sanitization extends WP_UnitTestCase { |
| 195 | 211 | 'a' => array( |
| 196 | 212 | 'b' => 1, |
| 197 | 213 | 'c' => 3, |
| | 214 | 'd' => '1', |
| 198 | 215 | ), |
| | 216 | 'b' => 1, |
| 199 | 217 | ), |
| 200 | 218 | rest_sanitize_value_from_schema( |
| 201 | 219 | array( |
diff --git a/tests/phpunit/tests/rest-api/rest-schema-validation.php b/tests/phpunit/tests/rest-api/rest-schema-validation.php
index 9932538..dc983b2 100644
|
a
|
b
|
class WP_Test_REST_Schema_Validation extends WP_UnitTestCase { |
| 186 | 186 | 'type' => 'object', |
| 187 | 187 | 'properties' => array( |
| 188 | 188 | 'a' => array( |
| 189 | | 'type' => 'number' |
| | 189 | 'type' => 'number', |
| 190 | 190 | ), |
| 191 | 191 | ), |
| 192 | 192 | ); |
| 193 | 193 | $this->assertTrue( rest_validate_value_from_schema( array( 'a' => 1 ), $schema ) ); |
| | 194 | $this->assertTrue( rest_validate_value_from_schema( array( 'a' => 1, 'b' => 2 ), $schema ) ); |
| 194 | 195 | $this->assertWPError( rest_validate_value_from_schema( array( 'a' => 'invalid' ), $schema ) ); |
| 195 | 196 | } |
| 196 | 197 | |
| | 198 | public function test_type_object_additional_properties_false() { |
| | 199 | $schema = array( |
| | 200 | 'type' => 'object', |
| | 201 | 'properties' => array( |
| | 202 | 'a' => array( |
| | 203 | 'type' => 'number', |
| | 204 | ), |
| | 205 | ), |
| | 206 | 'additionalProperties' => false, |
| | 207 | ); |
| | 208 | $this->assertTrue( rest_validate_value_from_schema( array( 'a' => 1 ), $schema ) ); |
| | 209 | $this->assertWPError( rest_validate_value_from_schema( array( 'a' => 1, 'b' => 2 ), $schema ) ); |
| | 210 | } |
| | 211 | |
| 197 | 212 | public function test_type_object_nested() { |
| 198 | 213 | $schema = array( |
| 199 | 214 | 'type' => 'object', |
diff --git a/tests/phpunit/tests/rest-api/rest-settings-controller.php b/tests/phpunit/tests/rest-api/rest-settings-controller.php
index b4d7c68..d91d8ba 100644
|
a
|
b
|
class WP_Test_REST_Settings_Controller extends WP_Test_REST_Controller_Testcase |
| 190 | 190 | 'type' => 'object', |
| 191 | 191 | ) ); |
| 192 | 192 | |
| | 193 | // We have to re-register the route, as the args changes based off registered settings. |
| | 194 | $this->server->override_by_default = true; |
| | 195 | $this->endpoint->register_routes(); |
| | 196 | |
| 193 | 197 | // Object is cast to correct types. |
| 194 | 198 | update_option( 'mycustomsetting', array( 'a' => '1' ) ); |
| 195 | 199 | $request = new WP_REST_Request( 'GET', '/wp/v2/settings' ); |
| … |
… |
class WP_Test_REST_Settings_Controller extends WP_Test_REST_Controller_Testcase |
| 209 | 213 | $request = new WP_REST_Request( 'GET', '/wp/v2/settings' ); |
| 210 | 214 | $response = $this->server->dispatch( $request ); |
| 211 | 215 | $data = $response->get_data(); |
| 212 | | $this->assertEquals( array( 'a' => 1 ), $data['mycustomsetting'] ); |
| | 216 | $this->assertEquals( null, $data['mycustomsetting'] ); |
| 213 | 217 | |
| 214 | 218 | unregister_setting( 'somegroup', 'mycustomsetting' ); |
| 215 | 219 | } |
| … |
… |
class WP_Test_REST_Settings_Controller extends WP_Test_REST_Controller_Testcase |
| 372 | 376 | unregister_setting( 'somegroup', 'mycustomsetting' ); |
| 373 | 377 | } |
| 374 | 378 | |
| | 379 | public function test_update_item_with_nested_object() { |
| | 380 | register_setting( 'somegroup', 'mycustomsetting', array( |
| | 381 | 'show_in_rest' => array( |
| | 382 | 'schema' => array( |
| | 383 | 'type' => 'object', |
| | 384 | 'properties' => array( |
| | 385 | 'a' => array( |
| | 386 | 'type' => 'object', |
| | 387 | 'properties' => array( |
| | 388 | 'b' => array( |
| | 389 | 'type' => 'number', |
| | 390 | ), |
| | 391 | ), |
| | 392 | ), |
| | 393 | ), |
| | 394 | ), |
| | 395 | ), |
| | 396 | 'type' => 'object', |
| | 397 | ) ); |
| | 398 | |
| | 399 | // We have to re-register the route, as the args changes based off registered settings. |
| | 400 | $this->server->override_by_default = true; |
| | 401 | $this->endpoint->register_routes(); |
| | 402 | wp_set_current_user( self::$administrator ); |
| | 403 | |
| | 404 | $request = new WP_REST_Request( 'PUT', '/wp/v2/settings' ); |
| | 405 | $request->set_param( 'mycustomsetting', array( 'a' => array( 'b' => 1, 'c' => 1 ) ) ); |
| | 406 | $response = $this->server->dispatch( $request ); |
| | 407 | $this->assertErrorResponse( 'rest_invalid_param', $response, 400 ); |
| | 408 | } |
| | 409 | |
| 375 | 410 | public function test_update_item_with_object() { |
| 376 | 411 | register_setting( 'somegroup', 'mycustomsetting', array( |
| 377 | 412 | 'show_in_rest' => array( |
| … |
… |
class WP_Test_REST_Settings_Controller extends WP_Test_REST_Controller_Testcase |
| 407 | 442 | $this->assertEquals( array(), $data['mycustomsetting'] ); |
| 408 | 443 | $this->assertEquals( array(), get_option( 'mycustomsetting' ) ); |
| 409 | 444 | |
| | 445 | // Provide more keys. |
| | 446 | $request = new WP_REST_Request( 'PUT', '/wp/v2/settings' ); |
| | 447 | $request->set_param( 'mycustomsetting', array( 'a' => 1, 'b' => 2 ) ); |
| | 448 | $response = $this->server->dispatch( $request ); |
| | 449 | |
| | 450 | $this->assertErrorResponse( 'rest_invalid_param', $response, 400 ); |
| | 451 | |
| 410 | 452 | // Setting an invalid object. |
| 411 | 453 | $request = new WP_REST_Request( 'PUT', '/wp/v2/settings' ); |
| 412 | 454 | $request->set_param( 'mycustomsetting', array( 'a' => 'invalid' ) ); |