Changeset 63014
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php
r62841 r63014 460 460 // This preserves the original orientation value in the metadata. 461 461 add_filter( 'wp_image_maybe_exif_rotate', '__return_false', 100 ); 462 // Disable server-side "big image" downscaling; the client supplies its 463 // own scaled version via the sideload endpoint. Scaling here would 464 // create a conflicting "-scaled" file and orphan the full-size upload. 465 add_filter( 'big_image_size_threshold', '__return_false', 100 ); 462 466 } 463 467 … … 692 696 remove_filter( 'wp_image_maybe_exif_rotate', '__return_false', 100 ); 693 697 remove_filter( 'image_editor_output_format', '__return_empty_array', 100 ); 698 remove_filter( 'big_image_size_threshold', '__return_false', 100 ); 694 699 } 695 700 -
trunk/tests/phpunit/tests/rest-api/rest-attachments-controller.php
r62841 r63014 3877 3877 3878 3878 /** 3879 * When the client generates sub-sizes (generate_sub_sizes is false), the 3880 * server must not perform its own "big image" downscaling on upload. 3881 * 3882 * Otherwise the server creates a `-scaled` file and records the upload as 3883 * `original_image`. The client's subsequent scaled sideload then collides 3884 * with that `-scaled` file and is renamed `-scaled-1`, the thumbnails 3885 * inherit the numbered name, and the server-generated full-size file is 3886 * left orphaned on disk. 3887 * 3888 * @ticket 65708 3889 * @requires function imagejpeg 3890 */ 3891 public function test_create_item_skips_big_image_scaling_when_client_generates_sub_sizes() { 3892 $this->enable_client_side_media_processing(); 3893 3894 wp_set_current_user( self::$author_id ); 3895 3896 // Force the threshold below the image's dimensions so scaling would be 3897 // triggered were it not suppressed for client-side processing. 3898 add_filter( 3899 'big_image_size_threshold', 3900 static function () { 3901 return 1000; 3902 } 3903 ); 3904 3905 // Upload a large image with the client handling sub-size generation. 3906 $request = new WP_REST_Request( 'POST', '/wp/v2/media' ); 3907 $request->set_header( 'Content-Type', 'image/jpeg' ); 3908 $request->set_header( 'Content-Disposition', 'attachment; filename=33772.jpg' ); 3909 $request->set_param( 'generate_sub_sizes', false ); 3910 $request->set_body( file_get_contents( DIR_TESTDATA . '/images/33772.jpg' ) ); 3911 $response = rest_get_server()->dispatch( $request ); 3912 $data = $response->get_data(); 3913 $attachment_id = $data['id']; 3914 3915 $this->assertSame( 201, $response->get_status(), 'Uploading the image should succeed.' ); 3916 3917 // The uploaded full-size image should be stored untouched: no 3918 // server-side "-scaled" file and no original_image swap. 3919 $original_file = get_attached_file( $attachment_id, true ); 3920 $original_basename = wp_basename( $original_file ); 3921 $original_name_stem = pathinfo( $original_basename, PATHINFO_FILENAME ); 3922 $this->assertStringNotContainsString( '-scaled', $original_basename, 'The server should not create a -scaled file when the client generates sub-sizes.' ); 3923 3924 $metadata = wp_get_attachment_metadata( $attachment_id ); 3925 $this->assertArrayNotHasKey( 'original_image', $metadata, 'The server should not record an original_image when it does not scale the upload.' ); 3926 3927 // The client's scaled sideload should now record the untouched upload as 3928 // original_image and keep the -scaled name without a numeric suffix. 3929 $request = new WP_REST_Request( 'POST', "/wp/v2/media/{$attachment_id}/sideload" ); 3930 $request->set_header( 'Content-Type', 'image/jpeg' ); 3931 $request->set_header( 'Content-Disposition', "attachment; filename={$original_name_stem}-scaled.jpg" ); 3932 $request->set_param( 'image_size', 'scaled' ); 3933 $request->set_body( file_get_contents( DIR_TESTDATA . '/images/33772.jpg' ) ); 3934 $response = rest_get_server()->dispatch( $request ); 3935 3936 $this->assertSame( 200, $response->get_status(), 'Sideloading the scaled image should succeed.' ); 3937 3938 $sub_size = $response->get_data(); 3939 $this->assertSame( $original_basename, $sub_size['original_image'], 'The untouched upload should be recorded as original_image.' ); 3940 $this->assertSame( "{$original_name_stem}-scaled.jpg", wp_basename( $sub_size['file'] ), 'The scaled sideload should keep the -scaled name without a numeric collision suffix.' ); 3941 } 3942 3943 /** 3944 * The complete client-side flow for an image over the "big image" threshold 3945 * should write only files that the metadata tracks, so that deleting the 3946 * attachment removes all of them. 3947 * 3948 * When the server scales the upload as well, its own full-size file is 3949 * never referenced by the metadata and survives "Delete Permanently", the 3950 * client's scaled sideload collides with the server's "-scaled" file and is 3951 * stored as "-scaled-1", and the sub-sizes inherit the numbered name. 3952 * 3953 * @ticket 65708 3954 * @covers WP_REST_Attachments_Controller::create_item 3955 * @covers WP_REST_Attachments_Controller::sideload_item 3956 * @covers WP_REST_Attachments_Controller::finalize_item 3957 * @requires function imagejpeg 3958 */ 3959 public function test_client_side_big_image_flow_leaves_no_orphaned_files() { 3960 $this->enable_client_side_media_processing(); 3961 3962 wp_set_current_user( self::$author_id ); 3963 3964 // Force the threshold below the uploaded image's dimensions so scaling 3965 // would be triggered were it not suppressed for client-side processing. 3966 add_filter( 3967 'big_image_size_threshold', 3968 static function () { 3969 return 1000; 3970 } 3971 ); 3972 3973 $upload_dir = wp_upload_dir(); 3974 $files_before = (array) glob( $upload_dir['path'] . '/*' ); 3975 3976 // 1. Upload the full-size image; the client owns all the derivatives. 3977 // 33772.jpg is 1920x1080, so it exceeds the threshold above. 3978 $request = new WP_REST_Request( 'POST', '/wp/v2/media' ); 3979 $request->set_header( 'Content-Type', 'image/jpeg' ); 3980 $request->set_header( 'Content-Disposition', 'attachment; filename=big-photo.jpg' ); 3981 $request->set_param( 'generate_sub_sizes', false ); 3982 $request->set_body( file_get_contents( DIR_TESTDATA . '/images/33772.jpg' ) ); 3983 $response = rest_get_server()->dispatch( $request ); 3984 $attachment_id = $response->get_data()['id']; 3985 3986 $this->assertSame( 201, $response->get_status(), 'Uploading the image should succeed.' ); 3987 3988 /* 3989 * 2. Sideload a thumbnail, as the client does for each sub-size. The 3990 * client names it after the file it uploaded, so a server-side 3991 * rename of that file is what pushes this into a collision. 3992 * test-image.jpg is 50x50, within the registered thumbnail maximum. 3993 */ 3994 $request = new WP_REST_Request( 'POST', "/wp/v2/media/{$attachment_id}/sideload" ); 3995 $request->set_header( 'Content-Type', 'image/jpeg' ); 3996 $request->set_header( 'Content-Disposition', 'attachment; filename=big-photo-150x150.jpg' ); 3997 $request->set_param( 'image_size', 'thumbnail' ); 3998 $request->set_body( file_get_contents( DIR_TESTDATA . '/images/test-image.jpg' ) ); 3999 $response = rest_get_server()->dispatch( $request ); 4000 $thumbnail_data = $response->get_data(); 4001 4002 $this->assertSame( 200, $response->get_status(), 'Sideloading the thumbnail should succeed.' ); 4003 $this->assertSame( 'big-photo-150x150.jpg', wp_basename( $thumbnail_data['file'] ), 'The thumbnail should not inherit a numeric collision suffix.' ); 4004 4005 // 3. Sideload the scaled full-size image. canola.jpg is 640x480, the 4006 // size the client would have downscaled the upload to. 4007 $request = new WP_REST_Request( 'POST', "/wp/v2/media/{$attachment_id}/sideload" ); 4008 $request->set_header( 'Content-Type', 'image/jpeg' ); 4009 $request->set_header( 'Content-Disposition', 'attachment; filename=big-photo-scaled.jpg' ); 4010 $request->set_param( 'image_size', 'scaled' ); 4011 $request->set_body( file_get_contents( self::$test_file ) ); 4012 $response = rest_get_server()->dispatch( $request ); 4013 $scaled_data = $response->get_data(); 4014 4015 $this->assertSame( 200, $response->get_status(), 'Sideloading the scaled image should succeed.' ); 4016 4017 // 4. Finalize, which writes the collected sub-size metadata in one pass. 4018 $request = new WP_REST_Request( 'POST', "/wp/v2/media/{$attachment_id}/finalize" ); 4019 $request->set_param( 'sub_sizes', array( $thumbnail_data, $scaled_data ) ); 4020 $response = rest_get_server()->dispatch( $request ); 4021 4022 $this->assertSame( 200, $response->get_status(), 'Finalize should succeed.' ); 4023 4024 $metadata = wp_get_attachment_metadata( $attachment_id ); 4025 4026 $this->assertSame( 'big-photo.jpg', $metadata['original_image'], 'The untouched upload should be recorded as original_image.' ); 4027 $this->assertSame( 'big-photo-scaled.jpg', wp_basename( $metadata['file'] ), 'The client-supplied scaled image should become the attached file.' ); 4028 $this->assertSame( 'big-photo-150x150.jpg', $metadata['sizes']['thumbnail']['file'], 'The thumbnail should keep its dimension-based name.' ); 4029 4030 // Every file written for this attachment must be reachable from the 4031 // metadata, otherwise it is orphaned on disk. 4032 $written = array_map( 'wp_basename', array_diff( (array) glob( $upload_dir['path'] . '/*' ), $files_before ) ); 4033 sort( $written ); 4034 $this->assertSame( 4035 array( 'big-photo-150x150.jpg', 'big-photo-scaled.jpg', 'big-photo.jpg' ), 4036 $written, 4037 'The flow should write only the full-size upload, its scaled copy, and the sub-sizes.' 4038 ); 4039 4040 // Deleting the attachment should therefore clean all of them up. 4041 wp_delete_attachment( $attachment_id, true ); 4042 4043 $remaining = array_diff( (array) glob( $upload_dir['path'] . '/*' ), $files_before ); 4044 $this->assertSame( array(), array_values( $remaining ), 'Deleting the attachment should leave no files behind.' ); 4045 } 4046 4047 /** 3879 4048 * Tests that sideloading scaled image requires authentication. 3880 4049 *
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)