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 008124d582..85af10f5f6 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 @@ -653,6 +653,26 @@ class WP_REST_Posts_Controller extends WP_REST_Controller { $prepared_post->post_type = $this->post_type; + if ( ! empty( $prepared_post->post_name ) + && ! empty( $prepared_post->post_status ) + && in_array( $prepared_post->post_status, array( 'draft', 'pending' ), true ) + ) { + /* + * `wp_unique_post_slug()` returns the same + * slug for 'draft' or 'pending' posts. + * + * To ensure that a unique slug is generated, + * pass the post data with the 'publish' status. + */ + $prepared_post->post_name = wp_unique_post_slug( + $prepared_post->post_name, + $prepared_post->id, + 'publish', + $prepared_post->post_type, + $prepared_post->post_parent + ); + } + $post_id = wp_insert_post( wp_slash( (array) $prepared_post ), true, false ); if ( is_wp_error( $post_id ) ) { @@ -834,6 +854,24 @@ class WP_REST_Posts_Controller extends WP_REST_Controller { return $post; } + if ( ! empty( $post->post_status ) ) { + $post_status = $post->post_status; + } else { + $post_status = $post_before->post_status; + } + + /* + * `wp_unique_post_slug()` returns the same + * slug for 'draft' or 'pending' posts. + * + * To ensure that a unique slug is generated, + * pass the post data with the 'publish' status. + */ + if ( ! empty( $post->post_name ) && in_array( $post_status, array( 'draft', 'pending' ), true ) ) { + $post_parent = ! empty( $post->post_parent ) ? $post->post_parent : 0; + $post->post_name = wp_unique_post_slug( $post->post_name, $post->ID, 'publish', $post->post_type, $post_parent ); + } + // Convert the post object to an array, otherwise wp_update_post() will expect non-escaped input. $post_id = wp_update_post( wp_slash( (array) $post ), true, false ); diff --git a/tests/phpunit/tests/rest-api/rest-posts-controller.php b/tests/phpunit/tests/rest-api/rest-posts-controller.php index 6655c774fc..6ff1edae3e 100644 --- a/tests/phpunit/tests/rest-api/rest-posts-controller.php +++ b/tests/phpunit/tests/rest-api/rest-posts-controller.php @@ -5236,6 +5236,47 @@ class WP_Test_REST_Posts_Controller extends WP_Test_REST_Post_Type_Controller_Te $GLOBALS['wp_rest_server']->override_by_default = false; } + /** + * @ticket 52422 + * + * @covers WP_REST_Request::create_item + */ + public function test_draft_post_do_not_have_the_same_slug_as_existing_post() { + wp_set_current_user( self::$editor_id ); + $this->factory()->post->create( array( 'post_name' => 'sample-slug' ) ); + + $request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/posts/%d', self::$post_id ) ); + $params = $this->set_post_data( + array( + 'status' => 'draft', + 'slug' => 'sample-slug', + ) + ); + $request->set_body_params( $params ); + $response = rest_get_server()->dispatch( $request ); + + $new_data = $response->get_data(); + $this->assertSame( + 'sample-slug-2', + $new_data['slug'], + 'The slug from the REST response did not match' + ); + + $post = get_post( $new_data['id'] ); + + $this->assertSame( + 'draft', + $post->post_status, + 'The post status is not draft' + ); + + $this->assertSame( + 'sample-slug-2', + $post->post_name, + 'The post slug was not set to "sample-slug-2"' + ); + } + public function tear_down() { if ( isset( $this->attachment_id ) ) { $this->remove_added_uploads();