#61114 closed defect (bug) (fixed)
PHP 8+ Fatal Error in WP_Upgrader due to TypeError in array_keys() Call
Reported by: | verygoode | Owned by: | azaozz |
---|---|---|---|
Milestone: | 6.7 | Priority: | normal |
Severity: | critical | Version: | 2.8 |
Component: | Upgrade/Install | Keywords: | has-patch has-unit-tests needs-testing php80 |
Focuses: | php-compatibility | Cc: |
Description
Aa PHP 8+ Fatal error occurs in multiple instances within class-wp-upgrader.php
when array_keys()
is called with a boolean argument instead of an array. This error is triggered under conditions where $wp_filesystem->dirlist()
fails to read a directory and returns false.
This often leads to the plugins/theme being missing from the site.
It appears the error stems from the function $wp_filesystem->dirlist()
returning false
-- likely due to a previously reported and unfixed bug where the upgrader deletes files related to other in-progress upgrades as reported by @bpayton in https://core.trac.wordpress.org/ticket/53705
Errors Observed
As of 6.5.2, the TypeError occurs in the install_package(
) method at the following lines when attempting to handle plugin, theme, or language pack upgrades or installations:
- Line 556: During the initial directory listing as part of a plugin or theme installation. https://core.trac.wordpress.org/browser/trunk/src/wp-admin/includes/class-wp-upgrader.php?rev=56550#L556
- Line 603: When checking if the source location has changed and requires a refresh of the directory listing. https://core.trac.wordpress.org/browser/trunk/src/wp-admin/includes/class-wp-upgrader.php?rev=56550#L603
PHP Fatal error: Uncaught TypeError: array_keys(): Argument #1 ($array) must be of type array, bool given in /wordpress/core/6.5.2/wp-admin/includes/class-wp-upgrader.php:556 Stack trace: #0 /wordpress/core/6.5.2/wp-admin/includes/class-wp-upgrader.php(556): array_keys(false) #1 /wordpress/core/6.5.2/wp-admin/includes/class-wp-upgrader.php(883): WP_Upgrader->install_package(Array) #2 /wordpress/core/6.5.2/wp-admin/includes/class-plugin-upgrader.php(137): WP_Upgrader->run(Array) #3 /srv/htdocs/wp-content/themes/hello-shoppable/inc/getting-started/getting-started.php(193): Plugin_Upgrader->install('https://downloa...') #4 /wordpress/core/6.5.2/wp-includes/class-wp-hook.php(324): Hello_Shoppable_Notice_Handler->hello_shoppable_getting_started('') #5 /wordpress/core/6.5.2/wp-includes/class-wp-hook.php(348): WP_Hook->apply_filters('', Array) #6 /wordpress/core/6.5.2/wp-includes/plugin.php(517): WP_Hook->do_action(Array) #7 /wordpress/core/6.5.2/wp-admin/admin-ajax.php(192): do_action('wp_ajax_hello_s...') #8 {main} thrown in /wordpress/core/6.5.2/wp-admin/includes/class-wp-upgrader.php on line 556
PHP Fatal error: Uncaught TypeError: array_keys(): Argument #1 ($array) must be of type array, bool given in /wordpress/core/6.5.2/wp-admin/includes/class-wp-upgrader.php:556 Stack trace: #0 /wordpress/core/6.5.2/wp-admin/includes/class-wp-upgrader.php(556): array_keys(false) #1 /wordpress/core/6.5.2/wp-admin/includes/class-wp-upgrader.php(883): WP_Upgrader->install_package(Array) #2 /wordpress/core/6.5.2/wp-admin/includes/class-plugin-upgrader.php(237): WP_Upgrader->run(Array) #3 /srv/htdocs/wp-content/plugins/elementor/includes/rollback.php(171): Plugin_Upgrader->upgrade('elementor/eleme...') #4 /srv/htdocs/wp-content/plugins/elementor/includes/rollback.php(184): Elementor\Rollback->upgrade() #5 /srv/htdocs/wp-content/plugins/elementor/includes/settings/tools.php(172): Elementor\Rollback->run() #6 /wordpress/core/6.5.2/wp-includes/class-wp-hook.php(324): Elementor\Tools->post_elementor_rollback('') #7 /wordpress/core/6.5.2/wp-includes/class-wp-hook.php(348): WP_Hook->apply_filters('', Array) #8 /wordpress/core/6.5.2/wp-includes/plugin.php(517): WP_Hook->do_action(Array) #9 /wordpress/core/6.5.2/wp-admin/admin-post.php(85): do_action('admin_post_elem...') #10 {main} thrown in /wordpress/core/6.5.2/wp-admin/includes/class-wp-upgrader.php on line 556
Steps to Reproduce
- Attempt to install or upgrade a plugin, theme, or language pack on a WordPress installation running under PHP 8.0 or later.
- Ensure that the
$wp_filesystem->dirlist()
function fails to return a directory list (can be simulated by providing a non-existent source directory via deletion or other methods). - Observe the fatal error logs as the install_package() method fails due to array_keys() receiving false.
Expected Behavior
While this could likely be alleviated by resolving https://core.trac.wordpress.org/ticket/53705 , the install_package()
method should handle cases where the directory listing is unavailable (i.e., when $wp_filesystem->dirlist()
returns false
) without resulting in a fatal error. Appropriate error handling should be in place to manage such failures gracefully.
Actual Behavior
Actual Behavior:
When $wp_filesystem->dirlist()
returns false, the absence of a check before the array_keys() function call leads to a fatal error due to a type mismatch (boolean given instead of array).
Change History (31)
#2
@
7 months ago
- Keywords needs-patch added
- Severity changed from major to normal
- Version changed from 6.5 to 2.8
This ticket was mentioned in Slack in #core-upgrade-install by afragen. View the logs.
7 months ago
#5
@
7 months ago
This is the line.
So perhaps casting to an array?
$source_files = array_keys( (array) $wp_filesystem->dirlist( $source ) );
#6
@
7 months ago
@afragen PHP 8.1 was the primary version I've seen but this can occur on any current PHP version ≥8
#7
@
7 months ago
Thanks @verygoode that confirms my suspicions. The above casting should fix the issue, can you test and report back?
#8
follow-up:
↓ 12
@
7 months ago
A quick way to reproduce is to spin up a site, set one or more plugins to a previous version like wp plugin install hello-dolly --version=1.6 --force
Then, run multiple requests at the same time to wp plugin update hello-dolly
and/or other plugin updates -- simulating a situation where multiple events are firing off to update something and leading to failures like reported on #53705
Without the casting, we can observe the fatal.
wp plugin update hello-dolly Enabling Maintenance mode... Downloading update from https://downloads.wordpress.org/plugin/hello-dolly.1.7.3.zip... Unpacking the update... Installing the latest version... Error: There has been a critical error on this website.Learn more about troubleshooting WordPress. There has been a critical error on this website.
With your casting recommendation, this fails without a fatal @afragen
wp plugin update hello-dolly Enabling Maintenance mode... Downloading update from https://downloads.wordpress.org/plugin/hello-dolly.1.7.3.zip Unpacking the update... Installing the latest version... Warning: Could not move the old version to the upgrade-temp-backup directory. Plugin update failed. Disabling Maintenance mode... +-------------+-------------+-------------+--------+ | name | old_version | new_version | status | +-------------+-------------+-------------+--------+ | hello-dolly | 1.6 | 1.7.2 | Error | +-------------+-------------+-------------+--------+ Error: No plugins updated (1 failed).
#9
@
7 months ago
With the casting, without a fatal, a warning may also be observed in WP-CLI
Warning: Directory listing failed. "hello-dolly"
The following warning is also logged in some cases, given that other events are clearing out the upgrade folder.
[02-May-2024 19:28:03 UTC] PHP Warning: dir(/Users/test/Local Sites/testing-upgrader/app/public/wp-content/upgrade/woocommerce.8.8.3-6ZaMui/woocommerce/src/Internal/Admin/Onboarding/): Failed to open directory: No such file or directory in /Users/test/Local Sites/testing-upgrader/app/public/wp-admin/includes/class-wp-filesystem-direct.php on line 636
#10
follow-up:
↓ 13
@
2 months ago
I believe all of my servers have started having issues that are related to this bug report. My servers are all on PHP8.1 and WordPress 6.6.2. When there is more than 1 plugin that needs an update, the problems arise.
The log file shows this:
[10-Oct-2024 17:36:58 UTC] PHP Warning: file_get_contents(/.../public_html/wp-content/plugins/wps-hide-login/wps-hide-login.php): Failed to open stream: No such file or directory in /.../public_html/wp-includes/functions.php on line 6864
[10-Oct-2024 17:36:58 UTC] PHP Fatal error: Uncaught TypeError: array_keys(): Argument #1 ($array) must be of type array, bool given in /.../public_html/wp-admin/includes/class-wp-upgrader.php:607
Stack trace:
#0 /.../public_html/wp-admin/includes/class-wp-upgrader.php(607): array_keys(false)
#1 /.../public_html/wp-admin/includes/class-wp-upgrader.php(887): WP_Upgrader->install_package(Array)
#2 /.../public_html/wp-admin/includes/class-plugin-upgrader.php(237): WP_Upgrader->run(Array)
#3 /.../public_html/wp-admin/update.php(74): Plugin_Upgrader->upgrade('wps-hide-login/...')
#4 {main}
thrown in /.../public_html/wp-admin/includes/class-wp-upgrader.php on line 607
[10-Oct-2024 17:36:59 UTC] PHP Warning: Trying to access array offset on value of type bool in /.../public_html/wp-admin/includes/ajax-actions.php on line 4681
Is there a work-around I can employ to get past this?
#11
@
2 months ago
I've started receiving this error on multiple websites as well today. WordPress 6.6.2, PHP 8.0
#12
in reply to:
↑ 8
;
follow-up:
↓ 14
@
2 months ago
- Milestone changed from Awaiting Review to 6.7
Replying to verygoode:
Warning: Could not move the old version to the upgrade-temp-backup directory.
Yep, I believe this is the right output when the plugin directory doesn't exist. Just wondering if it may be better to add a more specific error message there? Maybe the patch should be something like:
// Has the source location changed? If so, we need a new source_files list. if ( $source !== $remote_source ) { $dir_list = $wp_filesystem->dirlist( $source ); if ( empty( $dir_list ) ) { // Return new WP_ERROR } $source_files = array_keys( $dir_list ); }
This matches some of the existing code, see: https://core.trac.wordpress.org/browser/trunk/src/wp-admin/includes/class-wp-upgrader.php#L366
Moving to the 6.7 milestone for consideration.
#13
in reply to:
↑ 10
@
2 months ago
Replying to lifelightweb:
Is there a work-around I can employ to get past this?
I have the same configuration as you, PHP 8.1, latest WordPress. Running on Amazon Linux 2 and auto updates disabled.
This seems to happen to me:
- Plugin updates fine, no issues. This seems to happen with plugins with small numbers of files.
- Or, plugin updates correctly (success message), but refreshing the plugin page shows the plugin completely gone or deactivated
- Or, when upgrading the plugin I will get a variety of error messages: Destination folder already exists, Failed to copy file
My solution so far is:
- Before upgrading make sure your screenshot your plugins list, some will literally disappear
- Try upgrade
- If fail, try “add new plugin” screen from Plugins page
- If fail, try a few times to do this. Sometimes it seems to work on the second or third
- If that’s not working, rename folder in plugin directory using file manager or FTP or SSH. Try adding plugin again
I have been able to update all my plugins with these steps, but it’s been a challenge. Specifically big plugins (many files) like Yoast or W3 Cache have caused more issues.
I thought it was the Jetpack Protect plugin that caused this issue, but I think it’s more likely this core WordPress issue above.
It seems to happen sometimes - not always. For me, this has only seemed to affect an old old site I manage (but fully updated and on PHP 8.1)
I have run Wordfence premium scans and no issues. Site health is great.
#14
in reply to:
↑ 12
@
2 months ago
Forgive me if my request is out of line, but all my sites are pretty much dead in the water insofar as updates. I'm seeing this problem across all my servers, across multiple accounts all PHP8.1 and the latest WP version. I cannot install a fresh plugin, I cannot update an existing one. Doesn't matter how much disk space I have or how busy the server is. For sure I'm not hitting memory errors on some of these servers as they were just recently upgraded. This started literally this last week. I've never had this issue like this before. It would seem to me that something has changed and broken WordPress for people like me.
How does this get escalated to get some attention--this is getting very serious for me.
Replying to azaozz:
Replying to verygoode:
Warning: Could not move the old version to the upgrade-temp-backup directory.
Yep, I believe this is the right output when the plugin directory doesn't exist. Just wondering if it may be better to add a more specific error message there? Maybe the patch should be something like:
// Has the source location changed? If so, we need a new source_files list. if ( $source !== $remote_source ) { $dir_list = $wp_filesystem->dirlist( $source ); if ( empty( $dir_list ) ) { // Return new WP_ERROR } $source_files = array_keys( $dir_list ); }This matches some of the existing code, see: https://core.trac.wordpress.org/browser/trunk/src/wp-admin/includes/class-wp-upgrader.php#L366
Moving to the 6.7 milestone for consideration.
#15
@
2 months ago
- Severity changed from normal to critical
I have the exact same problem with 5 different sites, all of them under the same server. Also in some failed updates, the sites get blocked and goes to maintenance mode.
This ticket was mentioned in PR #7558 on WordPress/wordpress-develop by @costdev.
2 months ago
#16
- Keywords has-patch has-unit-tests added; needs-patch removed
Previously, WP_Upgrader::install_package()
attempted to get keys from a WP_Filesystem_*::dirlist()
result, expecting it to be an array. However, WP_Filesystem_*::dirlist()
may also return false
. The subsequent array_keys()
function call produces a Warning (PHP < 8.0) and Fatal Error (PHP 8.0+) because the first argument must be of array
type.
This change checks for a false
return value from WP_Filesystem_*::dirlist()
and returns a WP_Error
from WP_Upgrader::install_package()
.
2 months ago
#17
@azaozz @afragen This does a specific check for false
to ensure a targeted error message is displayed. By returning early, we also save on processing time if the upgrade can't continue anyway.
If/when you have bandwidth, let me know your thoughts here on the PR 🙂
2 months ago
#18
I see that this is currently milestoned for WordPress 6.7. While I totally agree that this can have a serious impact, this doesn't leave a lot of time for testing. The added unit tests certainly help, just be mindful of our timeframe here and the need to be confident that the error handling is safe through testing.
If this PR has consensus as the way we're going forward here, then we'll need to get the Test Team on this as soon as possible. Until/unless this PR has that consensus, let's not add to their already heavy workload. 🙂
2 months ago
#19
@costdev I think this looks good.
It seems to be a deprecation due to a PHP version and not so much from WP 6.7. What I don't understand is I don't see this issue on my sites, running 6.6.2 or beta/RC and PHP 8.3.x.
2 months ago
#20
Not a deprecation, but rather that it went from a warning to a Fatal Error in PHP 8.0. Ref
Autovivification of false
was deprecated, but not until PHP 8.1.
#21
follow-up:
↓ 24
@
2 months ago
I believe that this issue is exacerbated by Cloudflare's new "Speed Brain" feature which is enabled by default on all free plans since September 25.
This user documents how Speed Brain caused errors with deleting posts and media:
https://community.cloudflare.com/t/cloudflare-speed-brain-feedback-wordpress-delete-trash-errors/722992/2
"Today we are very excited to share the latest leap forward in speed: Speed Brain. It relies on the Speculation Rules API to prefetch the content of the user's likely next navigations." https://blog.cloudflare.com/introducing-speed-brain/
This could indicate that these specific issues are more likely to occur when using Chrome/Edge and Cloudflare's free plan. Auto updated plugins (probably?) wouldn't be affected as the update request isn't driven by the user's browser.
I tested this on a live and staging site, same server (WordPress 6.6.2, PHP 8.2.18.) I received errors when updating plugins on the staging site. Turned off Speed Brain and was able to update the live site with no errors.
The docs do state that the prefetch will not reach origin servers:
"Prefetch requests will never reach origin servers. Prefetch requests only serve content that is stored in Cloudflare’s Cache. If the content is not in Cache, the prefetch request will not continue to origin servers. Without this safeguard, origin server state could be modified despite the prefetch response not being rendered in the browser. An example of this could be a prefetch GET request to a sign-out URL inadvertently triggering a sign-out action on the server." https://developers.cloudflare.com/speed/optimization/content/speed-brain/
Perhaps it's not working correctly...
#22
@
2 months ago
I appreciate the investigation and reports back on this. I concur that speedbrain in CF is exacerbating the problem tremendously. And it is on by default. I have a logged a ticket with them, but they are so slow to respond that I doubt it will ever get addressed. I have gone through all my sites and turned speedbrain off in CF. This is helping for sure. As it pertains to this ticket, I do wish WP failed a little nicer. I still had a few that failed and the plugin folder either got corrupted and I could not re-install through the admin dashboard (had to go retrieve zip file and manually publish to server) or the plugin was automatically deactivated. My wish as an administrator of many WP sites is that the failure behavior of would be to put things back as they were, i.e., don't deactivate, don't delete files. Leave at the old version.
2 months ago
#23
Imho this looks good.
it went from a Warning to a Fatal Error in PHP 8.0. Ref
...doesn't leave a lot of time for testing
WP 6.7 is scheduled for release on November 12, 2024. Thinking four weeks is ample time to test this. Also this fix doesn't change the way the code works. Passing false
to array_keys()
would return an empty array in earlier PHP versions, right? There are still no files, the operation fails, and an error is eventually shown.
#24
in reply to:
↑ 21
;
follow-up:
↓ 27
@
2 months ago
Replying to da5f656f:
I believe that this issue is exacerbated by Cloudflare's new "Speed Brain" feature which is enabled by default on all free plans since September 25.
This user documents how Speed Brain caused errors with deleting posts and media:
https://community.cloudflare.com/t/cloudflare-speed-brain-feedback-wordpress-delete-trash-errors/722992/2
@da5f656f Could you open a new ticket with this? This ticket will (most likely) be closed soon with the fix for the PHP fatal error, but that doesn't fix the possible problems of "missing/unreachable directories" that may be caused by this Cloudflare feature.
#26
follow-up:
↓ 28
@
2 months ago
- Owner set to azaozz
- Status changed from new to assigned
@azaozz I've assigned this to you for shepherding in to 6.7. The first RC is a week from today so the commit will need to take place prior to that.
#27
in reply to:
↑ 24
;
follow-up:
↓ 29
@
2 months ago
Replying to azaozz:
@da5f656f Could you open a new ticket with this? This ticket will (most likely) be closed soon with the fix for the PHP fatal error, but that doesn't fix the possible problems of "missing/unreachable directories" that may be caused by this Cloudflare feature.
Sure, I created ticket:62223.
#28
in reply to:
↑ 26
@
8 weeks ago
Replying to peterwilsoncc:
Yep, sure, that was my intention.
Error for line 603.