@@ -3804,4 +3804,96 @@ public function test_sideload_image_size_invalid() {
38043804
38053805 $ this ->assertSame ( 400 , $ response ->get_status (), 'An unknown size name should be rejected. ' );
38063806 }
3807+
3808+ /**
3809+ * Tests that the sideload route declares `convert_format` as a boolean arg.
3810+ *
3811+ * Without this declaration, multipart/form-data requests deliver the value as
3812+ * a string ("false") which evaluates truthy in PHP, so the sideload handler's
3813+ * `if ( ! $request['convert_format'] )` check never fires and the
3814+ * `image_editor_output_format` filter is never suppressed - meaning the
3815+ * server still performs the format conversion the client opted out of.
3816+ *
3817+ * @ticket 64737
3818+ * @covers WP_REST_Attachments_Controller::register_routes
3819+ */
3820+ public function test_sideload_route_declares_convert_format_boolean () {
3821+ $ this ->enable_client_side_media_processing ();
3822+
3823+ $ routes = rest_get_server ()->get_routes ();
3824+ $ endpoint = '/wp/v2/media/(?P<id>[\d]+)/sideload ' ;
3825+ $ this ->assertArrayHasKey ( $ endpoint , $ routes , 'Sideload route should exist. ' );
3826+
3827+ $ args = $ routes [ $ endpoint ][0 ]['args ' ];
3828+
3829+ $ this ->assertArrayHasKey ( 'convert_format ' , $ args , 'Route should declare convert_format. ' );
3830+ $ this ->assertSame ( 'boolean ' , $ args ['convert_format ' ]['type ' ], 'convert_format should be a boolean. ' );
3831+ $ this ->assertTrue ( $ args ['convert_format ' ]['default ' ], 'convert_format should default to true. ' );
3832+ }
3833+
3834+ /**
3835+ * Tests that sideloading with `convert_format=false` (sent as the string
3836+ * "false", matching multipart/form-data semantics) suppresses the
3837+ * alt-extension collision check in `wp_unique_filename()`, so a companion
3838+ * file sharing the attachment basename does not get a numeric suffix.
3839+ *
3840+ * Mirrors the HEIC companion upload flow: a JPEG derivative is created via
3841+ * the media endpoint, then the original is sideloaded under the same stem.
3842+ * Without the arg declared as boolean, "false" coerces truthy, the filter
3843+ * is never added, and the companion is bumped to `-1` while the JPEG stays
3844+ * unsuffixed. PNG stands in for HEIC because core's default
3845+ * `image_editor_output_format` only maps HEIC/HEIF to JPEG; a local filter
3846+ * adds a PNG to JPEG mapping to trigger the same alt-ext check.
3847+ *
3848+ * @ticket 64737
3849+ * @covers WP_REST_Attachments_Controller::sideload_item
3850+ * @covers WP_REST_Attachments_Controller::register_routes
3851+ * @requires function imagejpeg
3852+ */
3853+ public function test_sideload_convert_format_false_suppresses_alt_ext_suffix () {
3854+ $ this ->enable_client_side_media_processing ();
3855+
3856+ wp_set_current_user ( self ::$ author_id );
3857+
3858+ // Upload a JPEG "parent" attachment the way client-side uploads do.
3859+ $ request = new WP_REST_Request ( 'POST ' , '/wp/v2/media ' );
3860+ $ request ->set_header ( 'Content-Type ' , 'image/jpeg ' );
3861+ $ request ->set_header ( 'Content-Disposition ' , 'attachment; filename=heic-companion.jpg ' );
3862+ $ request ->set_param ( 'generate_sub_sizes ' , false );
3863+ $ request ->set_body ( (string ) file_get_contents ( self ::$ test_file ) );
3864+
3865+ $ response = rest_get_server ()->dispatch ( $ request );
3866+ $ attachment_id = $ response ->get_data ()['id ' ];
3867+ $ this ->assertSame ( 201 , $ response ->get_status () );
3868+
3869+ // Simulate an alt-ext conversion mapping so an alt-extension companion
3870+ // (PNG here, HEIC in production) would otherwise get a `-1` suffix.
3871+ $ add_png_mapping = static function ( $ formats ) {
3872+ $ formats ['image/png ' ] = 'image/jpeg ' ;
3873+ return $ formats ;
3874+ };
3875+ add_filter ( 'image_editor_output_format ' , $ add_png_mapping , 5 );
3876+
3877+ // Sideload a companion sharing the same basename. Pass convert_format as
3878+ // the string "false" to match multipart/form-data request semantics.
3879+ $ request = new WP_REST_Request ( 'POST ' , "/wp/v2/media/ {$ attachment_id }/sideload " );
3880+ $ request ->set_header ( 'Content-Type ' , 'image/png ' );
3881+ $ request ->set_header ( 'Content-Disposition ' , 'attachment; filename=heic-companion.png ' );
3882+ $ request ->set_param ( 'image_size ' , 'original ' );
3883+ $ request ->set_param ( 'convert_format ' , 'false ' );
3884+ $ request ->set_body ( (string ) file_get_contents ( DIR_TESTDATA . '/images/one-blue-pixel-100x100.png ' ) );
3885+
3886+ $ response = rest_get_server ()->dispatch ( $ request );
3887+
3888+ remove_filter ( 'image_editor_output_format ' , $ add_png_mapping , 5 );
3889+
3890+ $ this ->assertSame ( 200 , $ response ->get_status () );
3891+
3892+ $ data = $ response ->get_data ();
3893+ $ this ->assertSame (
3894+ 'heic-companion.png ' ,
3895+ $ data ['file ' ],
3896+ 'Companion file should share the attachment basename without a numeric suffix. '
3897+ );
3898+ }
38073899}
0 commit comments