Media: Enable HEIC/HEIF uploads when server lacks image editor support#11323
Media: Enable HEIC/HEIF uploads when server lacks image editor support#11323adamsilverstein wants to merge 35 commits into
Conversation
Bypass the `wp_prevent_unsupported_mime_type_uploads` check for HEIC/HEIF images so they can be stored even when the server's image editor doesn't support them. The client-side canvas fallback handles processing using the browser's native HEVC decoder via createImageBitmap().
|
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 Core Committers: Use this line as a base for the props when committing in SVN: To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
Test using WordPress PlaygroundThe changes in this pull request can previewed and tested using a WordPress Playground instance. WordPress Playground is an experimental project that creates a full WordPress instance entirely within the browser. Some things to be aware of
For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation. |
…back # Conflicts: # src/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php
|
Note for follow-up: when r62081 (commit c863860, "Media: Remove client-side media processing feature for now") is reverted to reintroduce client-side media processing in 7.1, the Original block from this PR (dropped during the trunk merge since // When the client handles image processing (generate_sub_sizes is false),
// skip the server-side image editor support check.
if ( false === $request['generate_sub_sizes'] ) {
$prevent_unsupported_uploads = false;
} |
|
Heads up, there was a follow up to WordPress/gutenberg#76731:
Not sure if it needs a core sync at all since it's a GB load.php change. 🤔 |
Possibly, I will review once the 7.1 branch is open and I am able to reintroduce the feature in core. Thanks for cross linking this ticket! |
…back # Conflicts: # src/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php
Extends the /wp/v2/media/<id>/sideload route so the client-side media flow can upload a HEIC/HEIF companion original alongside the JPEG derivative: - Adds 'original-heic' to the allowed image_size enum. The companion filename is recorded under $metadata['original'] so it never collides with 'original_image', which the scaled-sideload flow owns. - Adds a 'generate_sub_sizes' boolean arg (default false) so callers that handle processing client-side can suppress server-side sub-size generation per request. - Adds 'image/heif' to the image_output_formats input list returned by the REST API root index. Backport of GB #76731.
When the client-side media flow sideloads a HEIC original alongside a JPEG derivative, the HEIC filename is stored in $metadata['original']. wp_delete_attachment_files() only tracks 'original_image', so without this hook the HEIC file would linger on disk after the attachment is removed. wp_delete_attachment_heic_companion_file() reads the meta key, guards against non-string values (e.g. arrays written by other flows), and deletes the file when present. Hooked into the delete_attachment action via default-filters.php. Backport of GB #76731, with the is_string() guard from GB #78128.
Adds REST API controller tests: - The sideload route exposes 'original-heic' in the image_size enum. - The sideload route exposes a 'generate_sub_sizes' boolean arg defaulting to false. - Sideloading an 'original-heic' image writes the filename to $metadata['original'] and leaves 'original_image' untouched. Adds wp_delete_attachment_heic_companion_file() unit tests: - The companion HEIC is removed when the attachment is deleted. - The hook is a no-op when $metadata['original'] is missing. - The hook bails when $metadata['original'] is not a string (regression coverage for the guard added in GB #78128).
707d121 to
b20bbca
Compare
…deload. The test was sending JPEG bytes with a .heic filename, which wp_check_filetype_and_ext() corrected to canola-1.jpg before the metadata assertion ran. Switch to the real test-image.heic fixture, set Content-Type accordingly, and pass convert_format=false to disable the default HEIC -> JPEG output mapping so the .heic extension is preserved.
Add 'original-heic' to the image_size enum and the missing generate_sub_sizes arg so the schema fixture matches what the live REST index now reports. Without this the test-fixtures step fails the git diff --exit-code check.
westonruter
left a comment
There was a problem hiding this comment.
Review from Claude Code (/code-review xhigh-effort pass), posted by @westonruter. Findings left inline; the bottom half are cleanup/altitude notes rather than bugs — dismiss anything that doesn't land.
Co-authored-by: Weston Ruter <westonruter@gmail.com>
Harden the companion-original handling surfaced in review: - Rename the companion metadata key from the over-generic 'original' to 'source_image' so unrelated plugin or theme data stored under 'original' can no longer drive file deletion on attachment delete. - Add IMAGE_SIZE_SOURCE_ORIGINAL and META_KEY_SOURCE_IMAGE class constants so the sideload image_size enum and its dispatch branch cannot drift. - Drop the unused generate_sub_sizes argument from the /sideload route schema; only create_item() reads it, so advertising it on sideload silently misleads clients. - Advertise the HEIC/HEIF -sequence variants in the REST index input formats so they match wp_is_heic_image_mime_type(). - Return a boolean from wp_delete_attachment_heic_companion_file() and strengthen the non-string guard test with a real on-disk bystander file so the regression it protects against can actually fail.
da64fe4 to
7f976bb
Compare
Co-authored-by: Weston Ruter <westonruter@gmail.com>
* Media: Rename HEIC companion metadata key to 'source_image' During the wordpress-develop backport review (WordPress/wordpress-develop#11323), the metadata key recording the sideloaded source-format original was renamed from the generic 'original' to the dedicated 'source_image', and the size token and the key were promoted to controller constants so the sideload schema and the metadata writer cannot drift apart. Port those changes back so the plugin and core agree on the key, and align the delete_attachment cleanup hook with the core implementation. Add PHPUnit coverage for the renamed key and the cleanup hook. * Add backport changelog entry linking to core PR #11323
* Media: Rename HEIC companion metadata key to 'source_image' During the wordpress-develop backport review (WordPress/wordpress-develop#11323), the metadata key recording the sideloaded source-format original was renamed from the generic 'original' to the dedicated 'source_image', and the size token and the key were promoted to controller constants so the sideload schema and the metadata writer cannot drift apart. Port those changes back so the plugin and core agree on the key, and align the delete_attachment cleanup hook with the core implementation. Add PHPUnit coverage for the renamed key and the cleanup hook. * Add backport changelog entry linking to core PR #11323 Source: WordPress/gutenberg@acb09cb
…into add/heic-canvas-fallback
…)/upload_from_file()
Install phpstan/phpstan-phpunit (pinned to 2.0.16) and include its extension.neon so PHPUnit assertions such as assertArrayHasKey(), assertInstanceOf(), and assertNotNull() narrow types during analysis. Only extension.neon is included, not the extension's rules.neon, to avoid introducing new strict rules and the errors they would surface. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
The changes in this PR now pass PHPStan rule level 10. I can cherry-pick the PHPStan-specific changes into a new core commit for Core-64898 so there's less to commit here. But I wanted to include the type definitions to reference with reviewing. |
| * width: int<1, max>, | ||
| * height: int<1, max>, | ||
| * file: non-empty-string, | ||
| * source_image?: non-empty-string, |
There was a problem hiding this comment.
This source_image key is newly introduced in this PR.
… types The @phpstan-return shape previously required width, height, file, and sizes, which only holds for raster images. PDFs expose just sizes and filesize, while audio/video attachments produce an entirely different set of keys. Mark those keys optional so non-image attachments are typed correctly, and add the documented-but-missing filesize and image_meta keys. Unseal the per-size arrays and add their filesize key as well, since size items ride through the same metadata filters and can carry plugin-added keys such as modern-format sources. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…tself test_sideload_route_includes_and_excludes_expected_fields treated the value returned by array_first( $routes[ $path ] ) as the args map, but that value is the route endpoint whose argument definitions live under its 'args' key. As a result assertArrayHasKey( 'image_size', ... ) failed on every PHP version, turning the whole PHPUnit matrix red. Descend into ['args'] first, matching the structure the sibling scaled-enum assertions already relied on.
|
This should be ready to go. |
Move the `@phpstan-return`/`@phpstan-param` annotations and the phpstan-phpunit extension out of this feature branch and into PR WordPress#12313 so the static-analysis work can be reviewed independently of the HEIC upload feature, per code-review feedback. Also correct the `$posts_clauses` test docblock: each recorded entry is the array of SQL clause fragments from the `posts_clauses` filter, so the type is `array[]`, not `string[]`.
Trac ticket: https://core.trac.wordpress.org/ticket/64915
Note: merge after client side media is restored in core.
What?
Allow HEIC/HEIF image uploads to succeed even when the server's image editor (ImageMagick/GD) doesn't support HEIC. Currently,
wp_prevent_unsupported_mime_type_uploadsblocks these uploads entirely.Gutenberg PR: WordPress/gutenberg#76731
Why?
HEIC is the default photo format on iPhones. When users upload HEIC images and the server can't process them, the upload fails with an unhelpful error. With the client-side media processing feature, the browser can decode HEIC using its native
createImageBitmap()API (leveraging OS-licensed HEVC codecs) and convert to JPEG for sub-size generation. But first, the server needs to accept the upload.How?
In
WP_REST_Attachments_Controller::create_item_permissions_check(), bypass the$prevent_unsupported_uploadscheck when the uploaded file is HEIC/HEIF (detected via the existingwp_is_heic_image_mime_type()helper). This allows the file to be stored on the server, after which the client-side canvas fallback generates a JPEG version and all required sub-sizes.A new
original-heicsideload image-size token records the source-format original under a dedicatedsource_imagemetadata key (so it never collides withoriginal_image), andwp_delete_attachment_heic_companion_file()cleans that companion file up on attachment delete.Behavior note
The HEIC bypass is unconditional for the HEIC/HEIF mime family — it does not require
generate_sub_sizes === false. As a result, a non-browser REST client uploading HEIC to a server without HEIC support now receives a stored attachment with emptymissing_image_sizesinstead of the previousrest_upload_image_type_not_supportederror. This is intentional: the browser can always decode HEIC client-side, and raw API consumers can generate sub-sizes themselves. Flagging it as a deliberate contract change forPOST /wp/v2/media.Related
@phpstan-return/@phpstan-paramannotations and the phpstan-phpunit extension that previously lived on this branch were moved to Code Quality: Add PHPStan type coverage for media and upload functions #12313 so the static-analysis work can be reviewed independently. When both land, the affected code passes PHPStan rule level 10.Testing Instructions
rest_upload_image_type_not_supportederror)missing_image_sizesis populated in the response (server couldn't generate sub-sizes)