Commit f2829f88203 for woocommerce
commit f2829f88203d952c532a4358e427c23f53fbd8f0
Author: Saskia Teichmann <s-a-s-k-i-a@users.noreply.github.com>
Date: Mon Sep 14 14:41:19 2026 +0200
Rebuild the order count cache when a status is registered after the cache was primed (#68114)
* Rebuild order count cache when a status is registered after priming
The order count cache saves the set of statuses it was primed with and
serves cache hits as long as those keys are warm. A custom order status
registered afterwards (e.g. by a just-activated plugin) was therefore
missing from the Orders screen filter row for up to a day, and orders
moved into it kept inflating the count of their previous status, because
the incremental cache updates skip statuses that are not cached.
OrderUtil::get_count_for_type() now treats a cached set that lacks a
currently registered status as a cache miss and rebuilds the counts
from the database. Cached statuses that are not registered are still
served unchanged for backward compatibility.
Fixes #68009
* Harden registered-status read against non-array filter results
Address review: coerce the wc_order_statuses filter result before
array_keys() on the warm-cache path so a broken filter callback cannot
fatal there, and add a warm-cache regression test.
* Flush order counts on plugin (de)activation instead of a staleness check
Address review: drop the registered-status comparison from
OrderUtil::get_count_for_type() and instead flush the order count
cache from OrderCountCacheService when a plugin is activated or
deactivated, keeping the cache logic in its own context. Tests and
changelog updated accordingly.
* Register the custom status as a post status in the regression test
Real plugins register their custom order status via register_post_status()
in addition to the wc_order_statuses filter; without the registration the
HPOS orders table stores the status unprefixed and the count lands under
a different key, which made the test assert against the wrong scenario.
* Polish: compact changelog, shorter docblock, hook-focused activation test
Address review: the changelog entry is shortened for readability, the
flush_cache() docblock only describes what the method does, and the
activation test now mirrors the deactivation test — it verifies that the
hook flushes the cache without exercising custom status handling.
* Add missing blank line between test methods
---------
Co-authored-by: Vladimir Reznichenko <kalessil@gmail.com>
diff --git a/plugins/woocommerce/changelog/68009-order-count-cache-late-registered-status b/plugins/woocommerce/changelog/68009-order-count-cache-late-registered-status
new file mode 100644
index 00000000000..3ae107aef73
--- /dev/null
+++ b/plugins/woocommerce/changelog/68009-order-count-cache-late-registered-status
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Flush the order count cache when a plugin is activated or deactivated, so newly registered order statuses show up in the Orders list right away.
diff --git a/plugins/woocommerce/src/Caches/OrderCountCacheService.php b/plugins/woocommerce/src/Caches/OrderCountCacheService.php
index bcf6f787d16..f72b5fba0f1 100644
--- a/plugins/woocommerce/src/Caches/OrderCountCacheService.php
+++ b/plugins/woocommerce/src/Caches/OrderCountCacheService.php
@@ -52,6 +52,8 @@ class OrderCountCacheService {
add_action( 'woocommerce_before_trash_order', array( $this, 'update_on_order_trashed' ), 10, 2 );
add_action( 'woocommerce_before_delete_order', array( $this, 'update_on_order_deleted' ), 10, 2 );
add_action( self::BACKGROUND_EVENT_HOOK, array( $this, 'prime_cache_if_cold' ) );
+ add_action( 'activated_plugin', array( $this, 'flush_cache' ) );
+ add_action( 'deactivated_plugin', array( $this, 'flush_cache' ) );
add_action( 'action_scheduler_ensure_recurring_actions', array( $this, 'schedule_background_actions' ) );
if ( defined( 'WC_PLUGIN_BASENAME' ) ) {
@@ -93,6 +95,20 @@ class OrderCountCacheService {
}
}
+ /**
+ * Flush the order count cache for all order types.
+ *
+ * @internal
+ * @since 11.2.0
+ *
+ * @return void
+ */
+ public function flush_cache() {
+ foreach ( wc_get_order_types( 'order-count' ) as $order_type ) {
+ $this->order_count_cache->flush( $order_type );
+ }
+ }
+
/**
* Register background caching for each order type.
*
diff --git a/plugins/woocommerce/tests/php/src/Caching/OrderCountCacheServiceTest.php b/plugins/woocommerce/tests/php/src/Caching/OrderCountCacheServiceTest.php
index cf06a74afdb..f9485fc662c 100644
--- a/plugins/woocommerce/tests/php/src/Caching/OrderCountCacheServiceTest.php
+++ b/plugins/woocommerce/tests/php/src/Caching/OrderCountCacheServiceTest.php
@@ -215,4 +215,28 @@ class OrderCountCacheServiceTest extends \WC_Unit_Test_Case {
$this->assertNull( $this->order_cache->get( 'shop_order', array( OrderInternalStatus::PENDING ) ) );
}
+
+ /**
+ * Test that activating a plugin flushes the order count cache.
+ */
+ public function test_activated_plugin_flushes_cache(): void {
+ OrderUtil::get_count_for_type( 'shop_order' );
+ $this->assertNotNull( $this->order_cache->get( 'shop_order' ) );
+
+ do_action( 'activated_plugin', 'custom-status-plugin/custom-status-plugin.php', false );
+
+ $this->assertNull( $this->order_cache->get( 'shop_order' ) );
+ }
+
+ /**
+ * Test that deactivating a plugin flushes the order count cache.
+ */
+ public function test_deactivated_plugin_flushes_cache(): void {
+ OrderUtil::get_count_for_type( 'shop_order' );
+ $this->assertNotNull( $this->order_cache->get( 'shop_order' ) );
+
+ do_action( 'deactivated_plugin', 'custom-status-plugin/custom-status-plugin.php', false );
+
+ $this->assertNull( $this->order_cache->get( 'shop_order' ) );
+ }
}