Commit b0a9a70d08d for woocommerce
commit b0a9a70d08dc89ac97dd2757b3d175a45d352341
Author: Mike Jolley <mike.jolley@me.com>
Date: Mon Oct 12 17:09:50 2026 +0100
Clear the system status plugin cache when a plugin is deleted (#69601)
The system status plugin lists are cached in hour-long transients that
were cleared on activate, deactivate and upgrader events, but not on
deletion, so deleted plugins stayed listed for up to an hour. Hook
clean_plugin_cache() to deleted_plugin, and add REST tests covering
the delete and install paths.
Closes #39986.
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
diff --git a/plugins/woocommerce/changelog/fix-39986-system-status-deleted-plugins b/plugins/woocommerce/changelog/fix-39986-system-status-deleted-plugins
new file mode 100644
index 00000000000..a8aaea51811
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-39986-system-status-deleted-plugins
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Stop listing deleted plugins in the system status report and REST API for up to an hour after they are removed.
diff --git a/plugins/woocommerce/includes/rest-api/Controllers/Version2/class-wc-rest-system-status-v2-controller.php b/plugins/woocommerce/includes/rest-api/Controllers/Version2/class-wc-rest-system-status-v2-controller.php
index c0c0c845478..2b6fd7dcc72 100644
--- a/plugins/woocommerce/includes/rest-api/Controllers/Version2/class-wc-rest-system-status-v2-controller.php
+++ b/plugins/woocommerce/includes/rest-api/Controllers/Version2/class-wc-rest-system-status-v2-controller.php
@@ -52,6 +52,7 @@ class WC_REST_System_Status_V2_Controller extends WC_REST_Controller {
add_action( 'switch_theme', array( __CLASS__, 'clean_theme_cache' ) );
add_action( 'activate_plugin', array( __CLASS__, 'clean_plugin_cache' ) );
add_action( 'deactivate_plugin', array( __CLASS__, 'clean_plugin_cache' ) );
+ add_action( 'deleted_plugin', array( __CLASS__, 'clean_plugin_cache' ) );
add_action(
'upgrader_process_complete',
function ( $upgrader, $extra ) {
diff --git a/plugins/woocommerce/tests/php/includes/rest-api/Controllers/Version2/class-wc-rest-system-status-v2-controller-test.php b/plugins/woocommerce/tests/php/includes/rest-api/Controllers/Version2/class-wc-rest-system-status-v2-controller-test.php
index 90bde4ffc9d..5b32a4fe917 100644
--- a/plugins/woocommerce/tests/php/includes/rest-api/Controllers/Version2/class-wc-rest-system-status-v2-controller-test.php
+++ b/plugins/woocommerce/tests/php/includes/rest-api/Controllers/Version2/class-wc-rest-system-status-v2-controller-test.php
@@ -17,6 +17,13 @@ class WC_REST_System_Status_V2_Controller_Test extends WC_REST_Unit_Test_Case {
*/
private $sut;
+ /**
+ * Plugin installed from the sample-woo-plugin.zip test fixture.
+ *
+ * @var string
+ */
+ private const TEST_PLUGIN_FILE = 'sample-woo-plugin/sample-woo-plugin.php';
+
/**
* Set up test fixtures.
*/
@@ -30,9 +37,16 @@ class WC_REST_System_Status_V2_Controller_Test extends WC_REST_Unit_Test_Case {
* Tear down test fixtures.
*/
public function tearDown(): void {
- parent::tearDown();
- remove_all_filters( 'wc_get_template' );
- delete_transient( 'wc_system_status_theme_info' );
+ try {
+ remove_all_filters( 'wc_get_template' );
+ delete_transient( 'wc_system_status_theme_info' );
+
+ if ( file_exists( WP_PLUGIN_DIR . '/' . self::TEST_PLUGIN_FILE ) ) {
+ delete_plugins( array( self::TEST_PLUGIN_FILE ) );
+ }
+ } finally {
+ parent::tearDown();
+ }
}
/**
@@ -123,4 +137,73 @@ class WC_REST_System_Status_V2_Controller_Test extends WC_REST_Unit_Test_Case {
$this->assertSame( 'MH', $settings['store_base_state'] );
$this->assertSame( DefaultCustomerAddress::GEOLOCATION_AJAX, $settings['default_customer_location'] );
}
+
+ /**
+ * @testdox Should not list a plugin in inactive_plugins after it has been deleted.
+ */
+ public function test_get_inactive_plugins_excludes_deleted_plugin(): void {
+ $this->login_as_administrator();
+ $this->install_test_plugin();
+
+ $this->assertContains(
+ self::TEST_PLUGIN_FILE,
+ $this->get_inactive_plugin_files_from_endpoint(),
+ 'Precondition: the installed but inactive plugin should be listed'
+ );
+
+ $this->assertTrue( delete_plugins( array( self::TEST_PLUGIN_FILE ) ), 'Precondition: the plugin should be deleted' );
+ // The non-persistent `plugins` cache group would be empty on the next real request.
+ wp_clean_plugins_cache( false );
+
+ $this->assertNotContains(
+ self::TEST_PLUGIN_FILE,
+ $this->get_inactive_plugin_files_from_endpoint(),
+ 'A deleted plugin should not be served from the cached inactive plugins list'
+ );
+ }
+
+ /**
+ * @testdox Should list a plugin in inactive_plugins after it has been installed.
+ */
+ public function test_get_inactive_plugins_includes_newly_installed_plugin(): void {
+ $this->login_as_administrator();
+
+ $this->assertNotContains(
+ self::TEST_PLUGIN_FILE,
+ $this->get_inactive_plugin_files_from_endpoint(),
+ 'Precondition: the plugin should not be listed before it is installed'
+ );
+
+ $this->install_test_plugin();
+
+ $this->assertContains(
+ self::TEST_PLUGIN_FILE,
+ $this->get_inactive_plugin_files_from_endpoint(),
+ 'A newly installed plugin should not be missing from the cached inactive plugins list'
+ );
+ }
+
+ /**
+ * Install the sample-woo-plugin.zip fixture through the plugin upgrader.
+ */
+ private function install_test_plugin(): void {
+ require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
+
+ $upgrader = new Plugin_Upgrader( new Automatic_Upgrader_Skin() );
+ $result = $upgrader->install( WC_Unit_Tests_Bootstrap::instance()->tests_dir . '/data/sample-woo-plugin.zip' );
+
+ $this->assertTrue( $result, 'Precondition: the test plugin should be installed' );
+ }
+
+ /**
+ * Fetch the inactive plugin files reported by the system status endpoint.
+ *
+ * @return string[]
+ */
+ private function get_inactive_plugin_files_from_endpoint(): array {
+ $response = $this->do_rest_get_request( 'system_status', array( '_fields' => 'inactive_plugins' ) );
+ $this->assertSame( 200, $response->get_status() );
+
+ return array_column( $response->get_data()['inactive_plugins'], 'plugin' );
+ }
}