diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php index 8a26a79a67..804d22a3cb 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php @@ -171,6 +171,14 @@ class WP_REST_Attachments_Controller extends WP_REST_Posts_Controller { update_post_meta( $attachment_id, '_wp_attachment_image_alt', sanitize_text_field( $request['alt_text'] ) ); } + if ( ! empty( $schema['properties']['featured_media'] ) && isset( $request['featured_media'] ) ) { + $thumbnail_update = $this->handle_featured_media( $request['featured_media'], $attachment_id ); + + if ( is_wp_error( $thumbnail_update ) ) { + return $thumbnail_update; + } + } + if ( ! empty( $schema['properties']['meta'] ) && isset( $request['meta'] ) ) { $meta_update = $this->meta->update_value( $request['meta'], $attachment_id ); @@ -322,6 +330,43 @@ class WP_REST_Attachments_Controller extends WP_REST_Posts_Controller { ); } + /** + * Determines the featured media based on a request param. + * + * @since 6.5.0 + * + * @param int $featured_media Featured Media ID. + * @param int $post_id Post ID. + * @return bool|WP_Error Whether the post thumbnail was successfully deleted, otherwise WP_Error. + */ + protected function handle_featured_media( $featured_media, $post_id ) { + $post_type = get_post_type( $post_id ); + $thumbnail_support = current_theme_supports( 'post-thumbnails', $post_type ) && post_type_supports( $post_type, 'thumbnail' ); + + // Similar check as in wp_insert_post(). + if ( ! $thumbnail_support && get_post_mime_type( $post_id ) ) { + if ( wp_attachment_is( 'audio', $post_id ) ) { + $thumbnail_support = post_type_supports( 'attachment:audio', 'thumbnail' ) || current_theme_supports( 'post-thumbnails', 'attachment:audio' ); + } elseif ( wp_attachment_is( 'video', $post_id ) ) { + $thumbnail_support = post_type_supports( 'attachment:video', 'thumbnail' ) || current_theme_supports( 'post-thumbnails', 'attachment:video' ); + } + } + + if ( $thumbnail_support ) { + return parent::handle_featured_media( $featured_media, $post_id ); + } + + return new WP_Error( + 'rest_no_featured_media', + sprintf( + /* translators: %s: attachment mime type */ + __( 'This site does not support post thumbnails on attachments with MIME type %s.' ), + get_post_mime_type( $post_id ) + ), + array( 'status' => 400 ) + ); + } + /** * Updates a single attachment. * @@ -355,6 +400,14 @@ class WP_REST_Attachments_Controller extends WP_REST_Posts_Controller { $attachment = get_post( $request['id'] ); + if ( ! empty( $schema['properties']['featured_media'] ) && isset( $request['featured_media'] ) ) { + $thumbnail_update = $this->handle_featured_media( $request['featured_media'], $attachment->ID ); + + if ( is_wp_error( $thumbnail_update ) ) { + return $thumbnail_update; + } + } + $fields_update = $this->update_additional_fields_for_object( $attachment, $request ); if ( is_wp_error( $fields_update ) ) { 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 b9a58b63fb..ce99d0dde1 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 @@ -2377,6 +2377,7 @@ class WP_REST_Posts_Controller extends WP_REST_Controller { 'comments', 'revisions', 'custom-fields', + 'thumbnail', ), ); diff --git a/tests/phpunit/includes/testcase-rest-post-type-controller.php b/tests/phpunit/includes/testcase-rest-post-type-controller.php index cd8ccaefc7..9b1d366315 100644 --- a/tests/phpunit/includes/testcase-rest-post-type-controller.php +++ b/tests/phpunit/includes/testcase-rest-post-type-controller.php @@ -77,7 +77,16 @@ abstract class WP_Test_REST_Post_Type_Controller_Testcase extends WP_Test_REST_C $this->assertSame( get_page_template_slug( $post->ID ), $data['template'] ); } - if ( post_type_supports( $post->post_type, 'thumbnail' ) ) { + if ( + post_type_supports( $post->post_type, 'thumbnail' ) || + ( + 'attachment' === $post->post_type && + ( + post_type_supports( 'attachment:audio', 'thumbnail' ) || + post_type_supports( 'attachment:video', 'thumbnail' ) + ) + ) + ) { $this->assertSame( (int) get_post_thumbnail_id( $post->ID ), $data['featured_media'] ); } else { $this->assertArrayNotHasKey( 'featured_media', $data ); diff --git a/tests/phpunit/tests/rest-api/rest-attachments-controller.php b/tests/phpunit/tests/rest-api/rest-attachments-controller.php index 44c4027579..19719b5b20 100644 --- a/tests/phpunit/tests/rest-api/rest-attachments-controller.php +++ b/tests/phpunit/tests/rest-api/rest-attachments-controller.php @@ -1067,6 +1067,77 @@ class WP_Test_REST_Attachments_Controller extends WP_Test_REST_Post_Type_Control $this->assertSame( $category['term_id'], $term[0]->term_id ); } + /** + * @ticket 41692 + */ + public function test_create_update_post_with_featured_media() { + // Add support for thumbnails on all attachment types to avoid incorrect-usage notice. + add_post_type_support( 'attachment', 'thumbnail' ); + + wp_set_current_user( self::$editor_id ); + + $request = new WP_REST_Request( 'POST', '/wp/v2/media' ); + $request->set_file_params( + array( + 'file' => array( + 'file' => file_get_contents( self::$test_file ), + 'name' => 'canola.jpg', + 'size' => filesize( self::$test_file ), + 'tmp_name' => self::$test_file, + ), + ) + ); + $request->set_header( 'Content-MD5', md5_file( self::$test_file ) ); + + $file = DIR_TESTDATA . '/images/canola.jpg'; + $attachment_id = self::factory()->attachment->create_object( + $file, + 0, + array( + 'post_mime_type' => 'image/jpeg', + 'menu_order' => rand( 1, 100 ), + ) + ); + + $request->set_param( 'featured_media', $attachment_id ); + + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + + $this->assertEquals( 201, $response->get_status() ); + + $new_attachment = get_post( $data['id'] ); + + $this->assertEquals( $attachment_id, (int) get_post_thumbnail_id( $new_attachment->ID ) ); + $this->assertEquals( $attachment_id, $data['featured_media'] ); + + $request = new WP_REST_Request( 'PUT', '/wp/v2/media/' . $new_attachment->ID ); + $params = $this->set_post_data( + array( + 'featured_media' => 0, + ) + ); + $request->set_body_params( $params ); + $response = rest_get_server()->dispatch( $request ); + $this->assertEquals( 200, $response->get_status() ); + $data = $response->get_data(); + $this->assertEquals( 0, $data['featured_media'] ); + $this->assertEquals( 0, (int) get_post_thumbnail_id( $new_attachment->ID ) ); + + $request = new WP_REST_Request( 'PUT', '/wp/v2/media/' . $new_attachment->ID ); + $params = $this->set_post_data( + array( + 'featured_media' => $attachment_id, + ) + ); + $request->set_body_params( $params ); + $response = rest_get_server()->dispatch( $request ); + $this->assertEquals( 200, $response->get_status() ); + $data = $response->get_data(); + $this->assertEquals( $attachment_id, $data['featured_media'] ); + $this->assertEquals( $attachment_id, (int) get_post_thumbnail_id( $new_attachment->ID ) ); + } + public function test_update_item() { wp_set_current_user( self::$editor_id ); $attachment_id = self::factory()->attachment->create_object( @@ -1576,7 +1647,7 @@ class WP_Test_REST_Attachments_Controller extends WP_Test_REST_Post_Type_Control $response = rest_get_server()->dispatch( $request ); $data = $response->get_data(); $properties = $data['schema']['properties']; - $this->assertCount( 27, $properties ); + $this->assertCount( 28, $properties ); $this->assertArrayHasKey( 'author', $properties ); $this->assertArrayHasKey( 'alt_text', $properties ); $this->assertArrayHasKey( 'caption', $properties ); @@ -1610,6 +1681,7 @@ class WP_Test_REST_Attachments_Controller extends WP_Test_REST_Post_Type_Control $this->assertArrayHasKey( 'rendered', $properties['title']['properties'] ); $this->assertArrayHasKey( 'type', $properties ); $this->assertArrayHasKey( 'missing_image_sizes', $properties ); + $this->assertArrayHasKey( 'featured_media', $properties ); } public function test_get_additional_field_registration() { diff --git a/tests/qunit/fixtures/wp-api-generated.js b/tests/qunit/fixtures/wp-api-generated.js index 8cf6b63cfb..b665646156 100644 --- a/tests/qunit/fixtures/wp-api-generated.js +++ b/tests/qunit/fixtures/wp-api-generated.js @@ -7315,6 +7315,11 @@ mockedApiResponse.Schema = { "type": "integer", "required": false }, + "featured_media": { + "description": "The ID of the featured media for the post.", + "type": "integer", + "required": false + }, "comment_status": { "description": "Whether or not comments are open on the post.", "type": "string", @@ -7517,6 +7522,11 @@ mockedApiResponse.Schema = { "type": "integer", "required": false }, + "featured_media": { + "description": "The ID of the featured media for the post.", + "type": "integer", + "required": false + }, "comment_status": { "description": "Whether or not comments are open on the post.", "type": "string", @@ -12693,6 +12703,7 @@ mockedApiResponse.MediaCollection = [ "rendered": "REST API Client Fixture: Attachment" }, "author": 0, + "featured_media": 0, "comment_status": "open", "ping_status": "closed", "template": "", @@ -12754,6 +12765,7 @@ mockedApiResponse.MediaModel = { "rendered": "REST API Client Fixture: Attachment" }, "author": 0, + "featured_media": 0, "comment_status": "open", "ping_status": "closed", "template": "",