Commit 3d8d6bf87dd for woocommerce
commit 3d8d6bf87dd5aa0aba50a87d6161f0778f85c13e
Author: Chi-Hsuan Huang <chihsuan.tw@gmail.com>
Date: Fri Jul 10 15:29:50 2026 +0800
fix: respect cleared actionable order statuses in Activity Panel counts (#66483)
* fix(activity-panel): respect cleared actionable order statuses in counts endpoint
* refactor(activity-panel): drop redundant fallback guard, expand counts test coverage
diff --git a/plugins/woocommerce/changelog/fix-66480-empty-actionable-order-statuses b/plugins/woocommerce/changelog/fix-66480-empty-actionable-order-statuses
new file mode 100644
index 00000000000..ecb6c1102fe
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-66480-empty-actionable-order-statuses
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Activity Panel counts endpoint now respects an explicitly-empty actionable order statuses setting instead of falling back to the defaults, so a cleared setting reports zero orders to fulfill.
diff --git a/plugins/woocommerce/src/Admin/API/ActivityPanelCounts.php b/plugins/woocommerce/src/Admin/API/ActivityPanelCounts.php
index 519d6f07924..53dc4f13d00 100644
--- a/plugins/woocommerce/src/Admin/API/ActivityPanelCounts.php
+++ b/plugins/woocommerce/src/Admin/API/ActivityPanelCounts.php
@@ -59,17 +59,27 @@ class ActivityPanelCounts extends \WC_REST_Data_Controller {
* @return \WP_REST_Response
*/
public function get_counts( $request ) {
+ $order_statuses = (array) $request->get_param( 'order_statuses' );
+
+ // When a merchant has cleared every actionable order status there is nothing
+ // "to fulfill". Short-circuit to 0 rather than querying: an empty status list
+ // would otherwise count every order, and the previous client-side
+ // getUnreadOrders() returned 0 in this case.
+ $orders_to_fulfill_count = empty( $order_statuses )
+ ? 0
+ : $this->get_count_via(
+ '/wc-analytics/orders',
+ array(
+ 'page' => 1,
+ 'per_page' => 1,
+ 'status' => $order_statuses,
+ '_fields' => array( 'id' ),
+ )
+ );
+
return rest_ensure_response(
array(
- 'orders_to_fulfill_count' => $this->get_count_via(
- '/wc-analytics/orders',
- array(
- 'page' => 1,
- 'per_page' => 1,
- 'status' => $request->get_param( 'order_statuses' ),
- '_fields' => array( 'id' ),
- )
- ),
+ 'orders_to_fulfill_count' => $orders_to_fulfill_count,
'reviews_to_moderate_count' => $this->get_count_via(
'/wc-analytics/products/reviews',
array(
@@ -161,8 +171,13 @@ class ActivityPanelCounts extends \WC_REST_Data_Controller {
* @return array
*/
private function get_default_order_statuses() {
- $actionable = get_option( 'woocommerce_actionable_order_statuses', array() );
- return is_array( $actionable ) && ! empty( $actionable ) ? $actionable : array( 'processing', 'on-hold' );
+ $actionable = get_option( 'woocommerce_actionable_order_statuses', false );
+
+ // Any array is respected as-is, including an explicitly empty one: the merchant
+ // intentionally cleared all actionable statuses, so there is nothing to fulfill,
+ // matching the previous client-side behaviour. A missing (never configured) or
+ // malformed option falls back to the built-in defaults.
+ return is_array( $actionable ) ? $actionable : array( 'processing', 'on-hold' );
}
/**
diff --git a/plugins/woocommerce/tests/php/src/Admin/API/ActivityPanelCountsTest.php b/plugins/woocommerce/tests/php/src/Admin/API/ActivityPanelCountsTest.php
index b27469fec97..f354e3d62de 100644
--- a/plugins/woocommerce/tests/php/src/Admin/API/ActivityPanelCountsTest.php
+++ b/plugins/woocommerce/tests/php/src/Admin/API/ActivityPanelCountsTest.php
@@ -16,6 +16,7 @@ use WC_Helper_Product;
use WC_REST_Unit_Test_Case;
use WP_Error;
use WP_REST_Request;
+use WP_REST_Server;
/**
* ActivityPanelCounts API controller test.
@@ -181,4 +182,66 @@ class ActivityPanelCountsTest extends WC_REST_Unit_Test_Case {
$this->assertNull( $data['products_low_in_stock_count'] );
$this->assertEquals( 1, $data['orders_to_fulfill_count'] );
}
+
+ /**
+ * Re-register REST routes on a fresh server. The endpoint's order_statuses default is
+ * computed at registration time (which setUp() has already run), so tests that change
+ * the actionable statuses option must re-register routes for the default to pick it up,
+ * as it would on a real request.
+ */
+ private function reregister_routes() {
+ $this->server = new WP_REST_Server();
+ $GLOBALS['wp_rest_server'] = $this->server;
+ // phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment -- Re-firing a core hook to re-register routes in the test, not defining one.
+ do_action( 'rest_api_init' );
+ }
+
+ /**
+ * Test that clearing all actionable order statuses yields a 0 orders-to-fulfill count,
+ * matching the previous client-side getUnreadOrders() behaviour, rather than falling back
+ * to the default statuses. Mirrors the client, which calls the endpoint without passing
+ * order_statuses and relies on the endpoint default.
+ *
+ * Option changes are rolled back by the per-test DB transaction, so no manual cleanup.
+ */
+ public function test_cleared_actionable_statuses_yield_zero_orders_to_fulfill() {
+ wp_set_current_user( $this->user );
+
+ WC_Helper_Order::create_order( 1, null, array( 'status' => OrderStatus::PROCESSING ) );
+
+ // Merchant cleared every actionable status.
+ update_option( 'woocommerce_actionable_order_statuses', array() );
+ $this->reregister_routes();
+
+ $request = new WP_REST_Request( 'GET', self::ENDPOINT );
+ $response = $this->server->dispatch( $request );
+ $data = $response->get_data();
+
+ $this->assertEquals( 200, $response->get_status() );
+ $this->assertSame( 0, $data['orders_to_fulfill_count'] );
+ }
+
+ /**
+ * Test that a merchant's custom (non-empty) actionable statuses selection drives the
+ * endpoint's default orders-to-fulfill count when the client passes no order_statuses
+ * param. Counts are asymmetric (2 completed vs 1 processing) so falling back to the
+ * built-in processing/on-hold defaults would fail the assertion.
+ */
+ public function test_custom_actionable_statuses_option_drives_default_count() {
+ wp_set_current_user( $this->user );
+
+ WC_Helper_Order::create_order( 1, null, array( 'status' => OrderStatus::PROCESSING ) );
+ WC_Helper_Order::create_order( 1, null, array( 'status' => OrderStatus::COMPLETED ) );
+ WC_Helper_Order::create_order( 1, null, array( 'status' => OrderStatus::COMPLETED ) );
+
+ update_option( 'woocommerce_actionable_order_statuses', array( OrderStatus::COMPLETED ) );
+ $this->reregister_routes();
+
+ $request = new WP_REST_Request( 'GET', self::ENDPOINT );
+ $response = $this->server->dispatch( $request );
+ $data = $response->get_data();
+
+ $this->assertEquals( 200, $response->get_status() );
+ $this->assertSame( 2, $data['orders_to_fulfill_count'] );
+ }
}