From 3665a3fe1a3e0c0fdd11bef37c97d2ded290c6d1 Mon Sep 17 00:00:00 2001 From: Paul Bearne Date: Tue, 10 Feb 2026 09:07:15 -0500 Subject: [PATCH 1/4] Add a file existence check in `class-wp-theme-json-resolver` to avoid potential errors. --- src/wp-includes/class-wp-theme-json-resolver.php | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/wp-includes/class-wp-theme-json-resolver.php b/src/wp-includes/class-wp-theme-json-resolver.php index 4d5bf3dce9ee3..20d4a6be5277d 100644 --- a/src/wp-includes/class-wp-theme-json-resolver.php +++ b/src/wp-includes/class-wp-theme-json-resolver.php @@ -106,11 +106,12 @@ protected static function read_json_file( $file_path ) { if ( array_key_exists( $file_path, static::$theme_json_file_cache ) ) { return static::$theme_json_file_cache[ $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 ]; + if( file_exists( $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 ]; + } } } From 838b1dd5bfd4477d62e3937b233f3ce28ed05c5e Mon Sep 17 00:00:00 2001 From: Paul Bearne Date: Tue, 10 Feb 2026 14:57:29 -0500 Subject: [PATCH 2/4] Update src/wp-includes/class-wp-theme-json-resolver.php Co-authored-by: Weston Ruter --- src/wp-includes/class-wp-theme-json-resolver.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wp-includes/class-wp-theme-json-resolver.php b/src/wp-includes/class-wp-theme-json-resolver.php index 20d4a6be5277d..a9ae0fefa7064 100644 --- a/src/wp-includes/class-wp-theme-json-resolver.php +++ b/src/wp-includes/class-wp-theme-json-resolver.php @@ -106,7 +106,7 @@ protected static function read_json_file( $file_path ) { if ( array_key_exists( $file_path, static::$theme_json_file_cache ) ) { return static::$theme_json_file_cache[ $file_path ]; } - if( file_exists( $file_path ) ) { + if ( file_exists( $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; From bbd76130534752c438a2df81a07e5698d4e1b017 Mon Sep 17 00:00:00 2001 From: Paul Bearne Date: Wed, 18 Feb 2026 13:22:43 -0500 Subject: [PATCH 3/4] Change file existence check to readability check used is_readable not file_exists --- src/wp-includes/class-wp-theme-json-resolver.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wp-includes/class-wp-theme-json-resolver.php b/src/wp-includes/class-wp-theme-json-resolver.php index a9ae0fefa7064..5e9e2334e4e2a 100644 --- a/src/wp-includes/class-wp-theme-json-resolver.php +++ b/src/wp-includes/class-wp-theme-json-resolver.php @@ -106,7 +106,7 @@ protected static function read_json_file( $file_path ) { if ( array_key_exists( $file_path, static::$theme_json_file_cache ) ) { return static::$theme_json_file_cache[ $file_path ]; } - if ( file_exists( $file_path ) ) { + 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; From 89e39fec690f7e9c49012b93144b5db416b1f7d5 Mon Sep 17 00:00:00 2001 From: Paul Bearne Date: Wed, 22 Jul 2026 12:08:30 -0400 Subject: [PATCH 4/4] Add unit tests for `WP_Theme_JSON_Resolver::read_json_file()` --- .../wpThemeJsonResolver/readJsonFile.php | 103 ++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 tests/phpunit/tests/theme/wpThemeJsonResolver/readJsonFile.php diff --git a/tests/phpunit/tests/theme/wpThemeJsonResolver/readJsonFile.php b/tests/phpunit/tests/theme/wpThemeJsonResolver/readJsonFile.php new file mode 100644 index 0000000000000..1fb4a5efa4038 --- /dev/null +++ b/tests/phpunit/tests/theme/wpThemeJsonResolver/readJsonFile.php @@ -0,0 +1,103 @@ +setAccessible( true ); + } + static::$property_theme_json_file_cache_orig_value = static::$property_theme_json_file_cache->getValue(); + } + + public static function tear_down_after_class() { + static::$property_theme_json_file_cache->setValue( null, static::$property_theme_json_file_cache_orig_value ); + parent::tear_down_after_class(); + } + + public function tear_down() { + // Reset data between tests. + static::$property_theme_json_file_cache->setValue( null, array() ); + parent::tear_down(); + } + + /** + * @ticket 64620 + */ + public function test_read_json_file() { + $read_json_file = new ReflectionMethod( WP_Theme_JSON_Resolver::class, 'read_json_file' ); + if ( PHP_VERSION_ID < 80100 ) { + $read_json_file->setAccessible( true ); + } + + // Test reading a valid JSON file. + $valid_file = DIR_TESTDATA . '/themedir1/block-theme/theme.json'; + $result = $read_json_file->invoke( null, $valid_file ); + $this->assertIsArray( $result ); + $this->assertArrayHasKey( 'version', $result ); + $this->assertSame( 3, $result['version'] ); + + // Test that the result is cached. + $cache = static::$property_theme_json_file_cache->getValue(); + $this->assertArrayHasKey( $valid_file, $cache ); + $this->assertSame( $result, $cache[ $valid_file ] ); + + // Test cache hit: modify cache and verify read_json_file returns cached value. + $cache[ $valid_file ] = array( + 'version' => 3, + 'cached' => true, + ); + static::$property_theme_json_file_cache->setValue( null, $cache ); + $result = $read_json_file->invoke( null, $valid_file ); + $this->assertSame( 3, $result['version'] ); + $this->assertTrue( $result['cached'] ); + + // Test non-existent file. + $non_existent_file = DIR_TESTDATA . '/non-existent.json'; + $result = $read_json_file->invoke( null, $non_existent_file ); + $this->assertSame( array(), $result ); + + // 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 ); + } + + // 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 ); + $this->assertSame( array(), $result ); + unlink( $invalid_json_file ); + } +}