diff --git a/src/wp-includes/formatting.php b/src/wp-includes/formatting.php index bd7d458baa..4fb4135451 100644 --- a/src/wp-includes/formatting.php +++ b/src/wp-includes/formatting.php @@ -5406,6 +5406,33 @@ function wp_unslash( $value ) { return stripslashes_deep( $value ); } +/** + * Adds slashes to only string values in an array of values. + * + * This should be used when preparing data for core APIs that expect slashed data. + * This should not be used to escape data going directly into an SQL query. + * + * @since 5.3.0 + * + * @param mixed $value Scalar or array of scalars. + * @return mixed Slashes $value + */ +function wp_slash_strings_only( $value ) { + return map_deep( $value, 'addslashes_strings_only' ); +} + +/** + * Adds slashes only if the provided value is a string. + * + * @since 5.3.0 + * + * @param mixed $value + * @return mixed + */ +function addslashes_strings_only( $value ) { + return is_string( $value ) ? addslashes( $value ) : $value; +} + /** * Extract and return the first URL from passed content. * diff --git a/src/wp-includes/rest-api/fields/class-wp-rest-meta-fields.php b/src/wp-includes/rest-api/fields/class-wp-rest-meta-fields.php index b3ea89a86e..411fc5460c 100644 --- a/src/wp-includes/rest-api/fields/class-wp-rest-meta-fields.php +++ b/src/wp-includes/rest-api/fields/class-wp-rest-meta-fields.php @@ -365,7 +365,7 @@ abstract class WP_REST_Meta_Fields { } } - if ( ! update_metadata( $meta_type, $object_id, wp_slash( $meta_key ), wp_slash( $value ) ) ) { + if ( ! update_metadata( $meta_type, $object_id, wp_slash( $meta_key ), wp_slash_strings_only( $value ) ) ) { return new WP_Error( 'rest_meta_database_error', /* translators: %s: Custom field key. */ diff --git a/tests/phpunit/tests/rest-api/rest-post-meta-fields.php b/tests/phpunit/tests/rest-api/rest-post-meta-fields.php index 2007e77ef2..39b167e782 100644 --- a/tests/phpunit/tests/rest-api/rest-post-meta-fields.php +++ b/tests/phpunit/tests/rest-api/rest-post-meta-fields.php @@ -2264,6 +2264,340 @@ class WP_Test_REST_Post_Meta_Fields extends WP_Test_REST_TestCase { self::assertFalse( $registered ); } + /** + * @ticket 48264 + */ + public function test_update_array_of_ints_meta() { + $this->grant_write_permission(); + register_post_meta( + 'post', + 'items', + array( + 'single' => true, + 'type' => 'array', + 'show_in_rest' => array( + 'schema' => array( + 'items' => array( + 'type' => 'integer', + ), + ), + ), + ) + ); + + $request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/posts/%d', self::$post_id ) ); + $request->set_body_params( + array( + 'meta' => array( + 'items' => array( 1, 2, 3 ), + ), + ) + ); + + rest_get_server()->dispatch( $request ); + $response = rest_get_server()->dispatch( $request ); + $this->assertEquals( 200, $response->get_status() ); + } + + /** + * @ticket 48264 + */ + public function test_update_array_of_ints_meta_stored_strings_are_updated() { + $this->grant_write_permission(); + register_post_meta( + 'post', + 'items', + array( + 'single' => true, + 'type' => 'array', + 'show_in_rest' => array( + 'schema' => array( + 'items' => array( + 'type' => 'integer', + ), + ), + ), + ) + ); + + update_post_meta( self::$post_id, 'items', array( '1', '2', '3' ) ); + $response = rest_get_server()->dispatch( new WP_REST_Request( 'GET', sprintf( '/wp/v2/posts/%d', self::$post_id ) ) ); + $this->assertEquals( array( 1, 2, 3 ), $response->get_data()['meta']['items'] ); + + $request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/posts/%d', self::$post_id ) ); + $request->set_body_params( + array( + 'meta' => array( + 'items' => array( 1, 2, 3 ), + ), + ) + ); + + $response = rest_get_server()->dispatch( $request ); + $this->assertEquals( 200, $response->get_status() ); + $this->assertSame( array( 1, 2, 3 ), get_post_meta( self::$post_id, 'items', true ) ); + } + + /** + * @ticket 48264 + */ + public function test_update_array_of_ints_meta_string_request_data_is_set_as_ints() { + $this->grant_write_permission(); + register_post_meta( + 'post', + 'items', + array( + 'single' => true, + 'type' => 'array', + 'show_in_rest' => array( + 'schema' => array( + 'items' => array( + 'type' => 'integer', + ), + ), + ), + ) + ); + + update_post_meta( self::$post_id, 'items', array( 1, 2, 3 ) ); + + $request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/posts/%d', self::$post_id ) ); + $request->set_body_params( + array( + 'meta' => array( + 'items' => array( '1', '2', '3' ), + ), + ) + ); + + $response = rest_get_server()->dispatch( $request ); + $this->assertEquals( 200, $response->get_status() ); + $this->assertSame( array( 1, 2, 3 ), get_post_meta( self::$post_id, 'items', true ) ); + } + + /** + * @ticket 48264 + */ + public function test_update_array_of_ints_meta_string_request_data_and_string_stored_data() { + $this->grant_write_permission(); + register_post_meta( + 'post', + 'items', + array( + 'single' => true, + 'type' => 'array', + 'show_in_rest' => array( + 'schema' => array( + 'items' => array( + 'type' => 'integer', + ), + ), + ), + ) + ); + + update_post_meta( self::$post_id, 'items', array( '1', '2', '3' ) ); + + $request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/posts/%d', self::$post_id ) ); + $request->set_body_params( + array( + 'meta' => array( + 'items' => array( '1', '2', '3' ), + ), + ) + ); + + $response = rest_get_server()->dispatch( $request ); + $this->assertEquals( 200, $response->get_status() ); + $this->assertSame( array( 1, 2, 3 ), get_post_meta( self::$post_id, 'items', true ) ); + } + + /** + * @ticket 48264 + */ + public function test_update_array_of_bools_meta() { + $this->grant_write_permission(); + register_post_meta( + 'post', + 'items', + array( + 'single' => true, + 'type' => 'array', + 'show_in_rest' => array( + 'schema' => array( + 'items' => array( + 'type' => 'boolean', + ), + ), + ), + ) + ); + + $request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/posts/%d', self::$post_id ) ); + $request->set_body_params( + array( + 'meta' => array( + 'items' => array( true, false ), + ), + ) + ); + + rest_get_server()->dispatch( $request ); + $response = rest_get_server()->dispatch( $request ); + $this->assertEquals( 200, $response->get_status() ); + } + + /** + * @ticket 48264 + */ + public function test_update_array_of_bools_meta_stored_strings_are_updated() { + $this->grant_write_permission(); + register_post_meta( + 'post', + 'items', + array( + 'single' => true, + 'type' => 'array', + 'show_in_rest' => array( + 'schema' => array( + 'items' => array( + 'type' => 'boolean', + ), + ), + ), + ) + ); + + update_post_meta( self::$post_id, 'items', array( '1', '0' ) ); + + $response = rest_get_server()->dispatch( new WP_REST_Request( 'GET', sprintf( '/wp/v2/posts/%d', self::$post_id ) ) ); + $this->assertEquals( array( true, false ), $response->get_data()['meta']['items'] ); + + $request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/posts/%d', self::$post_id ) ); + $request->set_body_params( + array( + 'meta' => array( + 'items' => array( true, false ), + ), + ) + ); + + $response = rest_get_server()->dispatch( $request ); + $this->assertEquals( 200, $response->get_status() ); + $this->assertSame( array( true, false ), get_post_meta( self::$post_id, 'items', true ) ); + } + + /** + * @ticket 48264 + */ + public function test_update_array_of_bools_meta_string_request_data_is_set_as_bools() { + $this->grant_write_permission(); + register_post_meta( + 'post', + 'items', + array( + 'single' => true, + 'type' => 'array', + 'show_in_rest' => array( + 'schema' => array( + 'items' => array( + 'type' => 'boolean', + ), + ), + ), + ) + ); + + update_post_meta( self::$post_id, 'items', array( true, false ) ); + + $request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/posts/%d', self::$post_id ) ); + $request->set_body_params( + array( + 'meta' => array( + 'items' => array( '1', '0' ), + ), + ) + ); + + $response = rest_get_server()->dispatch( $request ); + $this->assertEquals( 200, $response->get_status() ); + $this->assertSame( array( true, false ), get_post_meta( self::$post_id, 'items', true ) ); + } + + /** + * @ticket 48264 + */ + public function test_update_array_of_bools_meta_string_request_data_and_string_stored_data() { + $this->grant_write_permission(); + register_post_meta( + 'post', + 'items', + array( + 'single' => true, + 'type' => 'array', + 'show_in_rest' => array( + 'schema' => array( + 'items' => array( + 'type' => 'boolean', + ), + ), + ), + ) + ); + + update_post_meta( self::$post_id, 'items', array( '1', '0' ) ); + + $request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/posts/%d', self::$post_id ) ); + $request->set_body_params( + array( + 'meta' => array( + 'items' => array( '1', '0' ), + ), + ) + ); + + $response = rest_get_server()->dispatch( $request ); + $this->assertEquals( 200, $response->get_status() ); + $this->assertSame( array( true, false ), get_post_meta( self::$post_id, 'items', true ) ); + } + + /** + * @ticket 48264 + */ + public function test_update_array_of_bools_with_string_values_stored_and_opposite_request_data() { + $this->grant_write_permission(); + register_post_meta( + 'post', + 'items', + array( + 'single' => true, + 'type' => 'array', + 'show_in_rest' => array( + 'schema' => array( + 'items' => array( + 'type' => 'boolean', + ), + ), + ), + ) + ); + + update_post_meta( self::$post_id, 'items', array( '1', '0' ) ); + + $request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/posts/%d', self::$post_id ) ); + $request->set_body_params( + array( + 'meta' => array( + 'items' => array( false, true ), + ), + ) + ); + + $response = rest_get_server()->dispatch( $request ); + $this->assertEquals( 200, $response->get_status() ); + $this->assertSame( array( false, true ), get_post_meta( self::$post_id, 'items', true ) ); + } + /** * Internal function used to disable an insert query which * will trigger a wpdb error for testing purposes.