From 42e5e4d5d57c2db6022db5b392b02d6049d53de6 Mon Sep 17 00:00:00 2001 From: "K. Adam White" Date: Mon, 23 Sep 2019 17:39:36 +0000 Subject: [PATCH] REST API: Introduce date_floating property on status endpoint response objects. Expose a date_floating property on all status objects to permit clients (including the block editor) to make correct decisions about date handling for posts of varying status. Props mnelson4, earnjam, kadamwhite, jnylen0, nerrad, pento. See #39953. git-svn-id: https://develop.svn.wordpress.org/trunk@46252 602fd350-edb4-49c9-b593-d223f7449a82 --- src/wp-includes/post.php | 23 ++-- ...class-wp-rest-post-statuses-controller.php | 24 ++-- .../class-wp-rest-posts-controller.php | 10 +- .../rest-post-statuses-controller.php | 4 +- .../tests/rest-api/rest-posts-controller.php | 113 ++++++++++++++++++ tests/qunit/fixtures/wp-api-generated.js | 9 +- 6 files changed, 163 insertions(+), 20 deletions(-) diff --git a/src/wp-includes/post.php b/src/wp-includes/post.php index 2043f932ed..dde54c510d 100644 --- a/src/wp-includes/post.php +++ b/src/wp-includes/post.php @@ -337,14 +337,15 @@ function create_initial_post_types() { register_post_status( 'draft', array( - 'label' => _x( 'Draft', 'post status' ), - 'protected' => true, - '_builtin' => true, /* internal use only. */ + 'label' => _x( 'Draft', 'post status' ), + 'protected' => true, + '_builtin' => true, /* internal use only. */ /* translators: %s: Number of draft posts. */ - 'label_count' => _n_noop( + 'label_count' => _n_noop( 'Draft (%s)', 'Drafts (%s)' ), + 'date_floating' => true, ) ); @@ -394,9 +395,10 @@ function create_initial_post_types() { register_post_status( 'auto-draft', array( - 'label' => 'auto-draft', - 'internal' => true, - '_builtin' => true, /* internal use only. */ + 'label' => 'auto-draft', + 'internal' => true, + '_builtin' => true, /* internal use only. */ + 'date_floating' => true, ) ); @@ -1018,6 +1020,8 @@ function _wp_privacy_statuses() { * the top of the edit listings, * e.g. All (12) | Published (9) | My Custom Status (2) * Default is value of $internal. + * @type bool $date_floating Whether the post has a floating creation date. + * Default to false. * } * @return object */ @@ -1041,6 +1045,7 @@ function register_post_status( $post_status, $args = array() ) { 'publicly_queryable' => null, 'show_in_admin_status_list' => null, 'show_in_admin_all_list' => null, + 'date_floating' => null, ); $args = wp_parse_args( $args, $defaults ); $args = (object) $args; @@ -1085,6 +1090,10 @@ function register_post_status( $post_status, $args = array() ) { $args->show_in_admin_status_list = ! $args->internal; } + if ( null === $args->date_floating ) { + $args->date_floating = false; + } + if ( false === $args->label ) { $args->label = $post_status; } diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-post-statuses-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-post-statuses-controller.php index 155c8aaa96..c0cce245ba 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-post-statuses-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-post-statuses-controller.php @@ -234,6 +234,10 @@ class WP_REST_Post_Statuses_Controller extends WP_REST_Controller { $data['slug'] = $status->name; } + if ( in_array( 'date_floating', $fields, true ) ) { + $data['date_floating'] = $status->date_floating; + } + $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; $data = $this->add_additional_fields_to_object( $data, $request ); $data = $this->filter_response_by_context( $data, $context ); @@ -277,48 +281,54 @@ class WP_REST_Post_Statuses_Controller extends WP_REST_Controller { 'title' => 'status', 'type' => 'object', 'properties' => array( - 'name' => array( + 'name' => array( 'description' => __( 'The title for the status.' ), 'type' => 'string', 'context' => array( 'embed', 'view', 'edit' ), 'readonly' => true, ), - 'private' => array( + 'private' => array( 'description' => __( 'Whether posts with this status should be private.' ), 'type' => 'boolean', 'context' => array( 'edit' ), 'readonly' => true, ), - 'protected' => array( + 'protected' => array( 'description' => __( 'Whether posts with this status should be protected.' ), 'type' => 'boolean', 'context' => array( 'edit' ), 'readonly' => true, ), - 'public' => array( + 'public' => array( 'description' => __( 'Whether posts of this status should be shown in the front end of the site.' ), 'type' => 'boolean', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), - 'queryable' => array( + 'queryable' => array( 'description' => __( 'Whether posts with this status should be publicly-queryable.' ), 'type' => 'boolean', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), - 'show_in_list' => array( + 'show_in_list' => array( 'description' => __( 'Whether to include posts in the edit listing for their post type.' ), 'type' => 'boolean', 'context' => array( 'edit' ), 'readonly' => true, ), - 'slug' => array( + 'slug' => array( 'description' => __( 'An alphanumeric identifier for the status.' ), 'type' => 'string', 'context' => array( 'embed', 'view', 'edit' ), 'readonly' => true, ), + 'date_floating' => array( + 'description' => __( 'Whether posts of this status may have floating published dates.' ), + 'type' => 'boolean', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), ), ); diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php index b1e2836af2..d355c3d076 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php @@ -1021,16 +1021,18 @@ class WP_REST_Posts_Controller extends WP_REST_Controller { // Post date. if ( ! empty( $schema['properties']['date'] ) && ! empty( $request['date'] ) ) { - $date_data = rest_get_date_with_gmt( $request['date'] ); + $current_date = isset( $prepared_post->ID ) ? get_post( $prepared_post->ID )->post_date : false; + $date_data = rest_get_date_with_gmt( $request['date'] ); - if ( ! empty( $date_data ) ) { + if ( ! empty( $date_data ) && $current_date !== $date_data[0] ) { list( $prepared_post->post_date, $prepared_post->post_date_gmt ) = $date_data; $prepared_post->edit_date = true; } } elseif ( ! empty( $schema['properties']['date_gmt'] ) && ! empty( $request['date_gmt'] ) ) { - $date_data = rest_get_date_with_gmt( $request['date_gmt'], true ); + $current_date = isset( $prepared_post->ID ) ? get_post( $prepared_post->ID )->post_date_gmt : false; + $date_data = rest_get_date_with_gmt( $request['date_gmt'], true ); - if ( ! empty( $date_data ) ) { + if ( ! empty( $date_data ) && $current_date !== $date_data[1] ) { list( $prepared_post->post_date, $prepared_post->post_date_gmt ) = $date_data; $prepared_post->edit_date = true; } diff --git a/tests/phpunit/tests/rest-api/rest-post-statuses-controller.php b/tests/phpunit/tests/rest-api/rest-post-statuses-controller.php index 1fe933b008..bcf9475240 100644 --- a/tests/phpunit/tests/rest-api/rest-post-statuses-controller.php +++ b/tests/phpunit/tests/rest-api/rest-post-statuses-controller.php @@ -153,7 +153,7 @@ class WP_Test_REST_Post_Statuses_Controller extends WP_Test_REST_Controller_Test $response = rest_get_server()->dispatch( $request ); $data = $response->get_data(); $properties = $data['schema']['properties']; - $this->assertEquals( 7, count( $properties ) ); + $this->assertEquals( 8, count( $properties ) ); $this->assertArrayHasKey( 'name', $properties ); $this->assertArrayHasKey( 'private', $properties ); $this->assertArrayHasKey( 'protected', $properties ); @@ -161,6 +161,7 @@ class WP_Test_REST_Post_Statuses_Controller extends WP_Test_REST_Controller_Test $this->assertArrayHasKey( 'queryable', $properties ); $this->assertArrayHasKey( 'show_in_list', $properties ); $this->assertArrayHasKey( 'slug', $properties ); + $this->assertArrayhasKey( 'date_floating', $properties ); } public function test_get_additional_field_registration() { @@ -217,6 +218,7 @@ class WP_Test_REST_Post_Statuses_Controller extends WP_Test_REST_Controller_Test ), array_keys( $links ) ); + $this->assertEquals( $status_obj->date_floating, $data['date_floating'] ); } protected function check_post_status_object_response( $response ) { diff --git a/tests/phpunit/tests/rest-api/rest-posts-controller.php b/tests/phpunit/tests/rest-api/rest-posts-controller.php index 9596dd3ccd..10d5a1e3a2 100644 --- a/tests/phpunit/tests/rest-api/rest-posts-controller.php +++ b/tests/phpunit/tests/rest-api/rest-posts-controller.php @@ -4413,6 +4413,119 @@ class WP_Test_REST_Posts_Controller extends WP_Test_REST_Post_Type_Controller_Te } + public function test_putting_same_publish_date_does_not_remove_floating_date() { + + wp_set_current_user( self::$superadmin_id ); + + $time = date( 'Y-m-d H:i:s' ); + + $post = self::factory()->post->create_and_get( + array( + 'post_status' => 'draft', + 'post_date' => $time, + ) + ); + + $this->assertEquals( '0000-00-00 00:00:00', $post->post_date_gmt ); + + $get = new WP_REST_Request( 'GET', "/wp/v2/posts/{$post->ID}" ); + $get->set_query_params( array( 'context' => 'edit' ) ); + + $get = rest_get_server()->dispatch( $get ); + $get_body = $get->get_data(); + + $put = new WP_REST_Request( 'PUT', "/wp/v2/posts/{$post->ID}" ); + $put->set_body_params( $get_body ); + + $response = rest_get_server()->dispatch( $put ); + $body = $response->get_data(); + + $this->assertEquals( $get_body['date'], $body['date'] ); + $this->assertEquals( $get_body['date_gmt'], $body['date_gmt'] ); + + $this->assertEquals( '0000-00-00 00:00:00', get_post( $post->ID )->post_date_gmt ); + } + + public function test_putting_different_publish_date_removes_floating_date() { + + wp_set_current_user( self::$superadmin_id ); + + $time = date( 'Y-m-d H:i:s' ); + $new_time = date( 'Y-m-d H:i:s', strtotime( '+1 week' ) ); + + $post = self::factory()->post->create_and_get( + array( + 'post_status' => 'draft', + 'post_date' => $time, + ) + ); + + $this->assertEquals( '0000-00-00 00:00:00', $post->post_date_gmt ); + + $get = new WP_REST_Request( 'GET', "/wp/v2/posts/{$post->ID}" ); + $get->set_query_params( array( 'context' => 'edit' ) ); + + $get = rest_get_server()->dispatch( $get ); + $get_body = $get->get_data(); + + $put = new WP_REST_Request( 'PUT', "/wp/v2/posts/{$post->ID}" ); + $put->set_body_params( + array_merge( + $get_body, + array( + 'date' => mysql_to_rfc3339( $new_time ), + ) + ) + ); + + $response = rest_get_server()->dispatch( $put ); + $body = $response->get_data(); + + $this->assertEquals( mysql_to_rfc3339( $new_time ), $body['date'] ); + + $this->assertNotEquals( '0000-00-00 00:00:00', get_post( $post->ID )->post_date_gmt ); + } + + public function test_publishing_post_with_same_date_removes_floating_date() { + + wp_set_current_user( self::$superadmin_id ); + + $time = date( 'Y-m-d H:i:s' ); + + $post = self::factory()->post->create_and_get( + array( + 'post_status' => 'draft', + 'post_date' => $time, + ) + ); + + $this->assertEquals( '0000-00-00 00:00:00', $post->post_date_gmt ); + + $get = new WP_REST_Request( 'GET', "/wp/v2/posts/{$post->ID}" ); + $get->set_query_params( array( 'context' => 'edit' ) ); + + $get = rest_get_server()->dispatch( $get ); + $get_body = $get->get_data(); + + $put = new WP_REST_Request( 'PUT', "/wp/v2/posts/{$post->ID}" ); + $put->set_body_params( + array_merge( + $get_body, + array( + 'status' => 'publish', + ) + ) + ); + + $response = rest_get_server()->dispatch( $put ); + $body = $response->get_data(); + + $this->assertEquals( $get_body['date'], $body['date'] ); + $this->assertEquals( $get_body['date_gmt'], $body['date_gmt'] ); + + $this->assertNotEquals( '0000-00-00 00:00:00', get_post( $post->ID )->post_date_gmt ); + } + public function tearDown() { _unregister_post_type( 'private-post' ); _unregister_post_type( 'youseeme' ); diff --git a/tests/qunit/fixtures/wp-api-generated.js b/tests/qunit/fixtures/wp-api-generated.js index 57e92cb885..f9ce077692 100644 --- a/tests/qunit/fixtures/wp-api-generated.js +++ b/tests/qunit/fixtures/wp-api-generated.js @@ -7392,6 +7392,7 @@ mockedApiResponse.StatusesCollection = { "public": true, "queryable": true, "slug": "publish", + "date_floating": false, "_links": { "archives": [ { @@ -7405,6 +7406,7 @@ mockedApiResponse.StatusesCollection = { "public": false, "queryable": false, "slug": "future", + "date_floating": false, "_links": { "archives": [ { @@ -7418,6 +7420,7 @@ mockedApiResponse.StatusesCollection = { "public": false, "queryable": false, "slug": "draft", + "date_floating": true, "_links": { "archives": [ { @@ -7431,6 +7434,7 @@ mockedApiResponse.StatusesCollection = { "public": false, "queryable": false, "slug": "pending", + "date_floating": false, "_links": { "archives": [ { @@ -7444,6 +7448,7 @@ mockedApiResponse.StatusesCollection = { "public": false, "queryable": false, "slug": "private", + "date_floating": false, "_links": { "archives": [ { @@ -7457,6 +7462,7 @@ mockedApiResponse.StatusesCollection = { "public": false, "queryable": false, "slug": "trash", + "date_floating": false, "_links": { "archives": [ { @@ -7471,7 +7477,8 @@ mockedApiResponse.StatusModel = { "name": "Published", "public": true, "queryable": true, - "slug": "publish" + "slug": "publish", + "date_floating": false }; mockedApiResponse.TaxonomiesCollection = {