Skip to content

Media: Cover client-side big image scaling and orphaned files - #81061

Open
adamsilverstein wants to merge 4 commits into
trunkfrom
fix/65708-heic-scaled-orphan
Open

Media: Cover client-side big image scaling and orphaned files#81061
adamsilverstein wants to merge 4 commits into
trunkfrom
fix/65708-heic-scaled-orphan

Conversation

@adamsilverstein

@adamsilverstein adamsilverstein commented Aug 3, 2026

Copy link
Copy Markdown
Member

Related: https://core.trac.wordpress.org/ticket/65708

The Gutenberg plugin already suppresses server-side "big image" downscaling while the client owns the derivatives (added in #75817), but nothing tested it. Every existing test in this area uses an image below the threshold, so a regression here would have gone unnoticed - which is roughly what happened in core, where the same filter never made it into WP_REST_Attachments_Controller. That gap is what @ianmjones ran into on the Trac ticket.

When the server scales the upload as well, three things go wrong at once:

  • the client's own scaled file collides with the server's and is stored as -scaled-1
  • the sub-sizes inherit the numbered name (-150x150-1 and so on)
  • the server's full-size file is no longer referenced by the metadata, so it is orphaned on disk and survives "Delete Permanently"

This PR adds coverage for all three, and aligns the filter callback with the one core is adding in WordPress/wordpress-develop#12689 (__return_false, which is what the big_image_size_threshold docblock documents as the way to disable scaling). Same callback and priority means the plugin's registration and core's collapse into one instead of stacking.

I verified the tests catch the bug: with the plugin's filter removed, the e2e assertion fails on -scaled-1.jpeg and the PHP tests fail on big-photo-scaled.jpg / big-photo-150x150-1.jpg. With the filter in place they pass.

Note that the ticket describes this with a HEIC upload, but it is not HEIC specific - any image over the threshold hits it once the client owns the sub-sizes. The tests use a plain JPEG so they can run in Chromium, which has no native HEIC decode.

Why not a client-side change

The client is doing the right thing here. generate_sub_sizes: false is the contract that says the client owns every derivative including the scaled full-size copy, so the fix belongs on the server side of that contract. Core's PR does the same thing.

Testing instructions

Test in WordPress Playground

Automated:

npm run test:unit:php -- --filter Gutenberg_REST_Attachments_Controller_Test
npm run test:e2e -- test/e2e/specs/editor/various/big-image-size-threshold.spec.js

To see them fail, comment out the big_image_size_threshold filter in Gutenberg_REST_Attachments_Controller::create_item() and re-run.

Manual:

  1. In a browser with client-side media processing available (Chrome 137+), add an Image block.
  2. Upload an image larger than 2560px on its longest side.
  3. Check the attachment in the Media Library, or wp post meta get <id> _wp_attachment_metadata.
  4. file should be <name>-scaled.jpg and original_image should be <name>.jpg, with sub-sizes named <name>-WxH.jpg. No -1 anywhere.
  5. Delete the attachment permanently and confirm nothing is left in wp-content/uploads.

Types of changes

  • Align the big_image_size_threshold filter callback with core's.
  • Add PHP coverage for the upload not being scaled, and for the full upload/sideload/finalize flow leaving no orphaned files.
  • Tighten the big image threshold e2e spec: assert exact file names instead of the conditional checks that were passing either way.

The client owns every derivative when generate_sub_sizes is false, so the
server must not scale the upload. Use the same callback and priority core
uses for this so the two registrations collapse into one instead of
stacking.
The existing coverage used images below the threshold, so nothing caught
the server scaling an upload the client also scales: the client's file
lands as -scaled-1, sub-sizes inherit the number, and the server's
full-size file is orphaned. Assert the exact file names and that the
attachment leaves no untracked files behind.
@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Co-authored-by: adamsilverstein <adamsilverstein@git.wordpress.org>
Co-authored-by: andrewserong <andrewserong@git.wordpress.org>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@adamsilverstein adamsilverstein added [Type] Bug An existing feature does not function as intended [Feature] Media Anything that impacts the experience of managing media labels Aug 3, 2026
@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown

Size Change: 0 B

Total Size: 7.81 MB

compressed-size-action

@adamsilverstein
adamsilverstein requested review from andrewserong and swissspidy and removed request for spacedmonkey August 5, 2026 06:32
@adamsilverstein adamsilverstein added the No Core Sync Required Indicates that any changes do not need to be synced to WordPress Core label Aug 5, 2026
@adamsilverstein adamsilverstein self-assigned this Aug 5, 2026
@adamsilverstein

Copy link
Copy Markdown
Member Author

This is already fixed in core, no backport required.

@andrewserong andrewserong left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for porting this to GB! Left a tiny nit-picky comment, feel free to ignore 😄

Tested in Playground and confirmed no unexpected -1 in the scaled filename in the media object returned by the REST API.

LGTM 👍

Comment on lines +581 to +590
/*
* Disable server-side "big image" downscaling; the client supplies
* its own scaled version through the sideload endpoint. Scaling
* here would create a conflicting `-scaled` file and orphan the
* full-size upload. Uses the same callback and priority as
* WP_REST_Attachments_Controller::create_item() so the two
* registrations collapse into one on WordPress versions that
* apply it themselves.
*/
add_filter( 'big_image_size_threshold', '__return_false', 100 );

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Totally optional: since we already use the exact same code in core, should we also re-use the same code comment?

Suggested change
/*
* Disable server-side "big image" downscaling; the client supplies
* its own scaled version through the sideload endpoint. Scaling
* here would create a conflicting `-scaled` file and orphan the
* full-size upload. Uses the same callback and priority as
* WP_REST_Attachments_Controller::create_item() so the two
* registrations collapse into one on WordPress versions that
* apply it themselves.
*/
add_filter( 'big_image_size_threshold', '__return_false', 100 );
// Disable server-side "big image" downscaling; the client supplies its
// own scaled version via the sideload endpoint. Scaling here would
// create a conflicting "-scaled" file and orphan the full-size upload.
add_filter( 'big_image_size_threshold', '__return_false', 100 );

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[Feature] Media Anything that impacts the experience of managing media No Core Sync Required Indicates that any changes do not need to be synced to WordPress Core [Type] Bug An existing feature does not function as intended

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants