Make WordPress Core

Opened 2 months ago

Last modified 7 weeks ago

#60491 new defect (bug)

Fix handling of plugins with unmet requirements

Reported by: azaozz's profile azaozz Owned by:
Milestone: Future Release Priority: normal
Severity: normal Version: 5.1
Component: Plugins Keywords: needs-patch
Focuses: Cc:

Description

There are several bugs/inconsistencies when WordPress handles plugins with unmet requirements.

  • A plugin with unmet requirements cannot be installed or updated when using the UI, but this is possible to do/there are no checks when a plugin is installed or updated directly (with FTP, SSH, or git/svn).
  • A plugin with unmet requirements cannot be activated if already installed (happens only when a plugins is installed/updated from FTP, SSH, or git/svn). But if the plugin is already activated, it is not deactivated and continues to load.
  • The UX can be better. The warnings in wp-admin when a plugin has unmet requirements are somewhat inconsistent, and there are no email notifications if any of these errors occur automatically (for example a script is running on the server that uses svn or git to update plugins).

Attachments (3)

cannot-activate.png (63.3 KB) - added by azaozz 2 months ago.
Plugin with unmet requirements cannot be activated.
error-but-still-activated.png (60.3 KB) - added by azaozz 2 months ago.
An activated plugin with unmet requirements is still active/loaded. Also the background color under the notice needs fixing.
top-warning-inconsistent.png (75.1 KB) - added by azaozz 2 months ago.
The error message at the top of the screen is missing initially and appears after the second reload.

Download all attachments as: .zip

Change History (17)

@azaozz
2 months ago

Plugin with unmet requirements cannot be activated.

@azaozz
2 months ago

An activated plugin with unmet requirements is still active/loaded. Also the background color under the notice needs fixing.

@azaozz
2 months ago

The error message at the top of the screen is missing initially and appears after the second reload.

#1 @azaozz
2 months ago

  • Keywords needs-patch added

Most of these inconsistencies are (more or less) edge cases and happen only when a plugin is installed or updated directly, either using FTP or version control. A plugin with unmet requirements cannot be installed or updated through the WP UI as the API of the plugins repository on wp.org simply skips incompatible plugins.

Nevertheless thinking that the plugins API in WP should be fixed to behave consistently in these edge cases. The same behaviour should probably be implemented for plugin dependencies and in all other similar cases.

#2 @costdev
2 months ago

The error message at the top of the screen is missing initially and appears after the second reload.

Just a note on this one:

The error message missing initially is because the plugin header was modified rather than the server's PHP version. On the initial load, the plugin_data option storing the RequiresPHP header will not be "fresh" (to avoid loading from the filesystem on every page load).

If the server's PHP version were to change, and the server restarted if necessary to load the different PHP version, then the notice would appear on the first load.

#3 follow-up: @costdev
2 months ago

The main difference between "not loaded" and "deactivated" in the context of #60457 is that deactivating performs a DB write to active_plugins, whereas not loading just skips the require_once.

We will have to remove the automatic deactivation of dependents with unmet dependencies, at least from the frontend.

Changing so that automatic deactivation is only performed on plugins.php, for example, will involve relocating WP_Plugin_Dependencies::initialize() to below require ABSPATH . WPINC . '/vars.php'; in wp-settings.php. This means that $pagenow is defined and can therefore be used in a guard.

Last edited 2 months ago by costdev (previous) (diff)

#4 follow-up: @swissspidy
2 months ago

A plugin with unmet requirements cannot be updated when using the UI

That seems like an unnecessary restriction. What if the updated version doesn‘t have any dependencies anymore? Then you would have to uninstall and reinstall, which is weird.

#5 @costdev
2 months ago

The requirements on Plugins > Add New are taken from the WordPress.org Plugins API rather than from the install version's headers, so the removal of dependencies would be detected and the update should be possible in that case.

#6 @costdev
2 months ago

In 57592:

Upgrade/Install: Avoid update_option() calls during bootstrap.

[57545] introduced the Plugin Dependencies feature, which contains a new plugin_data option.

Previously, the plugin_data option was being updated during bootstrap and in get_plugins(), causing an error when using the install script as the options database table does not yet exist, and also risked an "out of sync" issue between the database and the cache on websites with heavy traffic.

This removes the calls to update_option() during Core's bootstrap, and guards the call in get_plugins() to ensure that it doesn't run when WordPress is installing.

Follow-up to [57545].

Props desrosj, swisspidy, huzaifaalmesbah, afragen, dd32, azaozz, costdev.
Fixes #60461. See #60457, #60491.

#7 in reply to: ↑ 4 @azaozz
2 months ago

Replying to swissspidy:

A plugin with unmet requirements cannot be updated when using the UI

That seems like an unnecessary restriction. What if the updated version doesn‘t have any dependencies anymore? Then you would have to uninstall and reinstall, which is weird.

This is handled by the wporg API. Think that if a plugin has had unmet requirements, but now all requirements are satisfied, it will be returned by the API as an available update (untested).

Last edited 2 months ago by azaozz (previous) (diff)

#8 in reply to: ↑ 3 @azaozz
2 months ago

