Repository navigation
Conversation
…Editor::make_image()`. When saving an image to a stream, `make_image()` ignored the return values of `fwrite()` and `fclose()`, so a save that failed to write the image still reported success. This is used by `WP_Image_Editor_GD` for stream destinations. Some stream wrappers, such as those for remote storage, only send the data when the stream is flushed or closed. As of PHP 8.6, `fclose()` returns `false` when flushing or closing a stream fails, which is the only way such a failure can be detected. `make_image()` now returns `false` when the image is not fully written or the stream cannot be closed. This matches `WP_Image_Editor_Imagick::write_image()`, which already checks the return value of `file_put_contents()`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011Y86gwJZGdkh78pggUy4zs
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. |
There was a problem hiding this comment.
🟢 Approval recommended
No active review issues are recorded for the changes that report incomplete stream writes and close failures.
No open issues 🎉
What changed in this PR
This PR makes image saves to streams report failure when writing is incomplete or closing the stream fails, allowing existing GD callers to return image_save_error.
Changes:
- Checks the number of bytes written and the result of
fclose()inWP_Image_Editor::make_image(). - Adds GD tests for successful saves, write failures, and flush failures detected on PHP 8.6+. The successful-save test depends on #15476 on PHP 8.6.
| File | Description |
|---|---|
tests/phpunit/tests/image/editorGd.php |
Adds stream-save tests and failure-simulating wrappers. |
src/wp-includes/class-wp-image-editor.php |
Checks stream write and close results and documents the behavior change. |
Review effort: Balanced
|
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:
|
|
There are some potential risks to this change as summarized by Claude:
|
| */ | ||
| $closed = fclose( $fp ); | ||
|
|
||
| if ( strlen( $contents ) !== $bytes_written || ! $closed ) { |
There was a problem hiding this comment.
I noticed that in addition to the false return check, this PR adds a length check (comparing the contents length to the written length. While this seems fine, I did wonder if there would be unanticipated edge cases where this might not be true even after a successful write (not sure how, just a feeling). I'll research this a bit further with Claude before approving. I'll also check if Imagick has a similar guard.
There was a problem hiding this comment.
Imagick uses file_put_contents instead of fopen, fwrite and fclose. Can we use that same approach here? does that function properly return an error now if stream writing fails?
There was a problem hiding this comment.
Claude made the same suggestion when reviewing:
Replace the fopen/fwrite/fclose block with file_put_contents( $filename, $contents ) !== false. That gives the length check, the 8.6 close-failure detection, and exact parity with Imagick in one call that PHP maintains. It's also less code.
adamsilverstein
left a comment
There was a problem hiding this comment.
Overall looks good, left a small suggestion.
Replaces the `fopen()`, `fwrite()`, and `fclose()` calls in `WP_Image_Editor::make_image()` with a single `file_put_contents()` call, matching `WP_Image_Editor_Imagick::write_image()`. `file_put_contents()` returns `false` when the stream cannot be opened or the image cannot be fully written, and as of PHP 8.6, when flushing or closing the stream fails. That covers the same failures as the separate byte count and `fclose()` checks, using the length check PHP itself maintains. An incomplete write now also triggers the PHP warning that `file_put_contents()` raises, as it already does for Imagick. `test_save_to_stream_fails_when_write_fails()` captures and asserts that warning. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When saving an image to a stream, `WP_Image_Editor::make_image()` currently ignores the return values of `fwrite()` and `fclose()`. A save that failed to write the image still reported success. `WP_Image_Editor_GD` uses this path for all stream destinations. The buffered image is now written with `file_put_contents()`, and `make_image()` returns `false` when that fails. This happens when the stream cannot be opened or the image is only partially written. As of PHP 8.6, it also happens when flushing or closing the stream fails. For stream wrappers that only send data on flush or close, a failed upload was previously reported as a successful save. Streams are now written the same way as in `WP_Image_Editor_Imagick::write_image()`, so both editors report the same failures. Developed in #15477. Props adamsilverstein. Fixes #66486. git-svn-id: https://develop.svn.wordpress.org/trunk@65294 602fd350-edb4-49c9-b593-d223f7449a82
When saving an image to a stream, `WP_Image_Editor::make_image()` currently ignores the return values of `fwrite()` and `fclose()`. A save that failed to write the image still reported success. `WP_Image_Editor_GD` uses this path for all stream destinations. The buffered image is now written with `file_put_contents()`, and `make_image()` returns `false` when that fails. This happens when the stream cannot be opened or the image is only partially written. As of PHP 8.6, it also happens when flushing or closing the stream fails. For stream wrappers that only send data on flush or close, a failed upload was previously reported as a successful save. Streams are now written the same way as in `WP_Image_Editor_Imagick::write_image()`, so both editors report the same failures. Developed in WordPress/wordpress-develop#15477. Props adamsilverstein. Fixes #66486. Built from https://develop.svn.wordpress.org/trunk@65294 git-svn-id: http://core.svn.wordpress.org/trunk@64443 1a063a9b-81f0-0310-95a4-ce76da25c4cd
When saving an image to a stream,
WP_Image_Editor::make_image()ignores the return values offwrite()andfclose(). A save that fails to write the image still reports success.WP_Image_Editor_GDuses this path for all stream destinations.This matters for stream wrappers that only send data when the stream is flushed or closed. For example, the AWS SDK's S3 stream wrapper performs the
PutObjectupload instream_flush(). If that upload fails, GD reports the image as saved even though it was never stored.As of PHP 8.6,
fclose()returnsfalsewhen flushing or closing a stream fails (php/php-src@c07cc85), so this failure can now be detected.WP_Image_Editor_Imagick::write_image()already checks the return value offile_put_contents()for streams, so the two editors currently behave differently for the same failing stream.Changes
make_image()now returnsfalsewhenfwrite()does not write the full image, or whenfclose()fails. Callers already turn afalseresult into animage_save_error.Tests_Image_Editor_GD:test_save_to_stream: a successful save to a stream.test_save_to_stream_fails_when_write_fails: the stream accepts no data (all PHP versions).test_save_to_stream_fails_when_flush_fails: the stream fails to flush (@requires PHP >= 8.6, as earlier versions cannot detect this).Dependency
On PHP 8.6,
test_save_to_streamrequires #15476, which implementsstream_flush()inWP_Test_Stream. Without it, writes toWP_Test_Streamfail to flush on PHP 8.6, and this change correctly reports them as failures.Testing
Ran against
trunk(--group image,media), with #15476 applied:imageandmediagroupsTests_Image_Editor_GD::test_flipfailure, which also fails ontrunkWithout the
make_image()change,test_save_to_stream_fails_when_write_failsandtest_save_to_stream_fails_when_flush_failsfail on PHP 8.6. PHPCS passes on the changed files.The
@since 7.2.0changelog entry may need adjusting, depending on the release this lands in.Trac ticket: Core-66486
Use of AI Tools
AI assistance: Yes
Tool(s): Claude Code
Model(s): Claude Opus 5.5
Used for: Investigating the issue, writing the change, tests, and PR description, and running the tests.
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.
🤖 Generated with Claude Code
https://claude.ai/code/session_011Y86gwJZGdkh78pggUy4zs