Skip to content
This repository was archived by the owner on Feb 23, 2024. It is now read-only.

Commit abac25f

Browse files
committed
Move Draft order logic behind feature flag. (#2874)
* refactor all draft order functionality to be in it’s own class and feature gate it. * move and fix tests for draft order deletes * add test to ensure only draft orders are deleted * implement review feedback and assert valid results before deleting * update tests * doh method can’t be protected * fix conditional for removing scheduled action * switch to use Woo Core function for catching the exception * add tests for error handling. * use `$wpdb->prepare` and remove temp group on test # Conflicts: # src/Domain/Bootstrap.php
1 parent 7830486 commit abac25f

File tree

6 files changed

+364
-161
lines changed

6 files changed

+364
-161
lines changed

src/Domain/Bootstrap.php

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
use Automattic\WooCommerce\Blocks\Payments\Integrations\Stripe;
2121
use Automattic\WooCommerce\Blocks\Payments\Integrations\Cheque;
2222
use Automattic\WooCommerce\Blocks\Payments\Integrations\PayPal;
23+
use Automattic\WooCommerce\Blocks\Domain\Services\DraftOrders;
2324

2425
/**
2526
* Takes care of bootstrapping the plugin.
@@ -77,7 +78,7 @@ protected function init() {
7778
$this->container->get( Installer::class );
7879
BlockAssets::init();
7980
}
80-
81+
$this->container->get( DraftOrders::class )->init();
8182
$this->container->get( PaymentsApi::class );
8283
$this->container->get( RestApi::class );
8384
Library::init();
@@ -190,6 +191,12 @@ function ( Container $container ) {
190191
return new Installer();
191192
}
192193
);
194+
$this->container->register(
195+
DraftOrders::class,
196+
function( Container $container ) {
197+
return new DraftOrders( $container->get( Package::class ) );
198+
}
199+
);
193200
}
194201

195202
/**
Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
<?php
2+
/**
3+
* Sets up all logic related to the Checkout Draft Orders service
4+
*
5+
* @package WooCommerce/Blocks
6+
*/
7+
8+
namespace Automattic\WooCommerce\Blocks\Domain\Services;
9+
10+
use Automattic\WooCommerce\Blocks\Domain\Package;
11+
use Exception;
12+
use WC_Order;
13+
14+
/**
15+
* Service class for adding DraftOrder functionality to WooCommerce core.
16+
*/
17+
class DraftOrders {
18+
19+
const DB_STATUS = 'wc-checkout-draft';
20+
const STATUS = 'checkout-draft';
21+
22+
/**
23+
* Holds the Package instance
24+
*
25+
* @var Package
26+
*/
27+
private $package;
28+
29+
/**
30+
* Constructor
31+
*
32+
* @param Package $package An instance of the package class.
33+
*/
34+
public function __construct( Package $package ) {
35+
$this->package = $package;
36+
}
37+
38+
/**
39+
* Set all hooks related to adding Checkout Draft order functionality to Woo Core.
40+
*/
41+
public function init() {
42+
if ( $this->package->is_feature_plugin_build() ) {
43+
add_filter( 'wc_order_statuses', [ $this, 'register_draft_order_status' ] );
44+
add_filter( 'woocommerce_register_shop_order_post_statuses', [ $this, 'register_draft_order_post_status' ] );
45+
add_filter( 'woocommerce_valid_order_statuses_for_payment', [ $this, 'append_draft_order_post_status' ] );
46+
add_action( 'woocommerce_cleanup_draft_orders', [ $this, 'delete_expired_draft_orders' ] );
47+
add_action( 'admin_init', [ $this, 'install' ] );
48+
} else {
49+
// Maybe remove existing cronjob if present because it shouldn't be needed in the environment.
50+
add_action( 'admin_init', [ $this, 'uninstall' ] );
51+
}
52+
}
53+
54+
/**
55+
* Installation related logic for Draft order functionality.
56+
*
57+
* @internal
58+
*/
59+
public function install() {
60+
$this->maybe_create_cronjobs();
61+
}
62+
63+
/**
64+
* Remove cronjobs if they exist (but only from admin).
65+
*
66+
* @internal
67+
*/
68+
public function uninstall() {
69+
$this->maybe_remove_cronjobs();
70+
}
71+
72+
/**
73+
* Maybe create cron events.
74+
*/
75+
protected function maybe_create_cronjobs() {
76+
if ( function_exists( 'as_next_scheduled_action' ) && false === as_next_scheduled_action( 'woocommerce_cleanup_draft_orders' ) ) {
77+
as_schedule_recurring_action( strtotime( 'midnight tonight' ), DAY_IN_SECONDS, 'woocommerce_cleanup_draft_orders' );
78+
}
79+
}
80+
81+
/**
82+
* Unschedule cron jobs that are present.
83+
*/
84+
protected function maybe_remove_cronjobs() {
85+
if ( function_exists( 'as_next_scheduled_action' ) && as_next_scheduled_action( 'woocommerce_cleanup_draft_orders' ) ) {
86+
as_unschedule_all_actions( 'woocommerce_cleanup_draft_orders' );
87+
}
88+
}
89+
90+
/**
91+
* Register custom order status for orders created via the API during checkout.
92+
*
93+
* Draft order status is used before payment is attempted, during checkout, when a cart is converted to an order.
94+
*
95+
* @param array $statuses Array of statuses.
96+
* @internal
97+
* @return array
98+
*/
99+
public function register_draft_order_status( array $statuses ) {
100+
$statuses[ self::DB_STATUS ] = _x( 'Draft', 'Order status', 'woo-gutenberg-products-block' );
101+
return $statuses;
102+
}
103+
104+
/**
105+
* Register custom order post status for orders created via the API during checkout.
106+
*
107+
* @param array $statuses Array of statuses.
108+
* @internal
109+
110+
* @return array
111+
*/
112+
public function register_draft_order_post_status( array $statuses ) {
113+
$statuses[ self::DB_STATUS ] = [
114+
'label' => _x( 'Draft', 'Order status', 'woo-gutenberg-products-block' ),
115+
'public' => false,
116+
'exclude_from_search' => false,
117+
'show_in_admin_all_list' => false,
118+
'show_in_admin_status_list' => true,
119+
/* translators: %s: number of orders */
120+
'label_count' => _n_noop( 'Drafts <span class="count">(%s)</span>', 'Drafts <span class="count">(%s)</span>', 'woo-gutenberg-products-block' ),
121+
];
122+
return $statuses;
123+
}
124+
125+
/**
126+
* Append draft status to a list of statuses.
127+
*
128+
* @param array $statuses Array of statuses.
129+
* @internal
130+
131+
* @return array
132+
*/
133+
public function append_draft_order_post_status( $statuses ) {
134+
$statuses[] = self::STATUS;
135+
return $statuses;
136+
}
137+
138+
/**
139+
* Delete draft orders older than a day in batches of 20.
140+
*
141+
* Ran on a daily cron schedule.
142+
*
143+
* @internal
144+
*/
145+
public function delete_expired_draft_orders() {
146+
$count = 0;
147+
$batch_size = 20;
148+
$orders = wc_get_orders(
149+
[
150+
'date_modified' => '<=' . strtotime( '-1 DAY' ),
151+
'limit' => $batch_size,
152+
'status' => self::DB_STATUS,
153+
'type' => 'shop_order',
154+
]
155+
);
156+
157+
// do we bail because the query results are unexpected?
158+
try {
159+
$this->assert_order_results( $orders, $batch_size );
160+
if ( $orders ) {
161+
foreach ( $orders as $order ) {
162+
$order->delete( true );
163+
$count ++;
164+
}
165+
}
166+
if ( $batch_size === $count && function_exists( 'as_enqueue_async_action' ) ) {
167+
as_enqueue_async_action( 'woocommerce_cleanup_draft_orders' );
168+
}
169+
} catch ( Exception $error ) {
170+
wc_caught_exception( $error, __METHOD__ );
171+
}
172+
}
173+
174+
/**
175+
* Asserts whether incoming order results are expected given the query
176+
* this service class executes.
177+
*
178+
* @param WC_Order[] $order_results The order results being asserted.
179+
* @param int $expected_batch_size The expected batch size for the results.
180+
* @throws Exception If any assertions fail, an exception is thrown.
181+
*/
182+
private function assert_order_results( $order_results, $expected_batch_size ) {
183+
// if not an array, then just return because it won't get handled
184+
// anyways.
185+
if ( ! is_array( $order_results ) ) {
186+
return;
187+
}
188+
189+
$suffix = ' This is an indicator that something is filtering WooCommerce or WordPress queries and modifying the query parameters.';
190+
191+
// if count is greater than our expected batch size, then that's a problem.
192+
if ( count( $order_results ) > 20 ) {
193+
throw new Exception( 'There are an unexpected number of results returned from the query.' . $suffix );
194+
}
195+
196+
// if any of the returned orders are not draft (or not a WC_Order), then that's a problem.
197+
foreach ( $order_results as $order ) {
198+
if ( ! ( $order instanceof WC_Order ) ) {
199+
throw new Exception( 'The returned results contain a value that is not a WC_Order.' . $suffix );
200+
}
201+
if ( ! $order->has_status( self::STATUS ) ) {
202+
throw new Exception( 'The results contain an order that is not a `wc-checkout-draft` status in the results.' . $suffix );
203+
}
204+
}
205+
}
206+
}

src/Installer.php

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ public function __construct() {
2525
*/
2626
public function install() {
2727
$this->maybe_create_tables();
28-
$this->maybe_create_cronjobs();
2928
}
3029

3130
/**
@@ -120,13 +119,4 @@ function() use ( $table_name ) {
120119
}
121120
);
122121
}
123-
124-
/**
125-
* Maybe create cron events.
126-
*/
127-
protected function maybe_create_cronjobs() {
128-
if ( function_exists( 'as_next_scheduled_action' ) && false === as_next_scheduled_action( 'woocommerce_cleanup_draft_orders' ) ) {
129-
as_schedule_recurring_action( strtotime( 'midnight tonight' ), DAY_IN_SECONDS, 'woocommerce_cleanup_draft_orders' );
130-
}
131-
}
132122
}

src/Library.php

Lines changed: 0 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,6 @@ class Library {
2222
public static function init() {
2323
add_action( 'init', array( __CLASS__, 'register_blocks' ) );
2424
add_action( 'init', array( __CLASS__, 'define_tables' ) );
25-
add_filter( 'wc_order_statuses', array( __CLASS__, 'register_draft_order_status' ) );
26-
add_filter( 'woocommerce_register_shop_order_post_statuses', array( __CLASS__, 'register_draft_order_post_status' ) );
27-
add_filter( 'woocommerce_valid_order_statuses_for_payment', array( __CLASS__, 'append_draft_order_post_status' ) );
28-
add_action( 'woocommerce_cleanup_draft_orders', array( __CLASS__, 'delete_expired_draft_orders' ) );
2925
}
3026

3127
/**
@@ -110,76 +106,4 @@ protected static function register_atomic_blocks() {
110106
$instance->register_block_type();
111107
}
112108
}
113-
114-
/**
115-
* Register custom order status for orders created via the API during checkout.
116-
*
117-
* Draft order status is used before payment is attempted, during checkout, when a cart is converted to an order.
118-
*
119-
* @param array $statuses Array of statuses.
120-
* @return array
121-
*/
122-
public static function register_draft_order_status( array $statuses ) {
123-
$statuses['wc-checkout-draft'] = _x( 'Draft', 'Order status', 'woo-gutenberg-products-block' );
124-
return $statuses;
125-
}
126-
127-
/**
128-
* Register custom order post status for orders created via the API during checkout.
129-
*
130-
* @param array $statuses Array of statuses.
131-
* @return array
132-
*/
133-
public static function register_draft_order_post_status( array $statuses ) {
134-
$statuses['wc-checkout-draft'] = [
135-
'label' => _x( 'Draft', 'Order status', 'woo-gutenberg-products-block' ),
136-
'public' => false,
137-
'exclude_from_search' => false,
138-
'show_in_admin_all_list' => false,
139-
'show_in_admin_status_list' => true,
140-
/* translators: %s: number of orders */
141-
'label_count' => _n_noop( 'Drafts <span class="count">(%s)</span>', 'Drafts <span class="count">(%s)</span>', 'woo-gutenberg-products-block' ),
142-
];
143-
return $statuses;
144-
}
145-
146-
/**
147-
* Append draft status to a list of statuses.
148-
*
149-
* @param array $statuses Array of statuses.
150-
* @return array
151-
*/
152-
public static function append_draft_order_post_status( $statuses ) {
153-
$statuses[] = 'checkout-draft';
154-
return $statuses;
155-
}
156-
157-
/**
158-
* Delete draft orders older than a day in batches of 20.
159-
*
160-
* Ran on a daily cron schedule.
161-
*/
162-
public static function delete_expired_draft_orders() {
163-
$count = 0;
164-
$batch_size = 20;
165-
$orders = wc_get_orders(
166-
[
167-
'date_modified' => '<=' . strtotime( '-1 DAY' ),
168-
'limit' => $batch_size,
169-
'status' => 'wc-checkout-draft',
170-
'type' => 'shop_order',
171-
]
172-
);
173-
174-
if ( $orders ) {
175-
foreach ( $orders as $order ) {
176-
$order->delete( true );
177-
$count ++;
178-
}
179-
}
180-
181-
if ( $batch_size === $count && function_exists( 'as_enqueue_async_action' ) ) {
182-
as_enqueue_async_action( 'woocommerce_cleanup_draft_orders' );
183-
}
184-
}
185109
}

0 commit comments

Comments
 (0)