Opened 4 weeks ago
Last modified 5 days ago
#66184 new defect (bug)
After uploading a plugin that "requires" other plugins, the "Activate Plugin" button leads to a wp_die() WSOD
| Reported by: | BusinessBloomer | Owned by: | |
|---|---|---|---|
| Priority: | high | Milestone: | 7.2 |
| Component: | Plugins | Version: | 7.1.2 |
| Severity: | normal | Keywords: | has-patch has-unit-tests |
| Cc: | Focuses: | ui, administration |
Description
When a plugin is installed by uploading a zip, the result screen always shows an "Activate Plugin" button, even when the plugin's Requires Plugins header lists plugins that are not installed or not active. Clicking it takes the user to a bare wp_die() white screen with no admin menu and no way forward other than the browser's back button or the "Manage plugins" link.
The Plugins list table and the AJAX install flow already block activation in this case (the AJAX flow since #61294). The installer screen should behave the same way.
Steps to reproduce
- Create a plugin with this header and zip it:
<?php /** * Plugin Name: Dep Test Plugin * Requires Plugins: classic-editor */
- Make sure the required plugin (Classic Editor in this example) is not installed on the site
- Go to Plugins > Add New Plugin > Upload Plugin and install the zip.
- On the result screen, click "Activate Plugin".
Actual result: a wp_die() white screen reading "Error: Dep Test Plugin requires 1 plugin to be installed and activated: Classic Editor. Manage plugins."
Expected result: the result screen does not offer "Activate Plugin" (or "Network Activate" on multisite) and tells the user which plugins are missing, inside the normal admin screen.
Cause
Uploads and non-JS installs run through update.php?action=upload-plugin and update.php?action=install-plugin, which render Plugin_Installer_Skin. Plugin_Installer_Skin::after() builds the activate links based only on the install result, the activate_plugin capability and whether the plugin is already active. It never checks the plugin's requirements, and update.php never initializes WP_Plugin_Dependencies.
The link goes to plugins.php?action=activate, where activate_plugin() calls validate_plugin_requirements(), gets a plugin_missing_dependencies error, and plugins.php passes it to wp_die().
Proposed fix
In Plugin_Installer_Skin::after(), when the install succeeded and the user can activate the plugin, call validate_plugin_requirements(), the same check activate_plugin() runs. If it returns a WP_Error, remove the activate and network activate links and show the error message as an admin notice.
validate_plugin_requirements() already calls WP_Plugin_Dependencies::initialize(), and Plugin_Upgrader::install() has already cleared the plugins cache, so the newly installed plugin's header is read correctly. Reusing this function also means the installer screen never offers a link that activate_plugin() would reject, including the WordPress and PHP version checks and the validate_plugin_requirements filter.
Patch attached.
Testing instructions
- Apply the patch.
- Repeat the steps to reproduce above.
- Confirm the result screen shows an error notice naming the missing plugin and no "Activate Plugin" button.
- Upload a plugin without a
Requires Pluginsheader and confirm "Activate Plugin" still appears. - Optional: repeat on multisite from Network Admin and confirm "Network Activate" is hidden only in the unmet-dependency case.
Related
Attachments (4)
Change History (17)
#1
@
4 weeks ago
I can confirm this issue, helped @BusinessBloomer with the discovery, and wrote about it here https://blog.wonderm00n.com/2026/09/25/wordpress-plugins-should-a-pro-add-on-auto-install-its-free-version/
#2
@
4 weeks ago
Thanks for the detailed write-up and the patch. I can reproduce this: Plugin_Installer_Skin::after() builds the activate and network-activate links from only the install result, the activate_plugin capability and is_plugin_active(), so it never runs validate_plugin_requirements(). A plugin with an unmet Requires Plugins header still gets an Activate button, and plugins.php then rejects it with wp_die() -- the same failure the AJAX flow had before #61294.
Your fix (run the same check activate_plugin() uses, then drop the links and surface the error) looks right to me.
If it's useful, I'm happy to open a PR in the GitHub mirror carrying your fix plus unit tests, with props to you and webdados -- the installer-skin path has no coverage for the unmet-dependency case yet, and core will want a tested PR to land it. Equally happy to just add tests on top if you'd rather open the PR yourself. Either way, nice catch.
This ticket was mentioned in PR #13760 on WordPress/wordpress-develop by BusinessBloomer.
4 weeks ago
#3
- Keywords has-patch added
Problem
After installing a plugin from a zip (Plugins > Add New Plugin > Upload Plugin), the result screen offers "Activate Plugin" even when the plugin's Requires Plugins header lists plugins that are not installed or not active. Clicking it goes to plugins.php?action=activate, where activate_plugin() returns the plugin_missing_dependencies error and plugins.php passes it to wp_die(). The user lands on a bare error page with no admin menu.
The Plugins list screen and the AJAX install flow (#61294) already avoid this. The installer screen should too.
Change
In Plugin_Installer_Skin::after(), when the install succeeded and the user can activate the plugin, run validate_plugin_requirements(), the same check activate_plugin() runs. If it returns a WP_Error, remove the activate and network activate links and show the error message as an inline admin notice.
Reusing validate_plugin_requirements() means the installer screen never offers a link that activate_plugin() would reject, including the WordPress and PHP version checks and the validate_plugin_requirements filter.
wp_clean_plugins_cache( false ) is called first. When I tested, get_plugins() did not yet list the newly uploaded plugin at this point of the request, so WP_Plugin_Dependencies did not know its Requires Plugins header and the check passed. Without the cache clear the link was still offered. I have not identified where the stale list comes from, so a reviewer who knows this part of the upgrader may have a better place for the fix.
Testing
Tested by hand on WordPress 7.1, not yet with PHPUnit.
- Create a plugin whose header contains
Requires Plugins: classic-editorand zip it. Make sure Classic Editor is not installed. - Upload it under Plugins > Add New Plugin > Upload Plugin.
- Before: the result screen shows "Activate Plugin", and clicking it ends in the
wp_die()screen. - After: no "Activate Plugin" link, and an inline error says "Dep Test Plugin requires 1 plugin to be installed and activated: classic-editor. Manage plugins."
- A plugin without a
Requires Pluginsheader still shows "Activate Plugin" and activates normally.
Not tested: multisite (Network Activate), and the case where the dependency is installed but inactive.
Not changed here: plugins.php?action=activate still uses wp_die() when reached directly, for example from a stale tab.
AI assistance
The patch and this description were drafted with Claude Code (Anthropic), which also ran the manual test above on a local WordPress 7.1 site. I reproduced the original bug and reviewed the diff.
🤖 Generated with Claude Code
@BusinessBloomer commented on PR #13760:
4 weeks ago
#4
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
props-botlabel.
Unlinked Accounts
The following contributors have not linked their GitHub and WordPress.org accounts: @rodolfomelogli@….
Contributors, please read how to link your accounts to ensure your work is properly credited in WordPress releases.
To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.
Linked my GitHub and WordPress.org accounts (BusinessBloomer). Could the props list be refreshed? Thanks.
#5
@
4 weeks ago
- Component General → Plugins
- Focuses administration added
- Milestone Awaiting Review → 7.2
This ticket was mentioned in PR #13921 on WordPress/wordpress-develop by @algorithmsunlocks.
3 weeks ago
#7
- Keywords has-unit-tests added
Trac ticket:
Use of AI Tools
@algorithmsunlocks commented on PR #13921:
3 weeks ago
#8
The Upgrade Develop Version Tests failures are unrelated to this change: setup-php failed to install WP-CLI ("Could not setup wp-cli"), so wp core download exits with 127. PHPUnit, coding standards, and E2E all pass. Could someone re-run the failed jobs?
#9
@
3 weeks ago
Patch Testing Report
Patch tested: https://github.com/WordPress/wordpress-develop/pull/13921 (the fix from PR 13760 plus new PHPUnit tests)
Environment
- WordPress: 7.2-alpha-63166-src (trunk)
- PHP: 8.3.33
- Server: wordpress-develop local Docker environment
- Database: MySQL 9.7.2
- Browser: Chrome 154
- OS: macOS
- Theme: Twenty Twenty-Five
- Plugins: none (Classic Editor only for steps 4-5)
Steps
- Zip a plugin with
Requires Plugins: classic-editor, with Classic Editor not installed. - Plugins > Add New Plugin > Upload Plugin, and install the zip.
- Upload a plugin without
Requires Plugins. - Install Classic Editor but leave it inactive, then re-upload the first plugin.
- Activate Classic Editor, then re-upload the first plugin.
Actual Results
- Before the patch: "Activate Plugin" is offered and leads to a
wp_die()screen. - With the patch: no activation button, and an inline error notice names the missing plugin.
- A plugin without dependencies still gets a working "Activate Plugin" button.
- Inactive dependency: still no activation button.
- Active dependency: "Activate Plugin" is shown and works.
Additional Notes
- New tests in
tests/phpunit/tests/admin/pluginInstallerSkin.phpfail on trunk and pass with the patch, on single site and multisite. - The failing "Upgrade Develop Version Tests" jobs on the PR are unrelated: setup-php could not install WP-CLI. Could someone with access re-run them?
- Minor: the new notice names the dependency by slug (
classic-editor) instead of its display name ("Classic Editor"), because the dependency API data isn't fetched during the upload request. - Edge case: if
WP_Plugin_Dependencies::initialize()already ran earlier in the same request (only possible when a plugin triggers it), the new plugin isn't detected and the link is still shown. This is probably a follow-up. - @BusinessBloomer, PR 13921 keeps your commit unchanged. Feel free to cherry-pick the test commit into PR 13760 instead, and I'll close mine.
Supplemental Artifacts
Before: the upload result offers "Activate Plugin":

Before: clicking it ends in wp_die():

After: no activation link, and an inline error notice:

AI assistance: Yes. Claude Code (Claude Opus 5.5) was used for reproduction, root-cause tracing, writing the tests, and capturing the screenshots. I reviewed and manually verified everything.
@huzaifaalmesbah commented on PR #13921:
2 weeks ago
#10
One suggestion for tests/phpunit/tests/admin/pluginInstallerSkin.php:
Currently, the test covers when the dependency is missing/not installed.
Could we also add a test case for when the dependency is installed on the site, but inactive (is_plugin_inactive())? That would ensure both branches of validate_plugin_requirements() are covered for this skin.
#11
@
11 days ago
@huzaifaalmesbah thanks, added test_should_not_offer_activation_when_required_plugin_is_inactive() to PR https://github.com/WordPress/wordpress-develop/pull/13921, also rebased on current trunk. --group 66184 (3 tests): 2 fail on trunk, 3/3 pass with the patch on single site and multisite; --group plugins (156 tests) passes.
@algorithmsunlocks commented on PR #13921:
11 days ago
#12
@huzaifaalmesbah thanks, added test_should_not_offer_activation_when_required_plugin_is_inactive() to PR https://github.com/WordPress/wordpress-develop/pull/13921, also rebased on current trunk. --group 66184 (3 tests): 2 fail on trunk, 3/3 pass with the patch on single site and multisite; --group plugins (156 tests) passes.
#13
@
5 days ago
Thanks @algorithmsunlocks, the new inactive-dependency test works: on current trunk, 2 of the 3 tests fail without the src change, and all 3 pass with it on single site and multisite.
On the open question from PR 13760: the wp_clean_plugins_cache( false ) call is needed, because WP_Upgrader::run() calls $this->skin->after() before upgrader_process_complete and before Plugin_Upgrader::install() clears the cache.
Minor, non-blocking: when the required plugin is not installed, the notice shows the slug (classic-editor) instead of the name shown on the wp_die() screen ("Classic Editor"), since no plugin API data is loaded on this screen.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)
Hide the activate links in Plugin_Installer_Skin::after() and show the error notice when validate_plugin_requirements() fails, e.g. for unmet Requires Plugins dependencies