Commit e9ef0196e33 for woocommerce

commit e9ef0196e335a5a0c27e27d3093f1503f217f123
Author: Boro Sitnikovski <buritomath@gmail.com>
Date:   Wed Mar 18 15:59:40 2026 +0100

    Add woocommerce_draft_order_batch_size filter to DraftOrders cleanup (#63740)

    * Add woocommerce_draft_order_batch_size filter to DraftOrders cleanup

    Make the batch size in delete_expired_draft_orders() filterable via
    `woocommerce_draft_order_batch_size` (default: 20) so high-traffic sites
    can tune cleanup throughput without patching core.

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

    * Changelog

    * Potential fix for pull request finding

    Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

    * Fix hard-coded batch size check in assert_order_results and add regression test

    The guard in assert_order_results compared against literal 20 instead of
    the $expected_batch_size parameter, causing a false exception when the
    woocommerce_draft_order_batch_size filter returned a value greater than 20.

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

    * Fix phpcs lint errors in new test method

    - Add missing doc comment
    - Add space after function keyword in closures
    - Expand inline closure to multi-line
    - Remove spaces around string array keys

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

    * Rename filter to woocommerce_delete_expired_draft_orders_batch_size

    Makes it clear the filter scopes to the expired draft order deletion
    specifically, not draft order handling in general.

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

    * Improve filter docblock to clarify throughput use case

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

    * Fix test to create distinct WC_Order instances per batch

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

    ---------

    Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
    Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

diff --git a/plugins/woocommerce/changelog/add-draft-orders-batch-size-filter b/plugins/woocommerce/changelog/add-draft-orders-batch-size-filter
new file mode 100644
index 00000000000..5fdb0cf45ac
--- /dev/null
+++ b/plugins/woocommerce/changelog/add-draft-orders-batch-size-filter
@@ -0,0 +1,4 @@
+Significance: minor
+Type: add
+
+Add woocommerce_delete_expired_draft_orders_batch_size filter to make draft order cleanup batch size configurable
diff --git a/plugins/woocommerce/src/Blocks/Domain/Services/DraftOrders.php b/plugins/woocommerce/src/Blocks/Domain/Services/DraftOrders.php
index 422b09742a0..a8b05145455 100644
--- a/plugins/woocommerce/src/Blocks/Domain/Services/DraftOrders.php
+++ b/plugins/woocommerce/src/Blocks/Domain/Services/DraftOrders.php
@@ -169,15 +169,25 @@ class DraftOrders {
 	}

 	/**
-	 * Delete draft orders older than a day in batches of 20.
+	 * Delete draft orders older than a day in configurable batches (default: 20).
 	 *
-	 * Ran on a daily cron schedule.
+	 * Ran on a daily cron schedule. Batch size is filterable via
+	 * `woocommerce_delete_expired_draft_orders_batch_size`.
 	 *
 	 * @internal
 	 */
 	public function delete_expired_draft_orders() {
-		$count      = 0;
-		$batch_size = 20;
+		$count = 0;
+		/**
+		 * Filters the number of draft orders deleted per batch during cleanup.
+		 *
+		 * Increasing this value can help improve deletion throughput for high-volume or busy stores
+		 * when the cleanup task cannot keep up with the draft orders backlog.
+		 *
+		 * @since 10.7.0
+		 * @param int $batch_size Number of draft orders to delete per batch. Default 20.
+		 */
+		$batch_size = max( 1, (int) apply_filters( 'woocommerce_delete_expired_draft_orders_batch_size', 20 ) );
 		$this->ensure_draft_status_registered();
 		$orders = wc_get_orders(
 			[
@@ -239,7 +249,7 @@ class DraftOrders {
 		$suffix = ' This is an indicator that something is filtering WooCommerce or WordPress queries and modifying the query parameters.';

 		// if count is greater than our expected batch size, then that's a problem.
-		if ( count( $order_results ) > 20 ) {
+		if ( count( $order_results ) > $expected_batch_size ) {
 			throw new Exception( 'There are an unexpected number of results returned from the query.' . $suffix );
 		}

diff --git a/plugins/woocommerce/tests/php/src/Blocks/Domain/Services/DeleteDraftOrders.php b/plugins/woocommerce/tests/php/src/Blocks/Domain/Services/DeleteDraftOrders.php
index 50d0d179cb5..8534dc0c536 100644
--- a/plugins/woocommerce/tests/php/src/Blocks/Domain/Services/DeleteDraftOrders.php
+++ b/plugins/woocommerce/tests/php/src/Blocks/Domain/Services/DeleteDraftOrders.php
@@ -123,6 +123,37 @@ class DeleteDraftOrders extends TestCase {
 		$this->assertEquals( 1, (int) $wpdb->get_var( "SELECT COUNT(ID) from $wpdb->posts posts WHERE posts.post_status = 'wc-on-hold'" ) );
 	}

+	/**
+	 * Test that a custom batch size filter allows more than the default 20 results without error.
+	 */
+	public function test_custom_batch_size_filter_allows_larger_results() {
+		add_filter(
+			'woocommerce_delete_expired_draft_orders_batch_size',
+			function () {
+				return 50;
+			}
+		);
+
+		$sample_results = function ( $results, $args ) {
+			if ( isset( $args['status'] ) && DraftOrders::DB_STATUS === $args['status'] ) {
+				$orders = array();
+				for ( $i = 0; $i < 50; $i++ ) {
+					$order = new WC_Order();
+					$order->set_status( DraftOrders::STATUS );
+					$orders[] = $order;
+				}
+				return $orders;
+			}
+			return $results;
+		};
+		$this->mock_results_for_wc_query( $sample_results );
+		$this->draft_orders_instance->delete_expired_draft_orders();
+		$this->assertNull( $this->caught_exception, 'No exception should be thrown when batch size filter allows more results.' );
+		$this->unset_mock_results_for_wc_query( $sample_results );
+
+		remove_all_filters( 'woocommerce_delete_expired_draft_orders_batch_size' );
+	}
+
 	public function test_greater_than_batch_results_error() {
 		$sample_results = function( $results, $args ) {
 			if ( isset( $args[ 'status' ] ) && DraftOrders::DB_STATUS === $args[ 'status' ] ) {