Replying to costdev:

The main difference between "not loaded" and "deactivated" in the context of #60457 is that deactivating performs a DB write to active_plugins, whereas not loading just skips the require_once.

We will have to remove the automatic deactivation of dependents with unmet dependencies, at least from the frontend.

Right, makes sense.

Also perhaps that part of the plugins API can be a bit better/faster/simpler? For example what if WP was deciding when to initialize a plugin? So initialization of plugins would not happen automatically by just loading the plugin file? Then WP would be able to handle all the edge cases much better/faster, and also will be able to show more consistent warnings to the users? Worth exploring imho :)

This ticket was mentioned in Slack in #core-upgrade-install by costdev. View the logs.


2 months ago

#10 follow-up: @afragen
2 months ago

Should an active plugin with new unmet requirements remain active? It would seem that doing so might result in a fatal.

This ticket was mentioned in Slack in #cli by costdev. View the logs.


2 months ago

#12 in reply to: ↑ 10 @azaozz
8 weeks ago

Replying to afragen:

Should an active plugin with new unmet requirements remain active?

Seems not. It may throw a fatal error as you say, but imho a worse case is if the plugin continues to work to some extend but accumulates errors in the DB, or returns the wrong info on certain requests, or other hard to find/notice error.

The above screenshot shows that an activated plugin with unmet PHP requirement is still loaded which is a bug imho, and needs fixing.

The error message at the top of the screen is missing initially and appears after the second reload.

Last edited 8 weeks ago by azaozz (previous) (diff)

#13 @azaozz
8 weeks ago

what if WP was deciding when to initialize a plugin

Thinking this is (probably) the best way forward for supporting all plugin requirements, current and future.

To be able to do this WP will need access to the plugin's meta (the headers) before it can decide to load/initialize the plugin or not. Seems there are two more or less straightforward ways to accomplish this:

  1. Store all of the plugin's meta in the DB, and update it when installing or updating a plugin (and maybe when activating to catch cases where a plugin was installed from FTP).

Advantages: Getting the plugins meta from the DB (or from persistent cache) will be super fast compared to "reading" the plugin's headers every time. Also this method will not need any changes to how plugin meta is currently stored in the plugin's main file (plugins headers).

Disadvantages: Edge case when updating an activated plugin outside of WP, through FTP or git/svn. That will bypass updating of the stored plugin meta and possibly result in loading a plugin with unmet requirements (like the case described above). Seems it's possible to solve this by storing the "last modified" time for the plugin's main file together with the plugin's meta, and check it every time to detect updates. As far as I see doing filemtime() for about 30-40 files will not introduce a noticeable delay. On my test site these are super fast, take few nanoseconds per file.

  1. Introduce a new way to store the plugin's meta in the main plugin file. Seems an associative array would work well. This can be in addition to the current headers or instead of them. Something like:
$plugin_meta = array(
    plugin_name       => 'My plugin',
    requires_at_least => '6.3',
    requires_PHP      => '8.0',
    version           => '1.2.345',
    init_callback     => 'my_plugin_init',
);

(Note the new init_callback meta key.)

Then when WP is loading the main plugin file, it can do something like:

$plugin_meta = null;

foreach ( $plugins as $plugin ) {
    if ( is_array( $plugin_meta ) ) {
        if (
            // Check PHP version requirement if set
            // Check WP version requirement if set
            // Check for plugin dependencies if set
            // Check any other (new) requirements if set
         ) {
            if ( is_callable( $plugin_meta['init_callback'] ) ) {
                call_user_func( plugin_meta['init_callback'] );
            } else {
                // Throw error depending on WP_Dev constants
            }
        } else {
            // Show a "plugin requirements not met" error/warning. 
            // The error should also explain that the plugin has been disabled
            // until the problem is resolved.
            // (Note that the plugin is still activated at this point
            // but WP is not loading/initializing it)
        }
    } else {
        // Load the plugin the old way (existing code)
    }

    $plugin_meta = null; // The meta array needs to be reset for the next plugin
}

Advantages: Fast and easy way to access the plugin's meta before initializing the plugin. This solves the edge case when an activated plugin is updated outside of WP, through FTP or git/svn.

Disadvantage: Requires the new $plugin_meta var to be defined in the main plugin file.


#14 @costdev
7 weeks ago

In 57658:

Plugin Dependencies: Remove auto-deactivation and bootstrapping logic.

Automatic deactivation of dependents with unmet dependencies requires a write operation to the database. This was performed during Core's bootstrap, which risked the database and cache becoming out-of-sync on sites with heavy traffic.

No longer loading plugins that have unmet requirements has not had a final approach decided core-wide, and is still in discussion in #60491 to be handled in a future release.

The plugin_data option, used to persistently store plugin data for detecting unmet dependencies during Core's bootstrap, is no longer needed.

Follow-up to [57545], [57592], [57606], [57617].

Props dd32, azaozz, swissspidy, desrosj, afragen, pbiron, zunaid321, costdev.
Fixes #60457. See #60491, #60510, #60518.

Note: See TracTickets for help on using tickets.