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' ) ); |