mirror of
https://github.com/gosticks/wordpress-develop.git
synced 2026-06-28 14:20:15 +00:00
REST API: Validate and Sanitize registered meta based off the schema.
With the addition of Array support in our schema validation functions, it's now possible to use these in the meta validation and sanitization steps. Also, this increases the test coverage of using registered via meta the API significantly. Fixes #38531. Props rachelbaker, tharsheblows. git-svn-id: https://develop.svn.wordpress.org/trunk@39222 602fd350-edb4-49c9-b593-d223f7449a82
This commit is contained in:
@@ -998,6 +998,9 @@ function rest_validate_value_from_schema( $value, $args, $param = '' ) {
|
||||
if ( ! is_array( $value ) ) {
|
||||
$value = preg_split( '/[\s,]+/', $value );
|
||||
}
|
||||
if ( ! wp_is_numeric_array( $value ) ) {
|
||||
return new WP_Error( 'rest_invalid_param', sprintf( /* translators: 1: parameter, 2: type name */ __( '%1$s is not of type %2$s.' ), $param, 'array' ) );
|
||||
}
|
||||
foreach ( $value as $index => $v ) {
|
||||
$is_valid = rest_validate_value_from_schema( $v, $args['items'], $param . '[' . $index . ']' );
|
||||
if ( is_wp_error( $is_valid ) ) {
|
||||
@@ -1107,6 +1110,9 @@ function rest_sanitize_value_from_schema( $value, $args ) {
|
||||
foreach ( $value as $index => $v ) {
|
||||
$value[ $index ] = rest_sanitize_value_from_schema( $v, $args['items'] );
|
||||
}
|
||||
// Normalize to numeric array so nothing unexpected
|
||||
// is in the keys.
|
||||
$value = array_values( $value );
|
||||
return $value;
|
||||
}
|
||||
if ( 'integer' === $args['type'] ) {
|
||||
@@ -1140,5 +1146,9 @@ function rest_sanitize_value_from_schema( $value, $args ) {
|
||||
}
|
||||
}
|
||||
|
||||
if ( 'string' === $args['type'] ) {
|
||||
return strval( $value );
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
@@ -84,7 +84,7 @@ abstract class WP_REST_Meta_Fields {
|
||||
$response[ $name ] = $value;
|
||||
}
|
||||
|
||||
return (object) $response;
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -133,10 +133,24 @@ abstract class WP_REST_Meta_Fields {
|
||||
*/
|
||||
if ( is_null( $request[ $name ] ) ) {
|
||||
$result = $this->delete_meta_value( $object_id, $name );
|
||||
} elseif ( $args['single'] ) {
|
||||
$result = $this->update_meta_value( $object_id, $name, $request[ $name ] );
|
||||
if ( is_wp_error( $result ) ) {
|
||||
return $result;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
$is_valid = rest_validate_value_from_schema( $request[ $name ], $args['schema'], 'meta.' . $name );
|
||||
if ( is_wp_error( $is_valid ) ) {
|
||||
$is_valid->add_data( array( 'status' => 400 ) );
|
||||
return $is_valid;
|
||||
}
|
||||
|
||||
$value = rest_sanitize_value_from_schema( $request[ $name ], $args['schema'] );
|
||||
|
||||
if ( $args['single'] ) {
|
||||
$result = $this->update_meta_value( $object_id, $name, $value );
|
||||
} else {
|
||||
$result = $this->update_multi_meta_value( $object_id, $name, $request[ $name ] );
|
||||
$result = $this->update_multi_meta_value( $object_id, $name, $value );
|
||||
}
|
||||
|
||||
if ( is_wp_error( $result ) ) {
|
||||
@@ -319,12 +333,13 @@ abstract class WP_REST_Meta_Fields {
|
||||
$default_args = array(
|
||||
'name' => $name,
|
||||
'single' => $args['single'],
|
||||
'type' => ! empty( $args['type'] ) ? $args['type'] : null,
|
||||
'schema' => array(),
|
||||
'prepare_callback' => array( $this, 'prepare_value' ),
|
||||
);
|
||||
|
||||
$default_schema = array(
|
||||
'type' => null,
|
||||
'type' => $default_args['type'],
|
||||
'description' => empty( $args['description'] ) ? '' : $args['description'],
|
||||
'default' => isset( $args['default'] ) ? $args['default'] : null,
|
||||
);
|
||||
@@ -332,20 +347,18 @@ abstract class WP_REST_Meta_Fields {
|
||||
$rest_args = array_merge( $default_args, $rest_args );
|
||||
$rest_args['schema'] = array_merge( $default_schema, $rest_args['schema'] );
|
||||
|
||||
if ( empty( $rest_args['schema']['type'] ) ) {
|
||||
// Skip over meta fields that don't have a defined type.
|
||||
if ( empty( $args['type'] ) ) {
|
||||
continue;
|
||||
}
|
||||
$type = ! empty( $rest_args['type'] ) ? $rest_args['type'] : null;
|
||||
$type = ! empty( $rest_args['schema']['type'] ) ? $rest_args['schema']['type'] : $type;
|
||||
|
||||
if ( $rest_args['single'] ) {
|
||||
$rest_args['schema']['type'] = $args['type'];
|
||||
} else {
|
||||
$rest_args['schema']['type'] = 'array';
|
||||
$rest_args['schema']['items'] = array(
|
||||
'type' => $args['type'],
|
||||
);
|
||||
}
|
||||
if ( ! in_array( $type, array( 'string', 'boolean', 'integer', 'number' ) ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( empty( $rest_args['single'] ) ) {
|
||||
$rest_args['schema']['items'] = array(
|
||||
'type' => $rest_args['type'],
|
||||
);
|
||||
$rest_args['schema']['type'] = 'array';
|
||||
}
|
||||
|
||||
$registered[ $rest_args['name'] ] = $rest_args;
|
||||
|
||||
@@ -26,24 +26,29 @@ class WP_Test_REST_Post_Meta_Fields extends WP_Test_REST_TestCase {
|
||||
register_meta( 'post', 'test_single', array(
|
||||
'show_in_rest' => true,
|
||||
'single' => true,
|
||||
'type' => 'string',
|
||||
));
|
||||
register_meta( 'post', 'test_multi', array(
|
||||
'show_in_rest' => true,
|
||||
'single' => false,
|
||||
'type' => 'string',
|
||||
));
|
||||
register_meta( 'post', 'test_bad_auth', array(
|
||||
'show_in_rest' => true,
|
||||
'single' => true,
|
||||
'auth_callback' => '__return_false',
|
||||
'type' => 'string',
|
||||
));
|
||||
register_meta( 'post', 'test_bad_auth_multi', array(
|
||||
'show_in_rest' => true,
|
||||
'single' => false,
|
||||
'auth_callback' => '__return_false',
|
||||
'type' => 'string',
|
||||
));
|
||||
register_meta( 'post', 'test_no_rest', array() );
|
||||
register_meta( 'post', 'test_rest_disabled', array(
|
||||
'show_in_rest' => false,
|
||||
'type' => 'string',
|
||||
));
|
||||
register_meta( 'post', 'test_custom_schema', array(
|
||||
'single' => true,
|
||||
@@ -54,9 +59,23 @@ class WP_Test_REST_Post_Meta_Fields extends WP_Test_REST_TestCase {
|
||||
),
|
||||
),
|
||||
));
|
||||
register_meta( 'post', 'test_custom_schema_multi', array(
|
||||
'single' => false,
|
||||
'type' => 'integer',
|
||||
'show_in_rest' => array(
|
||||
'schema' => array(
|
||||
'type' => 'number',
|
||||
),
|
||||
),
|
||||
));
|
||||
register_meta( 'post', 'test_invalid_type', array(
|
||||
'single' => true,
|
||||
'type' => false,
|
||||
'type' => 'lalala',
|
||||
'show_in_rest' => true,
|
||||
));
|
||||
register_meta( 'post', 'test_no_type', array(
|
||||
'single' => true,
|
||||
'type' => null,
|
||||
'show_in_rest' => true,
|
||||
));
|
||||
|
||||
@@ -341,6 +360,24 @@ class WP_Test_REST_Post_Meta_Fields extends WP_Test_REST_TestCase {
|
||||
$wpdb->show_errors = true;
|
||||
}
|
||||
|
||||
public function test_set_value_invalid_type() {
|
||||
$values = get_post_meta( self::$post_id, 'test_invalid_type', false );
|
||||
$this->assertEmpty( $values );
|
||||
|
||||
$this->grant_write_permission();
|
||||
|
||||
$data = array(
|
||||
'meta' => array(
|
||||
'test_invalid_type' => 'test_value',
|
||||
),
|
||||
);
|
||||
$request = new WP_REST_Request( 'POST', sprintf( '/wp/v2/posts/%d', self::$post_id ) );
|
||||
$request->set_body_params( $data );
|
||||
|
||||
$response = $this->server->dispatch( $request );
|
||||
$this->assertEmpty( get_post_meta( self::$post_id, 'test_invalid_type', false ) );
|
||||
}
|
||||
|
||||
public function test_set_value_multiple() {
|
||||
// Ensure no data exists currently.
|
||||
$values = get_post_meta( self::$post_id, 'test_multi', false );
|
||||
@@ -435,6 +472,92 @@ class WP_Test_REST_Post_Meta_Fields extends WP_Test_REST_TestCase {
|
||||
$this->assertEmpty( $meta );
|
||||
}
|
||||
|
||||
public function test_set_value_invalid_value() {
|
||||
register_meta( 'post', 'my_meta_key', array(
|
||||
'show_in_rest' => true,
|
||||
'single' => true,
|
||||
'type' => 'string',
|
||||
));
|
||||
|
||||
$this->grant_write_permission();
|
||||
|
||||
$data = array(
|
||||
'meta' => array(
|
||||
'my_meta_key' => array( 'c', 'n' ),
|
||||
),
|
||||
);
|
||||
$request = new WP_REST_Request( 'POST', sprintf( '/wp/v2/posts/%d', self::$post_id ) );
|
||||
$request->set_body_params( $data );
|
||||
|
||||
$response = $this->server->dispatch( $request );
|
||||
$this->assertErrorResponse( 'rest_invalid_param', $response, 400 );
|
||||
}
|
||||
|
||||
public function test_set_value_invalid_value_multiple() {
|
||||
register_meta( 'post', 'my_meta_key', array(
|
||||
'show_in_rest' => true,
|
||||
'single' => false,
|
||||
'type' => 'string',
|
||||
));
|
||||
|
||||
$this->grant_write_permission();
|
||||
|
||||
$data = array(
|
||||
'meta' => array(
|
||||
'my_meta_key' => array( array( 'a' ) ),
|
||||
),
|
||||
);
|
||||
$request = new WP_REST_Request( 'POST', sprintf( '/wp/v2/posts/%d', self::$post_id ) );
|
||||
$request->set_body_params( $data );
|
||||
|
||||
$response = $this->server->dispatch( $request );
|
||||
$this->assertErrorResponse( 'rest_invalid_param', $response, 400 );
|
||||
}
|
||||
|
||||
public function test_set_value_sanitized() {
|
||||
register_meta( 'post', 'my_meta_key', array(
|
||||
'show_in_rest' => true,
|
||||
'single' => true,
|
||||
'type' => 'integer',
|
||||
));
|
||||
|
||||
$this->grant_write_permission();
|
||||
|
||||
$data = array(
|
||||
'meta' => array(
|
||||
'my_meta_key' => '1', // Set to a string.
|
||||
),
|
||||
);
|
||||
$request = new WP_REST_Request( 'POST', sprintf( '/wp/v2/posts/%d', self::$post_id ) );
|
||||
$request->set_body_params( $data );
|
||||
|
||||
$response = $this->server->dispatch( $request );
|
||||
$data = $response->get_data();
|
||||
$this->assertEquals( 1, $data['meta']['my_meta_key'] );
|
||||
}
|
||||
|
||||
public function test_set_value_csv() {
|
||||
register_meta( 'post', 'my_meta_key', array(
|
||||
'show_in_rest' => true,
|
||||
'single' => false,
|
||||
'type' => 'integer',
|
||||
));
|
||||
|
||||
$this->grant_write_permission();
|
||||
|
||||
$data = array(
|
||||
'meta' => array(
|
||||
'my_meta_key' => '1,2,3', // Set to a string.
|
||||
),
|
||||
);
|
||||
$request = new WP_REST_Request( 'POST', sprintf( '/wp/v2/posts/%d', self::$post_id ) );
|
||||
$request->set_body_params( $data );
|
||||
|
||||
$response = $this->server->dispatch( $request );
|
||||
$data = $response->get_data();
|
||||
$this->assertEquals( array( 1, 2, 3 ), $data['meta']['my_meta_key'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends test_set_value_multiple
|
||||
*/
|
||||
@@ -485,6 +608,79 @@ class WP_Test_REST_Post_Meta_Fields extends WP_Test_REST_TestCase {
|
||||
$this->assertErrorResponse( 'rest_meta_database_error', $response, 500 );
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends test_get_value
|
||||
*/
|
||||
public function test_set_value_single_custom_schema() {
|
||||
// Ensure no data exists currently.
|
||||
$values = get_post_meta( self::$post_id, 'test_custom_schema', false );
|
||||
$this->assertEmpty( $values );
|
||||
|
||||
$this->grant_write_permission();
|
||||
|
||||
$data = array(
|
||||
'meta' => array(
|
||||
'test_custom_schema' => 3,
|
||||
),
|
||||
);
|
||||
$request = new WP_REST_Request( 'POST', sprintf( '/wp/v2/posts/%d', self::$post_id ) );
|
||||
$request->set_body_params( $data );
|
||||
|
||||
$response = $this->server->dispatch( $request );
|
||||
$this->assertEquals( 200, $response->get_status() );
|
||||
|
||||
$meta = get_post_meta( self::$post_id, 'test_custom_schema', false );
|
||||
$this->assertNotEmpty( $meta );
|
||||
$this->assertCount( 1, $meta );
|
||||
$this->assertEquals( 3, $meta[0] );
|
||||
|
||||
$data = $response->get_data();
|
||||
$meta = (array) $data['meta'];
|
||||
$this->assertArrayHasKey( 'test_custom_schema', $meta );
|
||||
$this->assertEquals( 3, $meta['test_custom_schema'] );
|
||||
}
|
||||
|
||||
public function test_set_value_multiple_custom_schema() {
|
||||
// Ensure no data exists currently.
|
||||
$values = get_post_meta( self::$post_id, 'test_custom_schema_multi', false );
|
||||
$this->assertEmpty( $values );
|
||||
|
||||
$this->grant_write_permission();
|
||||
|
||||
$data = array(
|
||||
'meta' => array(
|
||||
'test_custom_schema_multi' => array( 2 ),
|
||||
),
|
||||
);
|
||||
$request = new WP_REST_Request( 'POST', sprintf( '/wp/v2/posts/%d', self::$post_id ) );
|
||||
$request->set_body_params( $data );
|
||||
|
||||
$response = $this->server->dispatch( $request );
|
||||
$this->assertEquals( 200, $response->get_status() );
|
||||
|
||||
$meta = get_post_meta( self::$post_id, 'test_custom_schema_multi', false );
|
||||
$this->assertNotEmpty( $meta );
|
||||
$this->assertCount( 1, $meta );
|
||||
$this->assertEquals( 2, $meta[0] );
|
||||
|
||||
// Add another value.
|
||||
$data = array(
|
||||
'meta' => array(
|
||||
'test_custom_schema_multi' => array( 2, 8 ),
|
||||
),
|
||||
);
|
||||
$request->set_body_params( $data );
|
||||
|
||||
$response = $this->server->dispatch( $request );
|
||||
$this->assertEquals( 200, $response->get_status() );
|
||||
|
||||
$meta = get_post_meta( self::$post_id, 'test_custom_schema_multi', false );
|
||||
$this->assertNotEmpty( $meta );
|
||||
$this->assertCount( 2, $meta );
|
||||
$this->assertContains( 2, $meta );
|
||||
$this->assertContains( 8, $meta );
|
||||
}
|
||||
|
||||
public function test_remove_multi_value_db_error() {
|
||||
add_post_meta( self::$post_id, 'test_multi', 'val1' );
|
||||
$values = get_post_meta( self::$post_id, 'test_multi', false );
|
||||
@@ -515,6 +711,7 @@ class WP_Test_REST_Post_Meta_Fields extends WP_Test_REST_TestCase {
|
||||
$this->assertErrorResponse( 'rest_meta_database_error', $response, 500 );
|
||||
}
|
||||
|
||||
|
||||
public function test_delete_value() {
|
||||
add_post_meta( self::$post_id, 'test_single', 'val1' );
|
||||
$current = get_post_meta( self::$post_id, 'test_single', true );
|
||||
@@ -618,6 +815,7 @@ class WP_Test_REST_Post_Meta_Fields extends WP_Test_REST_TestCase {
|
||||
$this->assertArrayNotHasKey( 'test_no_rest', $meta_schema );
|
||||
$this->assertArrayNotHasKey( 'test_rest_disabled', $meta_schema );
|
||||
$this->assertArrayNotHasKey( 'test_invalid_type', $meta_schema );
|
||||
$this->assertArrayNotHasKey( 'test_no_type', $meta_schema );
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -76,6 +76,20 @@ class WP_Test_REST_Schema_Sanitization extends WP_UnitTestCase {
|
||||
$this->assertEquals( array( 1 ), rest_sanitize_value_from_schema( array( '1' ), $schema ) );
|
||||
}
|
||||
|
||||
public function test_type_array_nested() {
|
||||
$schema = array(
|
||||
'type' => 'array',
|
||||
'items' => array(
|
||||
'type' => 'array',
|
||||
'items' => array(
|
||||
'type' => 'number',
|
||||
),
|
||||
),
|
||||
);
|
||||
$this->assertEquals( array( array( 1 ), array( 2 ) ), rest_sanitize_value_from_schema( array( array( 1 ), array( 2 ) ), $schema ) );
|
||||
$this->assertEquals( array( array( 1 ), array( 2 ) ), rest_sanitize_value_from_schema( array( array( '1' ), array( '2' ) ), $schema ) );
|
||||
}
|
||||
|
||||
public function test_type_array_as_csv() {
|
||||
$schema = array(
|
||||
'type' => 'array',
|
||||
@@ -110,4 +124,32 @@ class WP_Test_REST_Schema_Sanitization extends WP_UnitTestCase {
|
||||
$this->assertEquals( array( 'ribs', 'chicken' ), rest_sanitize_value_from_schema( 'ribs,chicken', $schema ) );
|
||||
$this->assertEquals( array( 'chicken', 'coleslaw' ), rest_sanitize_value_from_schema( 'chicken,coleslaw', $schema ) );
|
||||
}
|
||||
|
||||
public function test_type_array_is_associative() {
|
||||
$schema = array(
|
||||
'type' => 'array',
|
||||
'items' => array(
|
||||
'type' => 'string',
|
||||
),
|
||||
);
|
||||
$this->assertEquals( array( '1', '2' ), rest_sanitize_value_from_schema( array( 'first' => '1', 'second' => '2' ), $schema ) );
|
||||
}
|
||||
|
||||
public function test_type_unknown() {
|
||||
$schema = array(
|
||||
'type' => 'lalala',
|
||||
);
|
||||
$this->assertEquals( 'Best lyrics', rest_sanitize_value_from_schema( 'Best lyrics', $schema ) );
|
||||
$this->assertEquals( 1.10, rest_sanitize_value_from_schema( 1.10, $schema ) );
|
||||
$this->assertEquals( 1, rest_sanitize_value_from_schema( 1, $schema ) );
|
||||
}
|
||||
|
||||
public function test_no_type() {
|
||||
$schema = array(
|
||||
'type' => null,
|
||||
);
|
||||
$this->assertEquals( 'Nothing', rest_sanitize_value_from_schema( 'Nothing', $schema ) );
|
||||
$this->assertEquals( 1.10, rest_sanitize_value_from_schema( 1.10, $schema ) );
|
||||
$this->assertEquals( 1, rest_sanitize_value_from_schema( 1, $schema ) );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,6 +104,19 @@ class WP_Test_REST_Schema_Validation extends WP_UnitTestCase {
|
||||
$this->assertWPError( rest_validate_value_from_schema( array( true ), $schema ) );
|
||||
}
|
||||
|
||||
public function test_type_array_nested() {
|
||||
$schema = array(
|
||||
'type' => 'array',
|
||||
'items' => array(
|
||||
'type' => 'array',
|
||||
'items' => array(
|
||||
'type' => 'number',
|
||||
),
|
||||
),
|
||||
);
|
||||
$this->assertTrue( rest_validate_value_from_schema( array( array( 1 ), array( 2 ) ), $schema ) );
|
||||
}
|
||||
|
||||
public function test_type_array_as_csv() {
|
||||
$schema = array(
|
||||
'type' => 'array',
|
||||
@@ -139,4 +152,23 @@ class WP_Test_REST_Schema_Validation extends WP_UnitTestCase {
|
||||
$this->assertTrue( rest_validate_value_from_schema( 'ribs,chicken', $schema ) );
|
||||
$this->assertWPError( rest_validate_value_from_schema( 'chicken,coleslaw', $schema ) );
|
||||
}
|
||||
|
||||
public function test_type_array_is_associative() {
|
||||
$schema = array(
|
||||
'type' => 'array',
|
||||
'items' => array(
|
||||
'type' => 'string',
|
||||
),
|
||||
);
|
||||
$this->assertWPError( rest_validate_value_from_schema( array( 'first' => '1', 'second' => '2' ), $schema ) );
|
||||
}
|
||||
|
||||
public function test_type_unknown() {
|
||||
$schema = array(
|
||||
'type' => 'lalala',
|
||||
);
|
||||
$this->assertTrue( rest_validate_value_from_schema( 'Best lyrics', $schema ) );
|
||||
$this->assertTrue( rest_validate_value_from_schema( 1, $schema ) );
|
||||
$this->assertTrue( rest_validate_value_from_schema( array(), $schema ) );
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user