diff --git a/src/wp-includes/post.php b/src/wp-includes/post.php index 8d9b58c037501..088b692b6081b 100644 --- a/src/wp-includes/post.php +++ b/src/wp-includes/post.php @@ -353,7 +353,10 @@ function create_initial_post_types() { 'editor', 'revisions', 'custom-fields', + 'author', ), + // Keep patterns when their author is deleted, as they may be used across the site. + 'delete_with_user' => false, ) ); diff --git a/src/wp-includes/view-config.php b/src/wp-includes/view-config.php index 8765a5d095008..e87558331bc49 100644 --- a/src/wp-includes/view-config.php +++ b/src/wp-includes/view-config.php @@ -369,7 +369,7 @@ function _wp_get_entity_view_config_posttype_wp_block( $data ) { 'slug' => 'all-patterns', ), array( - 'title' => __( 'My patterns' ), + 'title' => __( 'Site patterns' ), 'slug' => 'my-patterns', ), ); diff --git a/tests/phpunit/tests/rest-api/rest-blocks-controller.php b/tests/phpunit/tests/rest-api/rest-blocks-controller.php index 43a181683c788..aad645344d6d2 100644 --- a/tests/phpunit/tests/rest-api/rest-blocks-controller.php +++ b/tests/phpunit/tests/rest-api/rest-blocks-controller.php @@ -255,4 +255,104 @@ public function test_wp_patterns_sync_status_post_meta() { $this->assertArrayHasKey( 'wp_pattern_sync_status', $data ); $this->assertArrayNotHasKey( 'wp_pattern_sync_status', $data['meta'] ); } + + /** + * Check that the response includes the pattern's author. + * + * @ticket 66307 + */ + public function test_author_in_response() { + wp_set_current_user( self::$user_ids['editor'] ); + + $post_id = self::factory()->post->create( + array( + 'post_type' => 'wp_block', + 'post_author' => self::$user_ids['author'], + ) + ); + + $request = new WP_REST_Request( 'GET', '/wp/v2/blocks/' . $post_id ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + + $this->assertSame( self::$user_ids['author'], $data['author'] ); + } + + /** + * Check that patterns can be filtered by author. + * + * @ticket 66307 + */ + public function test_filter_by_author() { + wp_set_current_user( self::$user_ids['editor'] ); + + $editor_post_id = self::factory()->post->create( + array( + 'post_type' => 'wp_block', + 'post_author' => self::$user_ids['editor'], + ) + ); + $author_post_id = self::factory()->post->create( + array( + 'post_type' => 'wp_block', + 'post_author' => self::$user_ids['author'], + ) + ); + + $request = new WP_REST_Request( 'GET', '/wp/v2/blocks' ); + $request->set_param( 'author', self::$user_ids['author'] ); + $response = rest_get_server()->dispatch( $request ); + + $this->assertSame( array( $author_post_id ), wp_list_pluck( $response->get_data(), 'id' ), 'The author filter should only return the author\'s patterns.' ); + + $request = new WP_REST_Request( 'GET', '/wp/v2/blocks' ); + $request->set_param( 'author_exclude', self::$user_ids['author'] ); + $response = rest_get_server()->dispatch( $request ); + $ids = wp_list_pluck( $response->get_data(), 'id' ); + + $this->assertContains( $editor_post_id, $ids, 'The author_exclude filter should return other users\' patterns.' ); + $this->assertNotContains( $author_post_id, $ids, 'The author_exclude filter should not return the author\'s patterns.' ); + } + + /** + * Check that only users who can edit others' patterns can change a pattern's author. + * + * @ticket 66307 + * + * @dataProvider data_change_author + * + * @param string $role User role to test. + * @param int $expected_status Expected HTTP response status. + */ + public function test_change_author( $role, $expected_status ) { + $user_id = self::$user_ids[ $role ]; + wp_set_current_user( $user_id ); + + $post_id = self::factory()->post->create( + array( + 'post_type' => 'wp_block', + 'post_author' => $user_id, + ) + ); + $new_author = 'editor' === $role ? self::$user_ids['author'] : self::$user_ids['editor']; + + $request = new WP_REST_Request( 'PUT', '/wp/v2/blocks/' . $post_id ); + $request->set_body_params( array( 'author' => $new_author ) ); + $response = rest_get_server()->dispatch( $request ); + + $this->assertSame( $expected_status, $response->get_status(), 'The response status should match.' ); + $this->assertSame( 200 === $expected_status ? $new_author : $user_id, (int) get_post( $post_id )->post_author, 'The pattern\'s author should match.' ); + } + + /** + * Test cases for test_change_author(). + * + * @return array[] + */ + public function data_change_author() { + return array( + 'editor' => array( 'editor', 200 ), + 'author' => array( 'author', 403 ), + ); + } } diff --git a/tests/phpunit/tests/user/wpDeleteUser.php b/tests/phpunit/tests/user/wpDeleteUser.php index b2eec9be1fcb9..a74705d6dd104 100644 --- a/tests/phpunit/tests/user/wpDeleteUser.php +++ b/tests/phpunit/tests/user/wpDeleteUser.php @@ -124,6 +124,45 @@ public function test_wp_delete_user_reassignment_clears_post_caches() { $this->assertSame( (string) $reassign, $post->post_author ); } + /** + * Patterns may be used across the site, so they're kept when their author is deleted. + * + * @ticket 66307 + */ + public function test_delete_user_keeps_patterns() { + $user_id = self::factory()->user->create( array( 'role' => 'author' ) ); + $pattern_id = self::factory()->post->create( + array( + 'post_type' => 'wp_block', + 'post_author' => $user_id, + ) + ); + + wp_delete_user( $user_id ); + + $this->assertSame( 'publish', get_post_status( $pattern_id ) ); + } + + /** + * @ticket 66307 + */ + public function test_delete_user_reassigns_patterns() { + $user_id = self::factory()->user->create( array( 'role' => 'author' ) ); + $reassign = self::factory()->user->create( array( 'role' => 'author' ) ); + $pattern_id = self::factory()->post->create( + array( + 'post_type' => 'wp_block', + 'post_author' => $user_id, + ) + ); + + wp_delete_user( $user_id, $reassign ); + + $pattern = get_post( $pattern_id ); + $this->assertSame( 'publish', $pattern->post_status, 'The pattern should not be deleted.' ); + $this->assertSame( (string) $reassign, $pattern->post_author, 'The pattern should be reassigned.' ); + } + /** * @group ms-excluded */ diff --git a/tests/qunit/fixtures/wp-api-generated.js b/tests/qunit/fixtures/wp-api-generated.js index dba0cd776b59f..065333e1fdb0f 100644 --- a/tests/qunit/fixtures/wp-api-generated.js +++ b/tests/qunit/fixtures/wp-api-generated.js @@ -4669,6 +4669,24 @@ mockedApiResponse.Schema = { "format": "date-time", "required": false }, + "author": { + "description": "Limit result set to posts assigned to specific authors.", + "type": "array", + "items": { + "type": "integer" + }, + "default": [], + "required": false + }, + "author_exclude": { + "description": "Ensure result set excludes posts assigned to specific authors.", + "type": "array", + "items": { + "type": "integer" + }, + "default": [], + "required": false + }, "before": { "description": "Limit response to posts published before a given ISO8601 compliant date.", "type": "string", @@ -4970,6 +4988,11 @@ mockedApiResponse.Schema = { }, "required": false }, + "author": { + "description": "The ID for the author of the post.", + "type": "integer", + "required": false + }, "excerpt": { "description": "The excerpt for the post.", "type": "object", @@ -5179,6 +5202,11 @@ mockedApiResponse.Schema = { }, "required": false }, + "author": { + "description": "The ID for the author of the post.", + "type": "integer", + "required": false + }, "excerpt": { "description": "The excerpt for the post.", "type": "object", @@ -5543,6 +5571,11 @@ mockedApiResponse.Schema = { }, "required": false }, + "author": { + "description": "The ID for the author of the post.", + "type": "integer", + "required": false + }, "excerpt": { "description": "The excerpt for the post.", "type": "object",