1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
|
<?php /** * Traits for scheduling actions and dependencies. */
namespace Automattic\WooCommerce\Admin\Schedulers;
defined( 'ABSPATH' ) || exit;
/** * SchedulerTraits class. */ trait SchedulerTraits { /** * Action scheduler group. * * @var string|null */ public static $group = 'wc-admin-data';
/** * Queue instance. * * @var WC_Queue_Interface */ protected static $queue = null;
/** * Add all actions as hooks. */ public static function init() { foreach ( self::get_actions() as $action_name => $action_hook ) { $method = new \ReflectionMethod( static::class, $action_name ); add_action( $action_hook, array( static::class, 'do_action_or_reschedule' ), 10, $method->getNumberOfParameters() ); } }
/** * Get queue instance. * * @return WC_Queue_Interface */ public static function queue() { if ( is_null( self::$queue ) ) { self::$queue = WC()->queue(); }
return self::$queue; }
/** * Set queue instance. * * @param WC_Queue_Interface $queue Queue instance. */ public static function set_queue( $queue ) { self::$queue = $queue; }
/** * Gets the default scheduler actions for batching and scheduling actions. */ public static function get_default_scheduler_actions() { return array( 'schedule_action' => 'wc-admin_schedule_action_' . static::$name, 'queue_batches' => 'wc-admin_queue_batches_' . static::$name, ); }
/** * Gets the actions for this specific scheduler. * * @return array */ public static function get_scheduler_actions() { return array(); }
/** * Get all available scheduling actions. * Used to determine action hook names and clear events. */ public static function get_actions() { return array_merge( static::get_default_scheduler_actions(), static::get_scheduler_actions() ); }
/** * Get an action tag name from the action name. * * @param string $action_name The action name. * @return string|null */ public static function get_action( $action_name ) { $actions = static::get_actions(); return isset( $actions[ $action_name ] ) ? $actions[ $action_name ] : null; }
/** * Returns an array of actions and dependencies as key => value pairs. * * @return array */ public static function get_dependencies() { return array(); }
/** * Get dependencies associated with an action. * * @param string $action_name The action slug. * @return string|null */ public static function get_dependency( $action_name ) { $dependencies = static::get_dependencies(); return isset( $dependencies[ $action_name ] ) ? $dependencies[ $action_name ] : null; }
/** * Batch action size. */ public static function get_batch_sizes() { return array( 'queue_batches' => 100, ); }
/** * Returns the batch size for an action. * * @param string $action Single batch action name. * @return int Batch size. */ public static function get_batch_size( $action ) { $batch_sizes = static::get_batch_sizes(); $batch_size = isset( $batch_sizes[ $action ] ) ? $batch_sizes[ $action ] : 25;
/** * Filter the batch size for regenerating a report table. * * @param int $batch_size Batch size. * @param string $action Batch action name. */ return apply_filters( 'woocommerce_analytics_regenerate_batch_size', $batch_size, static::$name, $action ); }
/** * Flatten multidimensional arrays to store for scheduling. * * @param array $args Argument array. * @return string */ public static function flatten_args( $args ) { $flattened = array();
foreach ( $args as $arg ) { if ( is_array( $arg ) ) { $flattened[] = self::flatten_args( $arg ); } else { $flattened[] = $arg; } }
$string = '[' . implode( ',', $flattened ) . ']'; return $string; }
/** * Check if existing jobs exist for an action and arguments. * * @param string $action_name Action name. * @param array $args Array of arguments to pass to action. * @return bool */ public static function has_existing_jobs( $action_name, $args ) { $existing_jobs = self::queue()->search( array( 'status' => 'pending', 'per_page' => 1, 'claimed' => false, 'hook' => static::get_action( $action_name ), 'search' => self::flatten_args( $args ), 'group' => self::$group, ) );
if ( $existing_jobs ) { $existing_job = current( $existing_jobs );
// Bail out if there's a pending single action, or a pending scheduled actions. if ( ( static::get_action( $action_name ) === $existing_job->get_hook() ) || ( static::get_action( 'schedule_action' ) === $existing_job->get_hook() && in_array( self::get_action( $action_name ), $existing_job->get_args(), true ) ) ) { return true; } }
return false; }
/** * Get the next blocking job for an action. * * @param string $action_name Action name. * @return false|ActionScheduler_Action */ public static function get_next_blocking_job( $action_name ) { $dependency = self::get_dependency( $action_name );
if ( ! $dependency ) { return false; }
$blocking_jobs = self::queue()->search( array( 'status' => 'pending', 'orderby' => 'date', 'order' => 'DESC', 'per_page' => 1, 'search' => $dependency, // search is used instead of hook to find queued batch creation. 'group' => static::$group, ) );
return reset( $blocking_jobs ); }
/** * Check for blocking jobs and reschedule if any exist. */ public static function do_action_or_reschedule() { $action_hook = current_action(); $action_name = array_search( $action_hook, static::get_actions(), true ); $args = func_get_args();
// Check if any blocking jobs exist and schedule after they've completed // or schedule to run now if no blocking jobs exist. $blocking_job = static::get_next_blocking_job( $action_name ); if ( $blocking_job ) { $next_action_time = self::get_next_action_time( $blocking_job );
// Some actions, like single actions, don't have a next action time. if ( ! is_a( $next_action_time, 'DateTime' ) ) { $next_action_time = new \DateTime(); }
self::queue()->schedule_single( $next_action_time->getTimestamp() + 5, $action_hook, $args, static::$group ); } else { call_user_func_array( array( static::class, $action_name ), $args ); } }
/** * Get the DateTime for the next scheduled time an action should run. * This function allows backwards compatibility with Action Scheduler < v3.0. * * @param \ActionScheduler_Action $action Action. * @return DateTime|null */ public static function get_next_action_time( $action ) { if ( method_exists( $action->get_schedule(), 'get_next' ) ) { $after = new \DateTime(); $next_job_schedule = $action->get_schedule()->get_next( $after ); } else { $next_job_schedule = $action->get_schedule()->next(); }
return $next_job_schedule; }
/** * Schedule an action to run and check for dependencies. * * @param string $action_name Action name. * @param array $args Array of arguments to pass to action. */ public static function schedule_action( $action_name, $args = array() ) { // Check for existing jobs and bail if they already exist. if ( static::has_existing_jobs( $action_name, $args ) ) { return; }
$action_hook = static::get_action( $action_name ); if ( ! $action_hook ) { return; }
if ( // Skip scheduling if Action Scheduler tables have not been initialized. ! get_option( 'schema-ActionScheduler_StoreSchema' ) || apply_filters( 'woocommerce_analytics_disable_action_scheduling', false ) ) { call_user_func_array( array( static::class, $action_name ), $args ); return; }
self::queue()->schedule_single( time() + 5, $action_hook, $args, static::$group ); }
/** * Queue a large number of batch jobs, respecting the batch size limit. * Reduces a range of batches down to "single batch" jobs. * * @param int $range_start Starting batch number. * @param int $range_end Ending batch number. * @param string $single_batch_action Action to schedule for a single batch. * @param array $action_args Action arguments. * @return void */ public static function queue_batches( $range_start, $range_end, $single_batch_action, $action_args = array() ) { $batch_size = static::get_batch_size( 'queue_batches' ); $range_size = 1 + ( $range_end - $range_start ); $action_timestamp = time() + 5;
if ( $range_size > $batch_size ) { // If the current batch range is larger than a single batch, // split the range into $queue_batch_size chunks. $chunk_size = (int) ceil( $range_size / $batch_size );
for ( $i = 0; $i < $batch_size; $i++ ) { $batch_start = (int) ( $range_start + ( $i * $chunk_size ) ); $batch_end = (int) min( $range_end, $range_start + ( $chunk_size * ( $i + 1 ) ) - 1 );
if ( $batch_start > $range_end ) { return; }
self::schedule_action( 'queue_batches', array( $batch_start, $batch_end, $single_batch_action, $action_args ) ); } } else { // Otherwise, queue the single batches. for ( $i = $range_start; $i <= $range_end; $i++ ) { $batch_action_args = array_merge( array( $i ), $action_args ); self::schedule_action( $single_batch_action, $batch_action_args ); } } }
/** * Clears all queued actions. */ public static function clear_queued_actions() { if ( version_compare( \ActionScheduler_Versions::instance()->latest_version(), '3.0', '>=' ) ) { \ActionScheduler::store()->cancel_actions_by_group( static::$group ); } else { $actions = static::get_actions(); foreach ( $actions as $action ) { self::queue()->cancel_all( $action, null, static::$group ); } } } }
|