Repository navigation
Media: Add JPEG XL (JXL) upload support (backport GB #77584) - #12006
adamsilverstein wants to merge 21 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().
…back # Conflicts: # src/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php
…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).
…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.
…rvation. Add server-side support for the client-side media flow that decodes JPEG XL uploads to JPEG in the browser via lazy-loaded VIPS/WASM and preserves the original .jxl as a companion file (under $metadata['original'], the same slot the HEIC companion uses). * Register `image/jxl` as an allowed upload MIME type. WordPress core does not include JXL in its default MIME list, so without this filter the editor's allowed-types check rejects the original .jxl before it can be converted. * Add `wp_is_jxl_file()`, a magic-bytes check that recognizes both JXL flavors (the naked codestream beginning 0xFF 0x0A and the 12-byte ISOBMFF container signature). * Add `wp_filter_jxl_filetype_and_ext` on `wp_check_filetype_and_ext`. PHP's fileinfo reports JXL as `image/x-jxl` (and getimagesize() can't identify it at all), so core's MIME-mismatch check otherwise rejects the upload. When the file is genuinely JXL, restore the expected ext/type so the upload is allowed. * Accept `original-jxl` as a sideload `image_size`. The branch is collapsed with `original-heic` since both write to $metadata['original'] and are mutually exclusive. * Rename `wp_delete_attachment_heic_companion_file()` to `wp_delete_attachment_preserved_original_companion_file()` since the same hook now covers both HEIC and JXL companions. The implementation is unchanged; only the name and docblock are generalized. The test file is renamed alongside. Backport of WordPress/gutenberg#77584. Stacked on WordPress#11323 (HEIC canvas fallback).
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. |
Adding JPEG XL upload support exposes the original-jxl size in the attachment schema, so the generated QUnit fixture must be rebuilt to match. Keeps the "version-controlled files not modified" CI check green.
|
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. |
…ate delete Align the JPEG XL backport with the single companion-cleanup convention: the preserved source-format original (HEIC or JXL) is recorded under the `source_image` attachment-metadata key and deleted by one `delete_attachment` hook that lands in the HEIC upload backport. - Write the sideloaded `original-jxl` companion to `$metadata['source_image']` instead of a bare `original` key. - Drop `wp_delete_attachment_preserved_original_companion_file()` and its hook; the HEIC backport registers the single cleanup hook that reads `source_image`. - Drop the `original-heic` image_size enum value and branch (owned by the HEIC backport); this PR carries only `original-jxl`. - Update the route enum test to assert `original-jxl`, remove the orphaned preserved-original delete test, and drop the HEIC sideload test (its path is identical to the JXL one and is covered by the HEIC backport).
|
Marking as draft since we are punting JXL support for client side media for now, see WordPress/gutenberg#77584 |
# Conflicts: # src/wp-includes/rest-api/class-wp-rest-server.php # src/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php # tests/qunit/fixtures/wp-api-generated.js
Nothing in sideload_item() reads it - the only readers are create_item() and its permissions check, which use the argument registered by get_endpoint_args_for_item_schema(). Trunk now asserts the sideload route does not advertise it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AGarM6qGwHbEknVuo9tTHS
The blanket bypass also covered image/heic-sequence and image/heif-sequence, which trunk asserts must stay rejected because neither the server nor the client fallback can handle them. The still-image bypass belongs to its own backport, WordPress#13042. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AGarM6qGwHbEknVuo9tTHS
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AGarM6qGwHbEknVuo9tTHS
The branch adds image_output_formats to the REST index; the entry was lost while resolving the trunk merge in wp-api-generated.js. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AGarM6qGwHbEknVuo9tTHS
# Conflicts: # src/wp-includes/rest-api/class-wp-rest-server.php
Core also empties the type when image/jxl is not an allowed MIME type, so restoring it whenever the type was empty let JXL uploads through on sites that removed it, such as a network whose upload file types leave it out. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AcYRm3egtiB9yfX1VFaatb
7.2 is in beta, so the JXL support lands in the next release. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AcYRm3egtiB9yfX1VFaatb
Gutenberg now keeps a JXL upload in its own format and generates its sub-sizes as JXL in the browser, instead of converting it to JPEG. That needs the server to know the image's size, which getimagesize() cannot read for JXL. Teach wp_getimagesize() to read it from the codestream header, and build attachment metadata for image/jxl, so core reports the missing sub-sizes and the sideload endpoint accepts them. Also register JXL ahead of check_upload_mimes(), leave untyped arguments in the filetype filter alone, and have wp_is_jxl_file() share the codestream reader. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017uBiB3PfgBJpX6dbD1mN4B
Claude reworked this description to match the new direction in the Gutenberg PR:
AI Use
Code and description both written with 🤖 Claude Code. I will review and test.