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