@@ -3154,4 +3154,193 @@ static function ( $data ) use ( &$captured_data ) {
31543154 // Verify that the data is an array (not an object).
31553155 $ this ->assertIsArray ( $ captured_data , 'Data passed to wp_insert_attachment should be an array ' );
31563156 }
3157+
3158+ /**
3159+ * Tests sideloading a scaled image for an existing attachment.
3160+ *
3161+ * @ticket 64737
3162+ * @requires function imagejpeg
3163+ */
3164+ public function test_sideload_scaled_image () {
3165+ wp_set_current_user ( self ::$ author_id );
3166+
3167+ // First, create an attachment.
3168+ $ request = new WP_REST_Request ( 'POST ' , '/wp/v2/media ' );
3169+ $ request ->set_header ( 'Content-Type ' , 'image/jpeg ' );
3170+ $ request ->set_header ( 'Content-Disposition ' , 'attachment; filename=canola.jpg ' );
3171+ $ request ->set_body ( file_get_contents ( self ::$ test_file ) );
3172+ $ response = rest_get_server ()->dispatch ( $ request );
3173+ $ data = $ response ->get_data ();
3174+ $ attachment_id = $ data ['id ' ];
3175+
3176+ $ this ->assertSame ( 201 , $ response ->get_status () );
3177+
3178+ $ original_file = get_attached_file ( $ attachment_id , true );
3179+
3180+ // Sideload a "scaled" version of the image.
3181+ $ request = new WP_REST_Request ( 'POST ' , "/wp/v2/media/ {$ attachment_id }/sideload " );
3182+ $ request ->set_header ( 'Content-Type ' , 'image/jpeg ' );
3183+ $ request ->set_header ( 'Content-Disposition ' , 'attachment; filename=canola-scaled.jpg ' );
3184+ $ request ->set_param ( 'image_size ' , 'scaled ' );
3185+ $ request ->set_body ( file_get_contents ( self ::$ test_file ) );
3186+ $ response = rest_get_server ()->dispatch ( $ request );
3187+
3188+ $ this ->assertSame ( 200 , $ response ->get_status (), 'Sideloading scaled image should succeed. ' );
3189+
3190+ $ metadata = wp_get_attachment_metadata ( $ attachment_id );
3191+
3192+ // The original file should now be recorded as original_image.
3193+ $ this ->assertArrayHasKey ( 'original_image ' , $ metadata , 'Metadata should contain original_image. ' );
3194+ $ this ->assertSame ( wp_basename ( $ original_file ), $ metadata ['original_image ' ], 'original_image should be the basename of the original attached file. ' );
3195+
3196+ // The attached file should now point to the scaled version.
3197+ $ new_file = get_attached_file ( $ attachment_id , true );
3198+ $ this ->assertStringContainsString ( 'scaled ' , wp_basename ( $ new_file ), 'Attached file should now be the scaled version. ' );
3199+
3200+ // Metadata should have width, height, filesize, and file updated.
3201+ $ this ->assertArrayHasKey ( 'width ' , $ metadata , 'Metadata should contain width. ' );
3202+ $ this ->assertArrayHasKey ( 'height ' , $ metadata , 'Metadata should contain height. ' );
3203+ $ this ->assertArrayHasKey ( 'filesize ' , $ metadata , 'Metadata should contain filesize. ' );
3204+ $ this ->assertArrayHasKey ( 'file ' , $ metadata , 'Metadata should contain file. ' );
3205+ $ this ->assertStringContainsString ( 'scaled ' , $ metadata ['file ' ], 'Metadata file should reference the scaled version. ' );
3206+ $ this ->assertGreaterThan ( 0 , $ metadata ['width ' ], 'Width should be positive. ' );
3207+ $ this ->assertGreaterThan ( 0 , $ metadata ['height ' ], 'Height should be positive. ' );
3208+ $ this ->assertGreaterThan ( 0 , $ metadata ['filesize ' ], 'Filesize should be positive. ' );
3209+ }
3210+
3211+ /**
3212+ * Tests that sideloading scaled image requires authentication.
3213+ *
3214+ * @ticket 64737
3215+ * @requires function imagejpeg
3216+ */
3217+ public function test_sideload_scaled_image_requires_auth () {
3218+ wp_set_current_user ( self ::$ author_id );
3219+
3220+ // Create an attachment.
3221+ $ request = new WP_REST_Request ( 'POST ' , '/wp/v2/media ' );
3222+ $ request ->set_header ( 'Content-Type ' , 'image/jpeg ' );
3223+ $ request ->set_header ( 'Content-Disposition ' , 'attachment; filename=canola.jpg ' );
3224+ $ request ->set_body ( file_get_contents ( self ::$ test_file ) );
3225+ $ response = rest_get_server ()->dispatch ( $ request );
3226+ $ attachment_id = $ response ->get_data ()['id ' ];
3227+
3228+ // Try sideloading without authentication.
3229+ wp_set_current_user ( 0 );
3230+
3231+ $ request = new WP_REST_Request ( 'POST ' , "/wp/v2/media/ {$ attachment_id }/sideload " );
3232+ $ request ->set_header ( 'Content-Type ' , 'image/jpeg ' );
3233+ $ request ->set_header ( 'Content-Disposition ' , 'attachment; filename=canola-scaled.jpg ' );
3234+ $ request ->set_param ( 'image_size ' , 'scaled ' );
3235+ $ request ->set_body ( file_get_contents ( self ::$ test_file ) );
3236+ $ response = rest_get_server ()->dispatch ( $ request );
3237+
3238+ $ this ->assertErrorResponse ( 'rest_cannot_edit_image ' , $ response , 401 );
3239+ }
3240+
3241+ /**
3242+ * Tests that the sideload endpoint includes 'scaled' in the image_size enum.
3243+ *
3244+ * @ticket 64737
3245+ */
3246+ public function test_sideload_route_includes_scaled_enum () {
3247+ $ server = rest_get_server ();
3248+ $ routes = $ server ->get_routes ();
3249+
3250+ $ endpoint = '/wp/v2/media/(?P<id>[\d]+)/sideload ' ;
3251+ $ this ->assertArrayHasKey ( $ endpoint , $ routes , 'Sideload route should exist. ' );
3252+
3253+ $ route = $ routes [ $ endpoint ];
3254+ $ endpoint = $ route [0 ];
3255+ $ args = $ endpoint ['args ' ];
3256+
3257+ $ param_name = 'image_size ' ;
3258+ $ this ->assertArrayHasKey ( $ param_name , $ args , 'Route should have image_size arg. ' );
3259+ $ this ->assertContains ( 'scaled ' , $ args [ $ param_name ]['enum ' ], 'image_size enum should include scaled. ' );
3260+ }
3261+
3262+ /**
3263+ * Tests the filter_wp_unique_filename method handles the -scaled suffix.
3264+ *
3265+ * @ticket 64737
3266+ * @requires function imagejpeg
3267+ */
3268+ public function test_sideload_scaled_unique_filename () {
3269+ wp_set_current_user ( self ::$ author_id );
3270+
3271+ // Create an attachment.
3272+ $ request = new WP_REST_Request ( 'POST ' , '/wp/v2/media ' );
3273+ $ request ->set_header ( 'Content-Type ' , 'image/jpeg ' );
3274+ $ request ->set_header ( 'Content-Disposition ' , 'attachment; filename=canola.jpg ' );
3275+ $ request ->set_body ( file_get_contents ( self ::$ test_file ) );
3276+ $ response = rest_get_server ()->dispatch ( $ request );
3277+ $ attachment_id = $ response ->get_data ()['id ' ];
3278+
3279+ // Sideload with the -scaled suffix.
3280+ $ request = new WP_REST_Request ( 'POST ' , "/wp/v2/media/ {$ attachment_id }/sideload " );
3281+ $ request ->set_header ( 'Content-Type ' , 'image/jpeg ' );
3282+ $ request ->set_header ( 'Content-Disposition ' , 'attachment; filename=canola-scaled.jpg ' );
3283+ $ request ->set_param ( 'image_size ' , 'scaled ' );
3284+ $ request ->set_body ( file_get_contents ( self ::$ test_file ) );
3285+ $ response = rest_get_server ()->dispatch ( $ request );
3286+
3287+ $ this ->assertSame ( 200 , $ response ->get_status (), 'Sideloading scaled image should succeed. ' );
3288+
3289+ // The filename should retain the -scaled suffix without numeric disambiguation.
3290+ $ new_file = get_attached_file ( $ attachment_id , true );
3291+ $ basename = wp_basename ( $ new_file );
3292+ $ this ->assertMatchesRegularExpression ( '/canola-scaled\.jpg$/ ' , $ basename , 'Scaled filename should not have numeric suffix appended. ' );
3293+ }
3294+
3295+ /**
3296+ * Tests that sideloading a scaled image for a different attachment retains the numeric suffix
3297+ * when a file with the same name already exists on disk.
3298+ *
3299+ * @ticket 64737
3300+ * @requires function imagejpeg
3301+ */
3302+ public function test_sideload_scaled_unique_filename_conflict () {
3303+ wp_set_current_user ( self ::$ author_id );
3304+
3305+ // Create the first attachment.
3306+ $ request = new WP_REST_Request ( 'POST ' , '/wp/v2/media ' );
3307+ $ request ->set_header ( 'Content-Type ' , 'image/jpeg ' );
3308+ $ request ->set_header ( 'Content-Disposition ' , 'attachment; filename=canola.jpg ' );
3309+ $ request ->set_body ( file_get_contents ( self ::$ test_file ) );
3310+ $ response = rest_get_server ()->dispatch ( $ request );
3311+ $ attachment_id_a = $ response ->get_data ()['id ' ];
3312+
3313+ // Sideload a scaled image for attachment A, creating canola-scaled.jpg on disk.
3314+ $ request = new WP_REST_Request ( 'POST ' , "/wp/v2/media/ {$ attachment_id_a }/sideload " );
3315+ $ request ->set_header ( 'Content-Type ' , 'image/jpeg ' );
3316+ $ request ->set_header ( 'Content-Disposition ' , 'attachment; filename=canola-scaled.jpg ' );
3317+ $ request ->set_param ( 'image_size ' , 'scaled ' );
3318+ $ request ->set_body ( file_get_contents ( self ::$ test_file ) );
3319+ $ response = rest_get_server ()->dispatch ( $ request );
3320+
3321+ $ this ->assertSame ( 200 , $ response ->get_status (), 'First sideload should succeed. ' );
3322+
3323+ // Create a second, different attachment.
3324+ $ request = new WP_REST_Request ( 'POST ' , '/wp/v2/media ' );
3325+ $ request ->set_header ( 'Content-Type ' , 'image/jpeg ' );
3326+ $ request ->set_header ( 'Content-Disposition ' , 'attachment; filename=other.jpg ' );
3327+ $ request ->set_body ( file_get_contents ( self ::$ test_file ) );
3328+ $ response = rest_get_server ()->dispatch ( $ request );
3329+ $ attachment_id_b = $ response ->get_data ()['id ' ];
3330+
3331+ // Sideload scaled for attachment B using the same filename that already exists on disk.
3332+ $ request = new WP_REST_Request ( 'POST ' , "/wp/v2/media/ {$ attachment_id_b }/sideload " );
3333+ $ request ->set_header ( 'Content-Type ' , 'image/jpeg ' );
3334+ $ request ->set_header ( 'Content-Disposition ' , 'attachment; filename=canola-scaled.jpg ' );
3335+ $ request ->set_param ( 'image_size ' , 'scaled ' );
3336+ $ request ->set_body ( file_get_contents ( self ::$ test_file ) );
3337+ $ response = rest_get_server ()->dispatch ( $ request );
3338+
3339+ $ this ->assertSame ( 200 , $ response ->get_status (), 'Second sideload should succeed. ' );
3340+
3341+ // The filename should have a numeric suffix since the base name does not match this attachment.
3342+ $ new_file = get_attached_file ( $ attachment_id_b , true );
3343+ $ basename = wp_basename ( $ new_file );
3344+ $ this ->assertMatchesRegularExpression ( '/canola-scaled-\d+\.jpg$/ ' , $ basename , 'Scaled filename should have numeric suffix when file conflicts with a different attachment. ' );
3345+ }
31573346}
0 commit comments