Repository navigation
Conversation
file_is_displayable_image() only needs the image type, but it called wp_getimagesize(), which parses the image header to work out the dimensions before returning the type. Reading the type from the file signature is enough for this check, and it avoids the extra parsing for files that are only being tested for displayability. exif_imagetype() provides that signature check and reads the same header bytes. It is used when the exif extension is available, with wp_getimagesize() kept as a fallback so behaviour is unchanged when it is not. Errors are suppressed unless WP_DEBUG is on and the tests are not running, following the pattern already used by wp_get_image_mime(). Because the check is now based on the signature, a file whose signature is valid is treated as displayable even if the rest of its header cannot be parsed. Previously such a file was rejected here and discarded later by the image editor. Props joehoyle, jdgrimes, SergeyBiryukov. Fixes #28077.
Cover the result matching the previous behaviour for regular images and non-images, a strict boolean being passed to the filter, a nonexistent path, the filter passing its value through, and a valid signature being accepted when the rest of the header cannot be parsed. Props joehoyle, jdgrimes, SergeyBiryukov. Fixes #28077.
|
The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. 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. Props Bot commandsYou can trigger Props Bot actions by commenting on this PR:
|
|
| Location | Error | Identifier | First reported |
|---|---|---|---|
src/wp-admin/includes/image.php:1186 |
Call to function is_callable() with 'exif_imagetype' will always evaluate to true. |
function.alreadyNarrowedType |
4ac8f6a |
Running PHPStan locally
To run the analysis on your own machine, use npm run typecheck:php, or composer phpstan outside the Docker environment.
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. |
Summary
file_is_displayable_image()only needs the image type, but it obtains it fromwp_getimagesize(), which parses the image header far enough to determine the width and height before returning the type at index 2. Reading the type from the file signature is sufficient for this check and avoids that parsing.The function is a gate rather than a measurement: its only caller,
wp_generate_attachment_metadata(), uses it to decide whether the attachment is a displayable image at all, andwp_create_image_subsizes()measures the image afterwards.What this changes
exif_imagetype()reads the same header bytes asgetimagesize()but stops after matching the signature, and it returns the sameIMAGETYPE_*constant thatgetimagesize()returns at index 2. It is used when available:Three details:
The exif extension is optional, so the existing
wp_getimagesize()behaviour is retained as a fallback, as comment #1 asks. The fallback goes through the wrapper rather than baregetimagesize(), so the WebP, AVIF and HEIC handling added to that wrapper still applies when exif is unavailable.Error suppression is still needed. Comment #2 suggests
exif_imagetype()only emits a notice, so the@could be dropped. On current PHP it raisesE_WARNINGfor a path that does not exist, andE_NOTICEfor an empty file or for a directory, so dropping the suppression would introduce new warnings. The conditional pattern above is copied fromwp_get_image_mime(), which already wrapsexif_imagetype()this way.The result stays a strict boolean, because the filter documents
$resultas a bool.Behaviour change
The check is now based on the signature rather than on a successfully parsed header. A file whose signature is valid but whose remaining header data cannot be parsed is treated as displayable, where previously it was rejected here.
In practice the file still fails later:
wp_create_image_subsizes()callswp_get_image_editor(), which returnsWP_Error: File is not an image.for such a file. Nothing is generated either way, but the rejection now happens downstream. A test covers this so the trade-off is explicit.The two approaches were compared across all 56 image fixtures plus synthetic non-image, empty, nonexistent and truncated-file cases, and the results are identical apart from that one case. Type constants agree on every format, including AVIF, WebP, ICO, HEIC and TIFF.
Tests
tests/phpunit/tests/image/functions.phpadds coverage for the result matching the previous answer on regular images and non-images, a strict boolean reaching the filter, a nonexistent path, the filter's return value being passed through, and a valid signature being accepted when the rest of the header cannot be parsed.The signature test fails when the source change is reverted, which is the expected red state; the parity tests pass in both states by design, since they assert that existing behaviour is preserved.
Trac ticket: https://core.trac.wordpress.org/ticket/28077
Use of AI Tools
AI assistance: Yes
Tool(s): Claude
Model(s): Sonnet
Used for: test cases, code review
This Pull Request is for code review only. Please keep all other discussion in the Trac ticket. Do not merge this Pull Request. See GitHub Pull Requests for Code Review in the Core Handbook for more details.