Repository navigation
Conversation
… potential errors.
|
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 Unlinked AccountsThe following contributors have not linked their GitHub and WordPress.org accounts: @pbearne@git.wordpress.org. Contributors, please read how to link your accounts to ensure your work is properly credited in WordPress releases. 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. |
Co-authored-by: Weston Ruter <westonruter@gmail.com>
used is_readable not file_exists
westonruter
left a comment
There was a problem hiding this comment.
@pbearne It's still not clear how to reproduce this issue. I wonder if wrapping the code in an is_readable() will just make it harder to debug. Should there be an else statement to say the expected file is missing? Again, having a way to reproduce this would be helpful, and thus could be added as a test.
|
@westonruter what would you like in the else |
There was a problem hiding this comment.
Pull request overview
This PR adjusts WP_Theme_JSON_Resolver::read_json_file() to avoid calling wp_json_file_decode() when the passed path is not readable, preventing avoidable errors/warnings when theme.json (or similar JSON files) are missing or inaccessible.
Changes:
- Add a readability check before decoding JSON files in
WP_Theme_JSON_Resolver::read_json_file(). - Preserve existing caching behavior for successfully decoded theme.json-shaped arrays.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if ( is_array( $decoded_file ) ) { | ||
| static::$theme_json_file_cache[ $file_path ] = $decoded_file; | ||
| return static::$theme_json_file_cache[ $file_path ]; | ||
| if ( is_readable( $file_path ) ) { |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (1)
src/wp-includes/class-wp-theme-json-resolver.php:114
is_readable()is true for directories, which would still allowwp_json_file_decode()to run. That function callsjson_decode( file_get_contents( $filename ) ...); iffile_get_contents()returnsfalse(e.g., path is a directory), PHP 8+ can throw aTypeErrorbecausejson_decode()requires a string. Consider requiring a regular file as well as readability before decoding.
if ( is_readable( $file_path ) ) {
$decoded_file = wp_json_file_decode( $file_path, array( 'associative' => true ) );
if ( is_array( $decoded_file ) ) {
static::$theme_json_file_cache[ $file_path ] = $decoded_file;
return static::$theme_json_file_cache[ $file_path ];
}
| // Test unreadable file. | ||
| if ( function_exists( 'posix_getpwuid' ) && 'root' !== posix_getpwuid( posix_geteuid() )['name'] ) { | ||
| $unreadable_file = DIR_TESTDATA . '/unreadable.json'; | ||
| touch( $unreadable_file ); | ||
| chmod( $unreadable_file, 0000 ); | ||
| $result = @$read_json_file->invoke( null, $unreadable_file ); | ||
| $this->assertSame( array(), $result ); | ||
| unlink( $unreadable_file ); | ||
| } |
|
@pbearne Can you address the feedback from Copilot? Both seem like reasonable points to me. |
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
… by replacing redundant checks with `is_file`.
|
@t-hamano fixed |
There was a problem hiding this comment.
Thanks for working on this. The guard stops the PHP warning, but I have a concern about where the check lives and what it hides.
How I reproduced the original warning on trunk
get_style_variations() is the only caller of read_json_file() that does not check is_readable() first. The other callers check it at lines 256, 310 and 725. So the trigger is a file in a theme's styles/ folder that exists but cannot be read:
cd wp-content/themes/twentytwentyfive/styles
echo '{"version":3,"title":"Broken"}' > broken.json
chmod 000 broken.json
# or: mkdir fake.jsonWith WP_DEBUG on, open Appearance > Editor > Styles. Trunk logs file_get_contents(...): Failed to open stream: Permission denied and then a JSON decode notice. A directory named fake.json gives errno=21 Is a directory. With this PR, nothing is logged and the variation disappears from the list.
Smaller points
- The new check repeats the
is_readable()check that three of the four callers already do. This adds extra stat calls on those paths. - Failed reads are not cached. A broken file is checked and decoded again on every call to
get_style_variations()in the same request.
Note: Claude fine this.
| if ( is_array( $decoded_file ) ) { | ||
| static::$theme_json_file_cache[ $file_path ] = $decoded_file; | ||
| return static::$theme_json_file_cache[ $file_path ]; | ||
| if ( is_file( $file_path ) && is_readable( $file_path ) ) { |
There was a problem hiding this comment.
This check returns array() without any message. Before this change, wp_json_file_decode() reported the problem through wp_trigger_error(). Now a theme author with a broken or unreadable variation file gets no hint on a WP_DEBUG site.
I think the fix belongs inside wp_json_file_decode() instead. That function has two related bugs that other callers also hit, such as block.json registration:
- It overwrites
$filenamewithwp_normalize_path( realpath( $filename ) ).realpath()returnsfalsefor a missing file, so the notice prints an empty path:File doesn't exist!. You can see this withwp_json_file_decode( '/does/not/exist.json' );. - It calls
file_get_contents()without checkingis_file()andis_readable(). An unreadable file or a directory raises a PHP warning.
A check there, with a wp_trigger_error() call that keeps the original path, would fix every caller and keep the diagnostic.
| /** | ||
| * @ticket 64620 | ||
| */ | ||
| public function test_read_json_file() { |
There was a problem hiding this comment.
This one method covers six scenarios. The first failing assertion hides the rest. Could you split it into separate test methods or use a data provider?
The unreadable-file case also skips itself without telling PHPUnit. On Windows, without the posix extension, or when tests run as root, it reports a pass for code it never ran. $this->markTestSkipped() in its own method would show this.
| if ( function_exists( 'posix_getpwuid' ) && function_exists( 'posix_geteuid' ) ) { | ||
| $pwuid = posix_getpwuid( posix_geteuid() ); | ||
| if ( is_array( $pwuid ) && isset( $pwuid['name'] ) && 'root' !== $pwuid['name'] ) { | ||
| $unreadable_file = tempnam( sys_get_temp_dir(), 'unreadable-json' ); |
There was a problem hiding this comment.
If the assertion below fails, chmod() and unlink() never run. A file with mode 0000 then stays in the system temp folder. The invalid-JSON temp file has the same problem. Moving cleanup into tear_down() or a try/finally block would fix this.
| // Test invalid JSON. | ||
| $invalid_json_file = tempnam( sys_get_temp_dir(), 'invalid-json' ); | ||
| file_put_contents( $invalid_json_file, '{ invalid json }' ); | ||
| $result = @$read_json_file->invoke( null, $invalid_json_file ); |
There was a problem hiding this comment.
The @ hides the decode notice instead of checking that it fires. If the error reporting broke later, this test would still pass. Could you assert the notice with expectNotice() or the wp_trigger_error_run action? A matching assertion for the missing and unreadable cases would also cover the behavior change in the resolver.
… potential errors.
Trac ticket: https://core.trac.wordpress.org/ticket/64620