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 71be09ec423b6..643c6a570e077 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 @@ -161,9 +161,10 @@ public function update_value( $meta, $object_id ) { $args = $this->get_registered_fields()[ $meta_key ]; if ( $args['single'] ) { - $current = get_metadata( $this->get_meta_type(), $object_id, $meta_key, true ); + // Only a stored value needs protecting, so a key with nothing stored is not validated. + $current = get_metadata_raw( $this->get_meta_type(), $object_id, $meta_key, true ); - if ( is_wp_error( rest_validate_value_from_schema( $current, $args['schema'] ) ) ) { + if ( null !== $current && is_wp_error( rest_validate_value_from_schema( $current, $args['schema'] ) ) ) { $error->add( 'rest_invalid_stored_value', /* translators: %s: Custom field key. */ diff --git a/tests/phpunit/tests/rest-api/rest-comments-controller.php b/tests/phpunit/tests/rest-api/rest-comments-controller.php index a21defdc2338c..ad18d256b2a5e 100644 --- a/tests/phpunit/tests/rest-api/rest-comments-controller.php +++ b/tests/phpunit/tests/rest-api/rest-comments-controller.php @@ -4142,6 +4142,44 @@ public function test_cannot_create_empty_note_with_invalid_resolution_meta() { $this->assertErrorResponse( 'rest_comment_content_invalid', $response, 400 ); } + /** + * Sending back the meta of a comment with no note status must not fail. + * + * @ticket 66377 + */ + public function test_update_comment_with_meta_echoed_back_from_response() { + wp_set_current_user( self::$editor_id ); + $comment_id = self::factory()->comment->create( + array( + 'comment_post_ID' => self::factory()->post->create(), + ) + ); + + $request = new WP_REST_Request( 'GET', sprintf( '/wp/v2/comments/%d', $comment_id ) ); + $request->set_param( 'context', 'edit' ); + $meta = rest_get_server()->dispatch( $request )->get_data()['meta']; + + $this->assertArrayHasKey( '_wp_note_status', $meta ); + $this->assertNull( $meta['_wp_note_status'] ); + + $request = new WP_REST_Request( 'POST', sprintf( '/wp/v2/comments/%d', $comment_id ) ); + $request->add_header( 'Content-Type', 'application/json' ); + $request->set_body( + wp_json_encode( + array( + 'content' => 'Edited comment.', + 'meta' => $meta, + ) + ) + ); + + $response = rest_get_server()->dispatch( $request ); + + $this->assertSame( 200, $response->get_status() ); + $this->assertSame( 'Edited comment.', get_comment( $comment_id )->comment_content ); + $this->assertFalse( metadata_exists( 'comment', $comment_id, '_wp_note_status' ) ); + } + /** * @ticket 64096 */ 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 1f39c557e253a..1955a962e8c40 100644 --- a/tests/phpunit/tests/rest-api/rest-post-meta-fields.php +++ b/tests/phpunit/tests/rest-api/rest-post-meta-fields.php @@ -1096,6 +1096,106 @@ public function data_delete_does_not_trigger_error_if_no_meta_values() { ); } + /** + * Resetting a single key with nothing stored should not be treated as an invalid stored value. + * + * @ticket 66377 + * @dataProvider data_set_value_to_null_with_nothing_stored + * + * @param bool|array $show_in_rest The show_in_rest argument for the meta key. + * @param non-falsy-string&lowercase-string $type The meta type. + */ + public function test_set_value_to_null_with_nothing_stored( $show_in_rest, string $type ) { + register_post_meta( + 'post', + 'my_meta_key', + array( + 'show_in_rest' => $show_in_rest, + 'single' => true, + 'type' => $type, + ) + ); + + $this->grant_write_permission(); + + $request = new WP_REST_Request( 'POST', sprintf( '/wp/v2/posts/%d', self::$post_id ) ); + $request->set_header( 'Content-Type', 'application/json' ); + $request->set_body( + wp_json_encode( + array( + 'meta' => array( + 'my_meta_key' => null, + ), + ) + ) + ); + + $response = rest_get_server()->dispatch( $request ); + + $this->assertSame( 200, $response->get_status() ); + $this->assertFalse( metadata_exists( 'post', self::$post_id, 'my_meta_key' ) ); + } + + /** + * Data provider for test_set_value_to_null_with_nothing_stored(). + * + * @return array, non-falsy-string&lowercase-string}> Array of test parameters. + */ + public function data_set_value_to_null_with_nothing_stored(): array { + return array( + 'boolean' => array( true, 'boolean' ), + 'integer' => array( true, 'integer' ), + 'number' => array( true, 'number' ), + 'enum' => array( + array( + 'schema' => array( + 'type' => 'string', + 'enum' => array( 'a', 'b' ), + ), + ), + 'string', + ), + ); + } + + /** + * A stored empty string that does not match the schema is still protected from being reset. + * + * @ticket 66377 + */ + public function test_set_value_to_null_with_invalid_empty_string_stored() { + register_post_meta( + 'post', + 'my_meta_key', + array( + 'show_in_rest' => true, + 'single' => true, + 'type' => 'boolean', + ) + ); + + add_post_meta( self::$post_id, 'my_meta_key', '' ); + + $this->grant_write_permission(); + + $request = new WP_REST_Request( 'POST', sprintf( '/wp/v2/posts/%d', self::$post_id ) ); + $request->set_header( 'Content-Type', 'application/json' ); + $request->set_body( + wp_json_encode( + array( + 'meta' => array( + 'my_meta_key' => null, + ), + ) + ) + ); + + $response = rest_get_server()->dispatch( $request ); + + $this->assertErrorResponse( 'rest_invalid_stored_value', $response, 500 ); + $this->assertTrue( metadata_exists( 'post', self::$post_id, 'my_meta_key' ) ); + } + 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 );