Commit ae9c01e9f6e for woocommerce

commit ae9c01e9f6e9c122b02ebc3d427dcdd7b438513a
Author: Michael Pretty <prettyboymp@users.noreply.github.com>
Date:   Mon Apr 13 06:24:43 2026 -0400

    Exclude checkout-draft orders from default REST API order queries (#63743)

    * Exclude checkout-draft orders from default API order queries

    Set exclude_from_search to true for the checkout-draft post status and
    teach HPOS OrdersTableQuery to respect this attribute when resolving
    status=any, aligning it with WP_Query's existing behavior.

    Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

    * Add changefile(s) from automation for the following project(s): woocommerce

    * Add changefile(s) from automation for the following project(s): woocommerce

    * Update changelog to minor/update and fix lint alignment warning

    Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

    * Address review findings: CLOG-2, CMT-2

    - CLOG-2: Broaden changelog description to reflect full scope (HPOS, WP_Query, REST API)
    - CMT-2: Fix test comment to clarify both checkout-draft and auto-draft use same exclusion mechanism (exclude_from_search=true)

    Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

    * Add changefile(s) from automation for the following project(s): woocommerce

    ---------

    Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
    Co-authored-by: woocommercebot <woocommercebot@users.noreply.github.com>

diff --git a/plugins/woocommerce/changelog/63743-fix-exclude-checkout-draft-from-api-any-status b/plugins/woocommerce/changelog/63743-fix-exclude-checkout-draft-from-api-any-status
new file mode 100644
index 00000000000..ef86fba57c2
--- /dev/null
+++ b/plugins/woocommerce/changelog/63743-fix-exclude-checkout-draft-from-api-any-status
@@ -0,0 +1,4 @@
+Significance: minor
+Type: update
+
+Exclude checkout-draft orders from default REST API order queries (status=any). API consumers that rely on checkout-draft orders appearing in unfiltered order listings will need to explicitly request them via `status=checkout-draft`.
\ No newline at end of file
diff --git a/plugins/woocommerce/src/Blocks/Domain/Services/DraftOrders.php b/plugins/woocommerce/src/Blocks/Domain/Services/DraftOrders.php
index a8b05145455..11431f1b822 100644
--- a/plugins/woocommerce/src/Blocks/Domain/Services/DraftOrders.php
+++ b/plugins/woocommerce/src/Blocks/Domain/Services/DraftOrders.php
@@ -122,7 +122,7 @@ class DraftOrders {
 		return [
 			'label'                     => _x( 'Draft', 'Order status', 'woocommerce' ),
 			'public'                    => false,
-			'exclude_from_search'       => false,
+			'exclude_from_search'       => true,
 			'show_in_admin_all_list'    => false,
 			'show_in_admin_status_list' => true,
 			/* translators: %s: number of orders */
diff --git a/plugins/woocommerce/src/Internal/DataStores/Orders/OrdersTableQuery.php b/plugins/woocommerce/src/Internal/DataStores/Orders/OrdersTableQuery.php
index 5e9e988f251..b17c6c07d04 100644
--- a/plugins/woocommerce/src/Internal/DataStores/Orders/OrdersTableQuery.php
+++ b/plugins/woocommerce/src/Internal/DataStores/Orders/OrdersTableQuery.php
@@ -698,8 +698,10 @@ class OrdersTableQuery {
 		}

 		if ( empty( $this->args['status'] ) || in_array( 'any', $this->args['status'], true ) ) {
-			// Querying for 'any' status or empty status, filter to valid statuses from wc_get_order_statuses().
-			$this->args['status'] = $valid_statuses;
+			// Querying for 'any' status or empty status, filter to valid statuses from wc_get_order_statuses(),
+			// excluding statuses marked as exclude_from_search (e.g. checkout-draft) to match WP_Query behavior.
+			$exclude              = get_post_stati( array( 'exclude_from_search' => true ) );
+			$this->args['status'] = array_diff( $valid_statuses, $exclude );
 		} elseif ( in_array( 'all', $this->args['status'], true ) ) {
 			// Querying for 'all' status does not filter by status at all.
 			$this->args['status'] = array();
diff --git a/plugins/woocommerce/tests/php/src/Internal/DataStores/Orders/OrdersTableDataStoreTests.php b/plugins/woocommerce/tests/php/src/Internal/DataStores/Orders/OrdersTableDataStoreTests.php
index 4d09ff7d147..8a033bb6006 100644
--- a/plugins/woocommerce/tests/php/src/Internal/DataStores/Orders/OrdersTableDataStoreTests.php
+++ b/plugins/woocommerce/tests/php/src/Internal/DataStores/Orders/OrdersTableDataStoreTests.php
@@ -722,13 +722,13 @@ class OrdersTableDataStoreTests extends \HposTestCase {
 		$order_checkout_draft->set_status( 'checkout-draft' );
 		$order_checkout_draft->save();

-		// Test 'status' => 'any' - should return only valid WooCommerce statuses (excludes internal WordPress statuses like auto-draft).
+		// Test 'status' => 'any' - should return only valid WooCommerce statuses, excluding statuses with exclude_from_search=true (checkout-draft, auto-draft).
 		$query = new OrdersTableQuery( array( 'status' => 'any' ) );
-		$this->assertEquals( 4, count( $query->orders ), "status='any' should return only valid WooCommerce statuses" );
+		$this->assertEquals( 3, count( $query->orders ), "status='any' should return only merchant-facing WooCommerce statuses" );
 		$this->assertContains( $order_pending->get_id(), $query->orders, "status='any' should include pending orders" );
 		$this->assertContains( $order_processing->get_id(), $query->orders, "status='any' should include processing orders" );
 		$this->assertContains( $order_completed->get_id(), $query->orders, "status='any' should include completed orders" );
-		$this->assertContains( $order_checkout_draft->get_id(), $query->orders, "status='any' should include checkout-draft orders (registered WC status)" );
+		$this->assertNotContains( $order_checkout_draft->get_id(), $query->orders, "status='any' should exclude checkout-draft orders (exclude_from_search)" );
 		$this->assertNotContains( $order_auto_draft->get_id(), $query->orders, "status='any' should exclude auto-draft orders (internal WordPress status)" );

 		// Test 'status' => 'all' - should return all statuses without filtering.
@@ -751,24 +751,24 @@ class OrdersTableDataStoreTests extends \HposTestCase {

 		// Test with array of statuses including 'any'.
 		$query = new OrdersTableQuery( array( 'status' => array( 'any' ) ) );
-		$this->assertEquals( 4, count( $query->orders ), "status=['any'] should work same as status='any'" );
+		$this->assertEquals( 3, count( $query->orders ), "status=['any'] should work same as status='any'" );

 		// Test empty status (should behave like 'any') - historical and WP_Query like behavior.
 		$query = new OrdersTableQuery( array( 'status' => '' ) );
-		$this->assertEquals( 4, count( $query->orders ), "Empty status should behave like 'any' and return only valid WooCommerce statuses" );
+		$this->assertEquals( 3, count( $query->orders ), "Empty status should behave like 'any' and return only merchant-facing WooCommerce statuses" );
 		$this->assertContains( $order_pending->get_id(), $query->orders, 'Empty status should include pending orders' );
 		$this->assertContains( $order_processing->get_id(), $query->orders, 'Empty status should include processing orders' );
 		$this->assertContains( $order_completed->get_id(), $query->orders, 'Empty status should include completed orders' );
-		$this->assertContains( $order_checkout_draft->get_id(), $query->orders, 'Empty status should include checkout-draft orders (registered WC status)' );
+		$this->assertNotContains( $order_checkout_draft->get_id(), $query->orders, 'Empty status should exclude checkout-draft orders (exclude_from_search)' );
 		$this->assertNotContains( $order_auto_draft->get_id(), $query->orders, 'Empty status should exclude auto-draft orders (internal WordPress status)' );

 		// Test omitted status (should behave like 'any').
 		$query = new OrdersTableQuery( array() );
-		$this->assertEquals( 4, count( $query->orders ), "Omitted status should behave like 'any' and return only valid WooCommerce statuses" );
+		$this->assertEquals( 3, count( $query->orders ), "Omitted status should behave like 'any' and return only merchant-facing WooCommerce statuses" );
 		$this->assertContains( $order_pending->get_id(), $query->orders, 'Omitted status should include pending orders' );
 		$this->assertContains( $order_processing->get_id(), $query->orders, 'Omitted status should include processing orders' );
 		$this->assertContains( $order_completed->get_id(), $query->orders, 'Omitted status should include completed orders' );
-		$this->assertContains( $order_checkout_draft->get_id(), $query->orders, 'Omitted status should include checkout-draft orders (registered WC status)' );
+		$this->assertNotContains( $order_checkout_draft->get_id(), $query->orders, 'Omitted status should exclude checkout-draft orders (exclude_from_search)' );
 		$this->assertNotContains( $order_auto_draft->get_id(), $query->orders, 'Omitted status should exclude auto-draft orders (internal WordPress status)' );
 	}