#39766 closed defect (bug) (fixed)
Plugin does not gracefully fail when editing active plugin causes fatal error
Reported by: | ovann86 | Owned by: | westonruter |
---|---|---|---|
Milestone: | 4.9 | Priority: | high |
Severity: | normal | Version: | 4.7.2 |
Component: | Plugins | Keywords: | has-patch needs-testing |
Focuses: | administration | Cc: |
Description
When editing an active plugin in the wp-admin you see the following message above the 'Update file' button.
Warning: Making changes to active plugins is not recommended. If your changes cause a fatal error, the plugin will be automatically deactivated.
However, if an edit causes a fatal error the plugin is not automatically deactivated - resulting in the "white screen of death".
Similarly, attempting to activate a plugin that contains a fatal error will automatically deactivate the plugin.
Steps to reproduce
- In the WordPress Administration, go to Plugins-> Installed Plugins
- For an active plugin, click on the 'Edit' link
- Add erroneous code to plugin, for example
// DO NOT ADD THIS TO YOUR PLUGIN - IT WILL CAUSE A FATAL ERROR -- MISSING CLOSING endif; if ( ! function_exists( 'wp_password_change_notification' ) ) : function wp_password_change_notification( $user ) { return; }
- Click 'Update File'
- Changes are saved to the plugin, page loads and confirmation "File edited successfully." is displayed
- Attempt to load another page (for example, the same page or the home page) and the "white screen of death" occurs.
Attachments (6)
Change History (36)
#2
@
8 years ago
@MattyRob
Thanks for taking the time to run the test.
After some more testing it looks like this issue only occurs under PHP version 7.0 (I tested 5.3, 5.4, 5.5, 5.6, 7.1 and the plugin deactivated automatically).
If you can, could you try running the same test under PHP 7.0?
I'll try to do a screen capture of what I see shortly.
@
8 years ago
Adding fatal php to active plugin under PHP 7.0 - plugin DOES NOT deactivate - subsequent white screen of death on reload
#3
@
8 years ago
Weird, I've just installed PHP 7.0.15 and got it working on my localhost testing site - it still worked as expected?!?
What subversion of PHP 7 are you using? Could it be a PHP glitch that's been patched?
#4
@
8 years ago
I'm seeing the issue until 7.0.15 and 7.0.13, both Apache but different hosts (the later being a clean VPN install).
I did a little research and saw that PHP 7 introduced different error handling for included files with parse errors, but that wouldn't explain why it works for you with 7.0.15.
Under my test environment I was able to see that the activate_plugin function continued to run after the include_once() (with parse error) rather than stop and redirect to the previously set header location.
#5
@
8 years ago
- Keywords dev-feedback 2nd-opinion added
How bizarre. Must be something to do with PHP or server configuration then I would have though. Perhaps one for a core dev opinion.
#6
@
7 years ago
I get this all the time on a variety of customers sites, regardless of the PHP version. Anyone else?
#7
@
7 years ago
- Keywords needs-patch added; dev-feedback 2nd-opinion removed
- Milestone changed from Awaiting Review to 4.9
- Priority changed from normal to high
I'm able to reproduce this issue. The error detection logic does not seem to be reliable. More often than not when I edit a plugin's PHP file with any fatal error (syntax, missing function, throwing explicit exception) then somehow it seems PHP is just ignoring it when activate_plugin()
does:
<?php include_once( WP_PLUGIN_DIR . '/' . $plugin );
I've tried switching it to an include
or a require
but there is no change. If I file_get_contents()
on it before including it, then I see the file has the error in it. But when PHP actually loads it, then it doesn't complain. It's like PHP has a cache of the file in memory. I'm using VVV for testing with PHP 7.0.17.
#8
@
7 years ago
- Keywords has-patch needs-testing added; needs-patch removed
- Owner set to westonruter
- Status changed from new to accepted
I believe I've found the problem and have a solution. It's PHP's opcache that is indeed causing the problem. The solution here seems to simply be to call opcache_invalidate()
on the file right after it is written.
I also noticed a two problems with catching fatal errors when editing a plugin's file other than the main one, in particular editing akismet/class.akismet-admin.php
instead of akismet/admin.php
:
- Since
akismet/class.akismet-admin.php
only gets included in the admin, the fatal error would not get raised when attempted sandbox activation. The solution here is simply to defineWP_ADMIN
and trigger theinit
andadmin_init
actions, as well as any other key actions that plugins would hook into during loading. - With 1 fixed, when you are taken back to the plugin editor after trying to save an error causing the plugin to get deactivated, only the
file
param is provided and the plugin editor assumed theplugin
was thefile
if theplugin
param was absent. So when you tried to re-save the file to re-activate the plugin, then WordPress would complain about the plugin not having a file editor, because for example it incorrectly identifiedakismet/class.akismet-admin.php
as the plugin file. The fix here is just to try to look up the plugin for a given file.
Lastly, in the case of trying to save with a file with errors in it, the resulting notice about the plugin being deactivated strangely had a green success updated
style as opposed to an error
style.
These issues are addressed in 39766.0.diff.
#9
@
7 years ago
And 39766.1.diff adds:
- Adding more actions to trigger after activating.
- Removing all existing actions that other plugins may have already added.
- Ensure fatal error printed to screen will appear when editing plugin file other than the main one.
@clorith Would you test please?
This ticket was mentioned in Slack in #core-customize by westonruter. View the logs.
7 years ago
#11
@
7 years ago
Fatal error is detected, and displayed within the iframe. I'm still not a huge fan of deactivating a previously active plugin, but it is in line with the expected current behavior of the plugins editor at least.
#14
@
7 years ago
- Resolution fixed deleted
- Status changed from closed to reopened
I started getting a huge number of failures in the unit tests for one of my plugins, and I was able to trace it back to [41560]. The use of plugin_sandbox_scrape()
in activate_plugin()
is the problem, in particular, the running of the 'plugins_loaded'
action. I am still investigating exactly what is going on there (it has to do with how the plugin is activated before the tests run), but after reviewing this change I think that in general it needs a lot more testing with various plugins. Running these actions immediately after activation is unexpected by plugins and may cause issues. Also, some of the functions hooked to these actions by plugins may expect other actions to run between them, and will have problems. While running these common actions seems like a reasonable idea, I think that it needs a lot more thought, and is probably not feasible. It would be better to trigger a remote request that would run all of the usual code, rather than just simulating a few actions. A request for the plugins screen would be ideal, since even if things break somewhere else, as long as the user can access the plugins screen they can still deactivate the plugin if needed. But regardless, I think that the part of the commit running those actions may need to be reverted for now, until a better solution is found.
#16
follow-up:
↓ 17
@
7 years ago
@jdgrimes Just removing the plugins_loaded
action fixes the issue for your own plugins?
#17
in reply to:
↑ 16
@
7 years ago
Replying to westonruter:
@jdgrimes Just removing the
plugins_loaded
action fixes the issue for your own plugins?
Yes.
The reason that it was causing errors is because my plugin has some code hooked to 'plugins_loaded'
that alters an option in the database. That option also affects the behavior of the plugin's activation function. Basically, when that option is already set, the activation function assumes that the plugin has already been installed, and so it doesn't run some parts of the install routine (parts that it only needs to run on initial install and not on reactivation). As a result, those parts of the plugin are no longer activated by default.
In short, the design of the activation function was based on the (up to now valid) assumption that no parts of the plugin code would be run prior to activation. Now you are running any code hooked to these actions prior to activation, and if the plugin has never been activated, prior to it running its install routine (assuming that it installs itself via the activation hook).
Perhaps if you could determine whether the plugin had already been activated before or not, it would be OK to do something like this in activate_plugin()
only in the case when the plugin was being reactivated. Otherwise, I think that this is just asking for trouble.
#19
@
7 years ago
I can confirm that [41671] fixes the problem with my plugin. However, I suspect that other plugins will have issues with the other actions being called. This should probably be tested with a variety of plugins to determine whether there is going to be many issues.
#20
@
7 years ago
I think the plugin_sandbox_scrape()
approach should be removed entirely in favor of #21622 which can then be used by themes and plugins alike. The problem is time.
#21
@
7 years ago
As I just commented on #21622:
Please test and review these changes: https://github.com/xwp/wordpress-develop/pull/272
- Refactor themes and plugin editors to submit file changes via Ajax instead of doing full page refreshes. Moves file modifications from theme-editor.php and plugin-editor.php to common Ajax handler.
- Do sandboxed updates of PHP files for both active themes and plugins, with rollbacks in the case of a loopback request results in a fatal error. This supersedes changes in #39766 (Plugin does not gracefully fail when editing active plugin causes fatal error).
The improvements to the user experience and the robustness of the file editors are huge.
#24
follow-up:
↓ 25
@
7 years ago
@jdgrimes it is superseded in that it is a better approach to sandboxed plugin editing. However, the improvements to plugin_sandbox_scrape()
would still make plugin activation more robust as it also uses this function. That is why I didn't revert.
#25
in reply to:
↑ 24
@
7 years ago
Replying to westonruter:
@jdgrimes it is superseded in that it is a better approach to sandboxed plugin editing. However, the improvements to
plugin_sandbox_scrape()
would still make plugin activation more robust as it also uses this function. That is why I didn't revert.
OK. :-)
I asked in part because I realized that there was another test failure in my plugin resulting from this change. This time a function hooked to init
. It isn't a major thing, but I still have my doubts about this entire approach.
I think that if activate_plugin()
wants to use this approach, it should at least run the actions after the plugin is installed. Meaning that it wouldn't be able to use plugin_sandbox_scrape()
.
But I'd actually rather see a clean revert of the change to activate_plugin()
completely. After all, increasing the robustness of plugin activation in general wasn't the point of this ticket. I think if that is needed for some reason, it should be pursued in a separate ticket. Ideally, an approach more like that taken in #21622 would end up being used. As it is, I think that the current approach is fragile enough to make plugin activation less robust instead of more. I may be wrong about that though, of course.
#27
follow-up:
↓ 28
@
7 years ago
- Resolution set to fixed
- Status changed from reopened to closed
In 41819:
#28
in reply to:
↑ 27
@
7 years ago
Replying to westonruter:
In 41819:
Thank you @westonruter! :-D I can confirm that that fixed the remaining issue that I was having.
#30
@
7 years ago
Please do create a new topic on our support forums if you are having issues, this is likely caused by a plugin or theme conflict though which we'll be able to help you troubleshoot there.
@ovann86
I've just used your code and editing the main plugin file. The plugin was deactivated on the edit with an error message (screen shot to follow).
This can still happen if you edit an included file on occasions (I've been bitten a few times) but the current steps are not a 100% way to reproduce this issue.