REST API: Pass "null" as the post date property to reset post to initial "floating" date value.
Props TimothyBlynJacobs, adamsilverstein, jnylen0, mnelson4. Fixes #44975. git-svn-id: https://develop.svn.wordpress.org/trunk@46249 602fd350-edb4-49c9-b593-d223f7449a82
This commit is contained in:
parent
e3db9a1361
commit
27a5302e7d
@ -1209,6 +1209,20 @@ function rest_get_avatar_sizes() {
|
||||
* @return true|WP_Error
|
||||
*/
|
||||
function rest_validate_value_from_schema( $value, $args, $param = '' ) {
|
||||
if ( is_array( $args['type'] ) ) {
|
||||
foreach ( $args['type'] as $type ) {
|
||||
$type_args = $args;
|
||||
$type_args['type'] = $type;
|
||||
|
||||
if ( true === rest_validate_value_from_schema( $value, $type_args, $param ) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/* translators: 1: Parameter, 2: List of types. */
|
||||
return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s' ), $param, implode( ',', $args['type'] ) ) );
|
||||
}
|
||||
|
||||
if ( 'array' === $args['type'] ) {
|
||||
if ( ! is_null( $value ) ) {
|
||||
$value = wp_parse_list( $value );
|
||||
@ -1261,6 +1275,15 @@ function rest_validate_value_from_schema( $value, $args, $param = '' ) {
|
||||
}
|
||||
}
|
||||
|
||||
if ( 'null' === $args['type'] ) {
|
||||
if ( null !== $value ) {
|
||||
/* translators: 1: Parameter, 2: Type name. */
|
||||
return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s is not of type %2$s.' ), $param, 'null' ) );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if ( ! empty( $args['enum'] ) ) {
|
||||
if ( ! in_array( $value, $args['enum'], true ) ) {
|
||||
/* translators: 1: Parameter, 2: List of valid values. */
|
||||
@ -1365,6 +1388,27 @@ function rest_validate_value_from_schema( $value, $args, $param = '' ) {
|
||||
* @return true|WP_Error
|
||||
*/
|
||||
function rest_sanitize_value_from_schema( $value, $args ) {
|
||||
if ( is_array( $args['type'] ) ) {
|
||||
// Determine which type the value was validated against, and use that type when performing sanitization
|
||||
$validated_type = '';
|
||||
|
||||
foreach ( $args['type'] as $type ) {
|
||||
$type_args = $args;
|
||||
$type_args['type'] = $type;
|
||||
|
||||
if ( ! is_wp_error( rest_validate_value_from_schema( $value, $type_args ) ) ) {
|
||||
$validated_type = $type;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! $validated_type ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$args['type'] = $validated_type;
|
||||
}
|
||||
|
||||
if ( 'array' === $args['type'] ) {
|
||||
if ( empty( $args['items'] ) ) {
|
||||
return (array) $value;
|
||||
@ -1407,6 +1451,10 @@ function rest_sanitize_value_from_schema( $value, $args ) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
if ( 'null' === $args['type'] ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ( 'integer' === $args['type'] ) {
|
||||
return (int) $value;
|
||||
}
|
||||
|
@ -397,6 +397,30 @@ class WP_REST_Request implements ArrayAccess {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a parameter exists in the request.
|
||||
*
|
||||
* This allows distinguishing between an omitted parameter,
|
||||
* and a parameter specifically set to null.
|
||||
*
|
||||
* @since 5.3.0
|
||||
*
|
||||
* @param string $key Parameter name.
|
||||
*
|
||||
* @return bool True if a param exists for the given key.
|
||||
*/
|
||||
public function has_param( $key ) {
|
||||
$order = $this->get_parameter_order();
|
||||
|
||||
foreach ( $order as $type ) {
|
||||
if ( array_key_exists( $key, $this->params[ $type ] ) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a parameter on the request.
|
||||
*
|
||||
|
@ -1036,6 +1036,16 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
|
||||
}
|
||||
}
|
||||
|
||||
// Sending a null date or date_gmt value resets date and date_gmt to their
|
||||
// default values (`0000-00-00 00:00:00`).
|
||||
if (
|
||||
( ! empty( $schema['properties']['date_gmt'] ) && $request->has_param( 'date_gmt' ) && null === $request['date_gmt'] ) ||
|
||||
( ! empty( $schema['properties']['date'] ) && $request->has_param( 'date' ) && null === $request['date'] )
|
||||
) {
|
||||
$prepared_post->post_date_gmt = null;
|
||||
$prepared_post->post_date = null;
|
||||
}
|
||||
|
||||
// Post slug.
|
||||
if ( ! empty( $schema['properties']['slug'] ) && isset( $request['slug'] ) ) {
|
||||
$prepared_post->post_name = $request['slug'];
|
||||
@ -1891,13 +1901,13 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
|
||||
'properties' => array(
|
||||
'date' => array(
|
||||
'description' => __( "The date the object was published, in the site's timezone." ),
|
||||
'type' => 'string',
|
||||
'type' => array( 'string', 'null' ),
|
||||
'format' => 'date-time',
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
),
|
||||
'date_gmt' => array(
|
||||
'description' => __( 'The date the object was published, as GMT.' ),
|
||||
'type' => 'string',
|
||||
'type' => array( 'string', 'null' ),
|
||||
'format' => 'date-time',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
),
|
||||
|
@ -2625,6 +2625,66 @@ class WP_Test_REST_Posts_Controller extends WP_Test_REST_Post_Type_Controller_Te
|
||||
$this->assertEquals( $params['excerpt'], $post->post_excerpt );
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that updating a post with a `null` date or date_gmt results in a reset post, where all
|
||||
* date values are equal (date, date_gmt, date_modified and date_modofied_gmt) in the API response.
|
||||
* In the database, the post_date_gmt field is reset to the default `0000-00-00 00:00:00`.
|
||||
*
|
||||
* @ticket 44975
|
||||
*/
|
||||
public function test_rest_update_post_with_empty_date() {
|
||||
// Create a new test post.
|
||||
$post_id = $this->factory->post->create();
|
||||
wp_set_current_user( self::$editor_id );
|
||||
|
||||
// Set the post date to the future.
|
||||
$future_date = '2919-07-29T18:00:00';
|
||||
$request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/posts/%d', $post_id ) );
|
||||
$request->add_header( 'content-type', 'application/json' );
|
||||
$params = $this->set_post_data(
|
||||
array(
|
||||
'date_gmt' => $future_date,
|
||||
'date' => $future_date,
|
||||
'title' => 'update',
|
||||
'status' => 'draft',
|
||||
)
|
||||
);
|
||||
$request->set_body( wp_json_encode( $params ) );
|
||||
$response = rest_get_server()->dispatch( $request );
|
||||
$this->check_update_post_response( $response );
|
||||
$new_data = $response->get_data();
|
||||
|
||||
// Verify the post is set to the future date.
|
||||
$this->assertEquals( $new_data['date_gmt'], $future_date );
|
||||
$this->assertEquals( $new_data['date'], $future_date );
|
||||
$this->assertNotEquals( $new_data['date_gmt'], $new_data['modified_gmt'] );
|
||||
$this->assertNotEquals( $new_data['date'], $new_data['modified'] );
|
||||
|
||||
// Update post with a blank field (date or date_gmt).
|
||||
$request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/posts/%d', $post_id ) );
|
||||
$request->add_header( 'content-type', 'application/json' );
|
||||
$params = $this->set_post_data(
|
||||
array(
|
||||
'date_gmt' => null,
|
||||
'title' => 'test',
|
||||
'status' => 'draft',
|
||||
)
|
||||
);
|
||||
$request->set_body( wp_json_encode( $params ) );
|
||||
$response = rest_get_server()->dispatch( $request );
|
||||
|
||||
// Verify the date field values are reset in the API response.
|
||||
$this->check_update_post_response( $response );
|
||||
$new_data = $response->get_data();
|
||||
$this->assertEquals( $new_data['date_gmt'], $new_data['date'] );
|
||||
$this->assertNotEquals( $new_data['date_gmt'], $future_date );
|
||||
|
||||
$post = get_post( $post_id, 'ARRAY_A' );
|
||||
$this->assertEquals( $post['post_date_gmt'], '0000-00-00 00:00:00' );
|
||||
$this->assertNotEquals( $new_data['date_gmt'], $future_date );
|
||||
$this->assertNotEquals( $new_data['date'], $future_date );
|
||||
}
|
||||
|
||||
public function test_rest_update_post_raw() {
|
||||
wp_set_current_user( self::$editor_id );
|
||||
|
||||
|
@ -292,4 +292,59 @@ class WP_Test_REST_Schema_Sanitization extends WP_UnitTestCase {
|
||||
$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_nullable_date() {
|
||||
$schema = array(
|
||||
'type' => array( 'string', 'null' ),
|
||||
'format' => 'date-time',
|
||||
);
|
||||
|
||||
$this->assertNull( rest_sanitize_value_from_schema( null, $schema ) );
|
||||
$this->assertEquals( '2019-09-19T18:00:00', rest_sanitize_value_from_schema( '2019-09-19T18:00:00', $schema ) );
|
||||
$this->assertNull( rest_sanitize_value_from_schema( 'lalala', $schema ) );
|
||||
}
|
||||
|
||||
public function test_object_or_string() {
|
||||
$schema = array(
|
||||
'type' => array( 'object', 'string' ),
|
||||
'properties' => array(
|
||||
'raw' => array(
|
||||
'type' => 'string',
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
$this->assertEquals( 'My Value', rest_sanitize_value_from_schema( 'My Value', $schema ) );
|
||||
$this->assertEquals( array( 'raw' => 'My Value' ), rest_sanitize_value_from_schema( array( 'raw' => 'My Value' ), $schema ) );
|
||||
$this->assertNull( rest_sanitize_value_from_schema( array( 'raw' => 1 ), $schema ) );
|
||||
}
|
||||
|
||||
public function test_object_or_bool() {
|
||||
$schema = array(
|
||||
'type' => array( 'object', 'boolean' ),
|
||||
'properties' => array(
|
||||
'raw' => array(
|
||||
'type' => 'boolean',
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
$this->assertTrue( rest_sanitize_value_from_schema( true, $schema ) );
|
||||
$this->assertTrue( rest_sanitize_value_from_schema( '1', $schema ) );
|
||||
$this->assertTrue( rest_sanitize_value_from_schema( 1, $schema ) );
|
||||
|
||||
$this->assertFalse( rest_sanitize_value_from_schema( false, $schema ) );
|
||||
$this->assertFalse( rest_sanitize_value_from_schema( '0', $schema ) );
|
||||
$this->assertFalse( rest_sanitize_value_from_schema( 0, $schema ) );
|
||||
|
||||
$this->assertEquals( array( 'raw' => true ), rest_sanitize_value_from_schema( array( 'raw' => true ), $schema ) );
|
||||
$this->assertEquals( array( 'raw' => true ), rest_sanitize_value_from_schema( array( 'raw' => '1' ), $schema ) );
|
||||
$this->assertEquals( array( 'raw' => true ), rest_sanitize_value_from_schema( array( 'raw' => 1 ), $schema ) );
|
||||
|
||||
$this->assertEquals( array( 'raw' => false ), rest_sanitize_value_from_schema( array( 'raw' => false ), $schema ) );
|
||||
$this->assertEquals( array( 'raw' => false ), rest_sanitize_value_from_schema( array( 'raw' => '0' ), $schema ) );
|
||||
$this->assertEquals( array( 'raw' => false ), rest_sanitize_value_from_schema( array( 'raw' => 0 ), $schema ) );
|
||||
|
||||
$this->assertNull( rest_sanitize_value_from_schema( array( 'raw' => 'something non boolean' ), $schema ) );
|
||||
}
|
||||
}
|
||||
|
@ -296,4 +296,36 @@ class WP_Test_REST_Schema_Validation extends WP_UnitTestCase {
|
||||
$this->assertTrue( rest_validate_value_from_schema( 1, $schema ) );
|
||||
$this->assertTrue( rest_validate_value_from_schema( array(), $schema ) );
|
||||
}
|
||||
|
||||
public function test_type_null() {
|
||||
$this->assertTrue( rest_validate_value_from_schema( null, array( 'type' => 'null' ) ) );
|
||||
$this->assertWPError( rest_validate_value_from_schema( '', array( 'type' => 'null' ) ) );
|
||||
$this->assertWPError( rest_validate_value_from_schema( 'null', array( 'type' => 'null' ) ) );
|
||||
}
|
||||
|
||||
public function test_nullable_date() {
|
||||
$schema = array(
|
||||
'type' => array( 'string', 'null' ),
|
||||
'format' => 'date-time',
|
||||
);
|
||||
|
||||
$this->assertTrue( rest_validate_value_from_schema( null, $schema ) );
|
||||
$this->assertTrue( rest_validate_value_from_schema( '2019-09-19T18:00:00', $schema ) );
|
||||
$this->assertWPError( rest_validate_value_from_schema( 'some random string', $schema ) );
|
||||
}
|
||||
|
||||
public function test_object_or_string() {
|
||||
$schema = array(
|
||||
'type' => array( 'object', 'string' ),
|
||||
'properties' => array(
|
||||
'raw' => array(
|
||||
'type' => 'string',
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
$this->assertTrue( rest_validate_value_from_schema( 'My Value', $schema ) );
|
||||
$this->assertTrue( rest_validate_value_from_schema( array( 'raw' => 'My Value' ), $schema ) );
|
||||
$this->assertWPError( rest_validate_value_from_schema( array( 'raw' => array( 'a list' ) ), $schema ) );
|
||||
}
|
||||
}
|
||||
|
@ -373,12 +373,18 @@ mockedApiResponse.Schema = {
|
||||
"date": {
|
||||
"required": false,
|
||||
"description": "The date the object was published, in the site's timezone.",
|
||||
"type": "string"
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"date_gmt": {
|
||||
"required": false,
|
||||
"description": "The date the object was published, as GMT.",
|
||||
"type": "string"
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"slug": {
|
||||
"required": false,
|
||||
@ -553,12 +559,18 @@ mockedApiResponse.Schema = {
|
||||
"date": {
|
||||
"required": false,
|
||||
"description": "The date the object was published, in the site's timezone.",
|
||||
"type": "string"
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"date_gmt": {
|
||||
"required": false,
|
||||
"description": "The date the object was published, as GMT.",
|
||||
"type": "string"
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"slug": {
|
||||
"required": false,
|
||||
@ -893,12 +905,18 @@ mockedApiResponse.Schema = {
|
||||
"date": {
|
||||
"required": false,
|
||||
"description": "The date the object was published, in the site's timezone.",
|
||||
"type": "string"
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"date_gmt": {
|
||||
"required": false,
|
||||
"description": "The date the object was published, as GMT.",
|
||||
"type": "string"
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"slug": {
|
||||
"required": false,
|
||||
@ -1238,12 +1256,18 @@ mockedApiResponse.Schema = {
|
||||
"date": {
|
||||
"required": false,
|
||||
"description": "The date the object was published, in the site's timezone.",
|
||||
"type": "string"
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"date_gmt": {
|
||||
"required": false,
|
||||
"description": "The date the object was published, as GMT.",
|
||||
"type": "string"
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"slug": {
|
||||
"required": false,
|
||||
@ -1390,12 +1414,18 @@ mockedApiResponse.Schema = {
|
||||
"date": {
|
||||
"required": false,
|
||||
"description": "The date the object was published, in the site's timezone.",
|
||||
"type": "string"
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"date_gmt": {
|
||||
"required": false,
|
||||
"description": "The date the object was published, as GMT.",
|
||||
"type": "string"
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"slug": {
|
||||
"required": false,
|
||||
@ -1702,12 +1732,18 @@ mockedApiResponse.Schema = {
|
||||
"date": {
|
||||
"required": false,
|
||||
"description": "The date the object was published, in the site's timezone.",
|
||||
"type": "string"
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"date_gmt": {
|
||||
"required": false,
|
||||
"description": "The date the object was published, as GMT.",
|
||||
"type": "string"
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"slug": {
|
||||
"required": false,
|
||||
@ -2015,12 +2051,18 @@ mockedApiResponse.Schema = {
|
||||
"date": {
|
||||
"required": false,
|
||||
"description": "The date the object was published, in the site's timezone.",
|
||||
"type": "string"
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"date_gmt": {
|
||||
"required": false,
|
||||
"description": "The date the object was published, as GMT.",
|
||||
"type": "string"
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"slug": {
|
||||
"required": false,
|
||||
@ -2152,12 +2194,18 @@ mockedApiResponse.Schema = {
|
||||
"date": {
|
||||
"required": false,
|
||||
"description": "The date the object was published, in the site's timezone.",
|
||||
"type": "string"
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"date_gmt": {
|
||||
"required": false,
|
||||
"description": "The date the object was published, as GMT.",
|
||||
"type": "string"
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"slug": {
|
||||
"required": false,
|
||||
@ -2399,12 +2447,18 @@ mockedApiResponse.Schema = {
|
||||
"date": {
|
||||
"required": false,
|
||||
"description": "The date the object was published, in the site's timezone.",
|
||||
"type": "string"
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"date_gmt": {
|
||||
"required": false,
|
||||
"description": "The date the object was published, as GMT.",
|
||||
"type": "string"
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"slug": {
|
||||
"required": false,
|
||||
@ -2503,12 +2557,18 @@ mockedApiResponse.Schema = {
|
||||
"date": {
|
||||
"required": false,
|
||||
"description": "The date the object was published, in the site's timezone.",
|
||||
"type": "string"
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"date_gmt": {
|
||||
"required": false,
|
||||
"description": "The date the object was published, as GMT.",
|
||||
"type": "string"
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"slug": {
|
||||
"required": false,
|
||||
@ -2612,12 +2672,18 @@ mockedApiResponse.Schema = {
|
||||
"date": {
|
||||
"required": false,
|
||||
"description": "The date the object was published, in the site's timezone.",
|
||||
"type": "string"
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"date_gmt": {
|
||||
"required": false,
|
||||
"description": "The date the object was published, as GMT.",
|
||||
"type": "string"
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"slug": {
|
||||
"required": false,
|
||||
|
Loading…
Reference in New Issue
Block a user