PluginProbe
WPFunnels – Funnel Builder for WooCommerce with Checkout & One Click Upsell / 2.8.16
WPFunnels – Funnel Builder for WooCommerce with Checkout & One Click Upsell v2.8.16
3.13.3 3.13.2 3.13.1 3.13.0 3.12.13 3.12.12 3.12.11 3.12.10 3.12.9 3.12.8 3.12.7 3.12.6 3.12.5 3.12.4 3.12.3 3.12.1 3.12.2 3.12.0 3.11.1 3.11.0 3.10.9 3.10.8 3.10.7 3.10.6 2.8.16 All 261 releases
wpfunnels / packages / action-scheduler / classes / data-stores / ActionScheduler_DBStore.php

ActionScheduler_DBStore.php in WPFunnels – Funnel Builder for WooCommerce with Checkout & One Click Upsell 2.8.16, at packages/action-scheduler/classes/data-stores/ActionScheduler_DBStore.php

913 lines 27.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * Class ActionScheduler_DBStore
5 *
6 * Action data table data store.
7 *
8 * @since 3.0.0
9 */
10 class ActionScheduler_DBStore extends ActionScheduler_Store {
11
12 /**
13 * Used to share information about the before_date property of claims internally.
14 *
15 * This is used in preference to passing the same information as a method param
16 * for backwards-compatibility reasons.
17 *
18 * @var DateTime|null
19 */
20 private $claim_before_date = null;
21
22 /**
23 * @var int
24 */
25 protected static $max_args_length = 8000;
26
27 /**
28 * @var int
29 */
30 protected static $max_index_length = 191;
31
32 /**
33 * Initialize the data store
34 *
35 * @codeCoverageIgnore
36 */
37 public function init() {
38 $table_maker = new ActionScheduler_StoreSchema();
39 $table_maker->init();
40 $table_maker->register_tables();
41 }
42
43 /**
44 * Save an action.
45 *
46 * @param ActionScheduler_Action $action Action object.
47 * @param DateTime $date Optional schedule date. Default null.
48 *
49 * @return int Action ID.
50 * @throws RuntimeException Throws exception when saving the action fails.
51 */
52 public function save_action( ActionScheduler_Action $action, \DateTime $date = null ) {
53 try {
54
55 $this->validate_action( $action );
56
57 /**
58 * @var \wpdb $wpdb
59 */
60 global $wpdb;
61 $data = array(
62 'hook' => $action->get_hook(),
63 'status' => ( $action->is_finished() ? self::STATUS_COMPLETE : self::STATUS_PENDING ),
64 'scheduled_date_gmt' => $this->get_scheduled_date_string( $action, $date ),
65 'scheduled_date_local' => $this->get_scheduled_date_string_local( $action, $date ),
66 'schedule' => serialize( $action->get_schedule() ), // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize
67 'group_id' => $this->get_group_id( $action->get_group() ),
68 );
69 $args = wp_json_encode( $action->get_args() );
70 if ( strlen( $args ) <= static::$max_index_length ) {
71 $data['args'] = $args;
72 } else {
73 $data['args'] = $this->hash_args( $args );
74 $data['extended_args'] = $args;
75 }
76
77 $table_name = ! empty( $wpdb->actionscheduler_actions ) ? $wpdb->actionscheduler_actions : $wpdb->prefix . 'actionscheduler_actions';
78 $wpdb->insert( $table_name, $data );
79 $action_id = $wpdb->insert_id;
80
81 if ( is_wp_error( $action_id ) ) {
82 throw new \RuntimeException( $action_id->get_error_message() );
83 } elseif ( empty( $action_id ) ) {
84 throw new \RuntimeException( $wpdb->last_error ? $wpdb->last_error : __( 'Database error.', 'woocommerce' ) );
85 }
86
87 do_action( 'action_scheduler_stored_action', $action_id );
88
89 return $action_id;
90 } catch ( \Exception $e ) {
91 /* translators: %s: error message */
92 throw new \RuntimeException( sprintf( __( 'Error saving action: %s', 'woocommerce' ), $e->getMessage() ), 0 );
93 }
94 }
95
96 /**
97 * Generate a hash from json_encoded $args using MD5 as this isn't for security.
98 *
99 * @param string $args JSON encoded action args.
100 * @return string
101 */
102 protected function hash_args( $args ) {
103 return md5( $args );
104 }
105
106 /**
107 * Get action args query param value from action args.
108 *
109 * @param array $args Action args.
110 * @return string
111 */
112 protected function get_args_for_query( $args ) {
113 $encoded = wp_json_encode( $args );
114 if ( strlen( $encoded ) <= static::$max_index_length ) {
115 return $encoded;
116 }
117 return $this->hash_args( $encoded );
118 }
119 /**
120 * Get a group's ID based on its name/slug.
121 *
122 * @param string $slug The string name of a group.
123 * @param bool $create_if_not_exists Whether to create the group if it does not already exist. Default, true - create the group.
124 *
125 * @return int The group's ID, if it exists or is created, or 0 if it does not exist and is not created.
126 */
127 protected function get_group_id( $slug, $create_if_not_exists = true ) {
128 if ( empty( $slug ) ) {
129 return 0;
130 }
131 /**
132 * @var \wpdb $wpdb
133 */
134 global $wpdb;
135 $group_id = (int) $wpdb->get_var( $wpdb->prepare( "SELECT group_id FROM {$wpdb->actionscheduler_groups} WHERE slug=%s", $slug ) );
136 if ( empty( $group_id ) && $create_if_not_exists ) {
137 $group_id = $this->create_group( $slug );
138 }
139
140 return $group_id;
141 }
142
143 /**
144 * Create an action group.
145 *
146 * @param string $slug Group slug.
147 *
148 * @return int Group ID.
149 */
150 protected function create_group( $slug ) {
151 /**
152 * @var \wpdb $wpdb
153 */
154 global $wpdb;
155 $wpdb->insert( $wpdb->actionscheduler_groups, array( 'slug' => $slug ) );
156
157 return (int) $wpdb->insert_id;
158 }
159
160 /**
161 * Retrieve an action.
162 *
163 * @param int $action_id Action ID.
164 *
165 * @return ActionScheduler_Action
166 */
167 public function fetch_action( $action_id ) {
168 /**
169 * @var \wpdb $wpdb
170 */
171 global $wpdb;
172 $data = $wpdb->get_row(
173 $wpdb->prepare(
174 "SELECT a.*, g.slug AS `group` FROM {$wpdb->actionscheduler_actions} a LEFT JOIN {$wpdb->actionscheduler_groups} g ON a.group_id=g.group_id WHERE a.action_id=%d",
175 $action_id
176 )
177 );
178
179 if ( empty( $data ) ) {
180 return $this->get_null_action();
181 }
182
183 if ( ! empty( $data->extended_args ) ) {
184 $data->args = $data->extended_args;
185 unset( $data->extended_args );
186 }
187
188 // Convert NULL dates to zero dates.
189 $date_fields = array(
190 'scheduled_date_gmt',
191 'scheduled_date_local',
192 'last_attempt_gmt',
193 'last_attempt_gmt',
194 );
195 foreach ( $date_fields as $date_field ) {
196 if ( is_null( $data->$date_field ) ) {
197 $data->$date_field = ActionScheduler_StoreSchema::DEFAULT_DATE;
198 }
199 }
200
201 try {
202 $action = $this->make_action_from_db_record( $data );
203 } catch ( ActionScheduler_InvalidActionException $exception ) {
204 do_action( 'action_scheduler_failed_fetch_action', $action_id, $exception );
205 return $this->get_null_action();
206 }
207
208 return $action;
209 }
210
211 /**
212 * Create a null action.
213 *
214 * @return ActionScheduler_NullAction
215 */
216 protected function get_null_action() {
217 return new ActionScheduler_NullAction();
218 }
219
220 /**
221 * Create an action from a database record.
222 *
223 * @param object $data Action database record.
224 *
225 * @return ActionScheduler_Action|ActionScheduler_CanceledAction|ActionScheduler_FinishedAction
226 */
227 protected function make_action_from_db_record( $data ) {
228
229 $hook = $data->hook;
230 $args = json_decode( $data->args, true );
231 $schedule = unserialize( $data->schedule ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_unserialize
232
233 $this->validate_args( $args, $data->action_id );
234 $this->validate_schedule( $schedule, $data->action_id );
235
236 if ( empty( $schedule ) ) {
237 $schedule = new ActionScheduler_NullSchedule();
238 }
239 $group = $data->group ? $data->group : '';
240
241 return ActionScheduler::factory()->get_stored_action( $data->status, $data->hook, $args, $schedule, $group );
242 }
243
244 /**
245 * Returns the SQL statement to query (or count) actions.
246 *
247 * @since x.x.x $query['status'] accepts array of statuses instead of a single status.
248 *
249 * @param array $query Filtering options.
250 * @param string $select_or_count Whether the SQL should select and return the IDs or just the row count.
251 *
252 * @return string SQL statement already properly escaped.
253 * @throws InvalidArgumentException If the query is invalid.
254 */
255 protected function get_query_actions_sql( array $query, $select_or_count = 'select' ) {
256
257 if ( ! in_array( $select_or_count, array( 'select', 'count' ), true ) ) {
258 throw new InvalidArgumentException( __( 'Invalid value for select or count parameter. Cannot query actions.', 'woocommerce' ) );
259 }
260
261 $query = wp_parse_args(
262 $query,
263 array(
264 'hook' => '',
265 'args' => null,
266 'date' => null,
267 'date_compare' => '<=',
268 'modified' => null,
269 'modified_compare' => '<=',
270 'group' => '',
271 'status' => '',
272 'claimed' => null,
273 'per_page' => 5,
274 'offset' => 0,
275 'orderby' => 'date',
276 'order' => 'ASC',
277 )
278 );
279
280 /**
281 * @var \wpdb $wpdb
282 */
283 global $wpdb;
284 $sql = ( 'count' === $select_or_count ) ? 'SELECT count(a.action_id)' : 'SELECT a.action_id';
285 $sql .= " FROM {$wpdb->actionscheduler_actions} a";
286 $sql_params = array();
287
288 if ( ! empty( $query['group'] ) || 'group' === $query['orderby'] ) {
289 $sql .= " LEFT JOIN {$wpdb->actionscheduler_groups} g ON g.group_id=a.group_id";
290 }
291
292 $sql .= ' WHERE 1=1';
293
294 if ( ! empty( $query['group'] ) ) {
295 $sql .= ' AND g.slug=%s';
296 $sql_params[] = $query['group'];
297 }
298
299 if ( $query['hook'] ) {
300 $sql .= ' AND a.hook=%s';
301 $sql_params[] = $query['hook'];
302 }
303 if ( ! is_null( $query['args'] ) ) {
304 $sql .= ' AND a.args=%s';
305 $sql_params[] = $this->get_args_for_query( $query['args'] );
306 }
307
308 if ( $query['status'] ) {
309 $statuses = (array) $query['status'];
310 $placeholders = array_fill( 0, count( $statuses ), '%s' );
311 $sql .= ' AND a.status IN (' . join( ', ', $placeholders ) . ')';
312 $sql_params = array_merge( $sql_params, array_values( $statuses ) );
313 }
314
315 if ( $query['date'] instanceof \DateTime ) {
316 $date = clone $query['date'];
317 $date->setTimezone( new \DateTimeZone( 'UTC' ) );
318 $date_string = $date->format( 'Y-m-d H:i:s' );
319 $comparator = $this->validate_sql_comparator( $query['date_compare'] );
320 $sql .= " AND a.scheduled_date_gmt $comparator %s";
321 $sql_params[] = $date_string;
322 }
323
324 if ( $query['modified'] instanceof \DateTime ) {
325 $modified = clone $query['modified'];
326 $modified->setTimezone( new \DateTimeZone( 'UTC' ) );
327 $date_string = $modified->format( 'Y-m-d H:i:s' );
328 $comparator = $this->validate_sql_comparator( $query['modified_compare'] );
329 $sql .= " AND a.last_attempt_gmt $comparator %s";
330 $sql_params[] = $date_string;
331 }
332
333 if ( true === $query['claimed'] ) {
334 $sql .= ' AND a.claim_id != 0';
335 } elseif ( false === $query['claimed'] ) {
336 $sql .= ' AND a.claim_id = 0';
337 } elseif ( ! is_null( $query['claimed'] ) ) {
338 $sql .= ' AND a.claim_id = %d';
339 $sql_params[] = $query['claimed'];
340 }
341
342 if ( ! empty( $query['search'] ) ) {
343 $sql .= ' AND (a.hook LIKE %s OR (a.extended_args IS NULL AND a.args LIKE %s) OR a.extended_args LIKE %s';
344 for ( $i = 0; $i < 3; $i++ ) {
345 $sql_params[] = sprintf( '%%%s%%', $query['search'] );
346 }
347
348 $search_claim_id = (int) $query['search'];
349 if ( $search_claim_id ) {
350 $sql .= ' OR a.claim_id = %d';
351 $sql_params[] = $search_claim_id;
352 }
353
354 $sql .= ')';
355 }
356
357 if ( 'select' === $select_or_count ) {
358 if ( 'ASC' === strtoupper( $query['order'] ) ) {
359 $order = 'ASC';
360 } else {
361 $order = 'DESC';
362 }
363 switch ( $query['orderby'] ) {
364 case 'hook':
365 $sql .= " ORDER BY a.hook $order";
366 break;
367 case 'group':
368 $sql .= " ORDER BY g.slug $order";
369 break;
370 case 'modified':
371 $sql .= " ORDER BY a.last_attempt_gmt $order";
372 break;
373 case 'none':
374 break;
375 case 'action_id':
376 $sql .= " ORDER BY a.action_id $order";
377 break;
378 case 'date':
379 default:
380 $sql .= " ORDER BY a.scheduled_date_gmt $order";
381 break;
382 }
383
384 if ( $query['per_page'] > 0 ) {
385 $sql .= ' LIMIT %d, %d';
386 $sql_params[] = $query['offset'];
387 $sql_params[] = $query['per_page'];
388 }
389 }
390
391 if ( ! empty( $sql_params ) ) {
392 $sql = $wpdb->prepare( $sql, $sql_params ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
393 }
394
395 return $sql;
396 }
397
398 /**
399 * Query for action count or list of action IDs.
400 *
401 * @since x.x.x $query['status'] accepts array of statuses instead of a single status.
402 *
403 * @see ActionScheduler_Store::query_actions for $query arg usage.
404 *
405 * @param array $query Query filtering options.
406 * @param string $query_type Whether to select or count the results. Defaults to select.
407 *
408 * @return string|array|null The IDs of actions matching the query. Null on failure.
409 */
410 public function query_actions( $query = array(), $query_type = 'select' ) {
411 /**
412 * @var wpdb $wpdb
413 */
414 global $wpdb;
415
416 $sql = $this->get_query_actions_sql( $query, $query_type );
417
418 return ( 'count' === $query_type ) ? $wpdb->get_var( $sql ) : $wpdb->get_col( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.NoSql, WordPress.DB.DirectDatabaseQuery.NoCaching
419 }
420
421 /**
422 * Get a count of all actions in the store, grouped by status.
423 *
424 * @return array Set of 'status' => int $count pairs for statuses with 1 or more actions of that status.
425 */
426 public function action_counts() {
427 global $wpdb;
428
429 $sql = "SELECT a.status, count(a.status) as 'count'";
430 $sql .= " FROM {$wpdb->actionscheduler_actions} a";
431 $sql .= ' GROUP BY a.status';
432
433 $actions_count_by_status = array();
434 $action_stati_and_labels = $this->get_status_labels();
435
436 foreach ( $wpdb->get_results( $sql ) as $action_data ) { // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
437 // Ignore any actions with invalid status.
438 if ( array_key_exists( $action_data->status, $action_stati_and_labels ) ) {
439 $actions_count_by_status[ $action_data->status ] = $action_data->count;
440 }
441 }
442
443 return $actions_count_by_status;
444 }
445
446 /**
447 * Cancel an action.
448 *
449 * @param int $action_id Action ID.
450 *
451 * @return void
452 * @throws \InvalidArgumentException If the action update failed.
453 */
454 public function cancel_action( $action_id ) {
455 /**
456 * @var \wpdb $wpdb
457 */
458 global $wpdb;
459
460 $updated = $wpdb->update(
461 $wpdb->actionscheduler_actions,
462 array( 'status' => self::STATUS_CANCELED ),
463 array( 'action_id' => $action_id ),
464 array( '%s' ),
465 array( '%d' )
466 );
467 if ( empty( $updated ) ) {
468 /* translators: %s: action ID */
469 throw new \InvalidArgumentException( sprintf( __( 'Unidentified action %s', 'woocommerce' ), $action_id ) );
470 }
471 do_action( 'action_scheduler_canceled_action', $action_id );
472 }
473
474 /**
475 * Cancel pending actions by hook.
476 *
477 * @since 3.0.0
478 *
479 * @param string $hook Hook name.
480 *
481 * @return void
482 */
483 public function cancel_actions_by_hook( $hook ) {
484 $this->bulk_cancel_actions( array( 'hook' => $hook ) );
485 }
486
487 /**
488 * Cancel pending actions by group.
489 *
490 * @param string $group Group slug.
491 *
492 * @return void
493 */
494 public function cancel_actions_by_group( $group ) {
495 $this->bulk_cancel_actions( array( 'group' => $group ) );
496 }
497
498 /**
499 * Bulk cancel actions.
500 *
501 * @since 3.0.0
502 *
503 * @param array $query_args Query parameters.
504 */
505 protected function bulk_cancel_actions( $query_args ) {
506 /**
507 * @var \wpdb $wpdb
508 */
509 global $wpdb;
510
511 if ( ! is_array( $query_args ) ) {
512 return;
513 }
514
515 // Don't cancel actions that are already canceled.
516 if ( isset( $query_args['status'] ) && self::STATUS_CANCELED === $query_args['status'] ) {
517 return;
518 }
519
520 $action_ids = true;
521 $query_args = wp_parse_args(
522 $query_args,
523 array(
524 'per_page' => 1000,
525 'status' => self::STATUS_PENDING,
526 'orderby' => 'action_id',
527 )
528 );
529
530 while ( $action_ids ) {
531 $action_ids = $this->query_actions( $query_args );
532 if ( empty( $action_ids ) ) {
533 break;
534 }
535
536 $format = array_fill( 0, count( $action_ids ), '%d' );
537 $query_in = '(' . implode( ',', $format ) . ')';
538 $parameters = $action_ids;
539 array_unshift( $parameters, self::STATUS_CANCELED );
540
541 $wpdb->query(
542 $wpdb->prepare(
543 "UPDATE {$wpdb->actionscheduler_actions} SET status = %s WHERE action_id IN {$query_in}", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
544 $parameters
545 )
546 );
547
548 do_action( 'action_scheduler_bulk_cancel_actions', $action_ids );
549 }
550 }
551
552 /**
553 * Delete an action.
554 *
555 * @param int $action_id Action ID.
556 * @throws \InvalidArgumentException If the action deletion failed.
557 */
558 public function delete_action( $action_id ) {
559 /**
560 * @var \wpdb $wpdb
561 */
562 global $wpdb;
563 $deleted = $wpdb->delete( $wpdb->actionscheduler_actions, array( 'action_id' => $action_id ), array( '%d' ) );
564 if ( empty( $deleted ) ) {
565 throw new \InvalidArgumentException( sprintf( __( 'Unidentified action %s', 'woocommerce' ), $action_id ) ); //phpcs:ignore WordPress.WP.I18n.MissingTranslatorsComment
566 }
567 do_action( 'action_scheduler_deleted_action', $action_id );
568 }
569
570 /**
571 * Get the schedule date for an action.
572 *
573 * @param string $action_id Action ID.
574 *
575 * @return \DateTime The local date the action is scheduled to run, or the date that it ran.
576 */
577 public function get_date( $action_id ) {
578 $date = $this->get_date_gmt( $action_id );
579 ActionScheduler_TimezoneHelper::set_local_timezone( $date );
580 return $date;
581 }
582
583 /**
584 * Get the GMT schedule date for an action.
585 *
586 * @param int $action_id Action ID.
587 *
588 * @throws \InvalidArgumentException If action cannot be identified.
589 * @return \DateTime The GMT date the action is scheduled to run, or the date that it ran.
590 */
591 protected function get_date_gmt( $action_id ) {
592 /**
593 * @var \wpdb $wpdb
594 */
595 global $wpdb;
596 $record = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->actionscheduler_actions} WHERE action_id=%d", $action_id ) );
597 if ( empty( $record ) ) {
598 throw new \InvalidArgumentException( sprintf( __( 'Unidentified action %s', 'woocommerce' ), $action_id ) ); //phpcs:ignore WordPress.WP.I18n.MissingTranslatorsComment
599 }
600 if ( self::STATUS_PENDING === $record->status ) {
601 return as_get_datetime_object( $record->scheduled_date_gmt );
602 } else {
603 return as_get_datetime_object( $record->last_attempt_gmt );
604 }
605 }
606
607 /**
608 * Stake a claim on actions.
609 *
610 * @param int $max_actions Maximum number of action to include in claim.
611 * @param \DateTime $before_date Jobs must be schedule before this date. Defaults to now.
612 * @param array $hooks Hooks to filter for.
613 * @param string $group Group to filter for.
614 *
615 * @return ActionScheduler_ActionClaim
616 */
617 public function stake_claim( $max_actions = 10, \DateTime $before_date = null, $hooks = array(), $group = '' ) {
618 $claim_id = $this->generate_claim_id();
619
620 $this->claim_before_date = $before_date;
621 $this->claim_actions( $claim_id, $max_actions, $before_date, $hooks, $group );
622 $action_ids = $this->find_actions_by_claim_id( $claim_id );
623 $this->claim_before_date = null;
624
625 return new ActionScheduler_ActionClaim( $claim_id, $action_ids );
626 }
627
628 /**
629 * Generate a new action claim.
630 *
631 * @return int Claim ID.
632 */
633 protected function generate_claim_id() {
634 /**
635 * @var \wpdb $wpdb
636 */
637 global $wpdb;
638 $now = as_get_datetime_object();
639 $wpdb->insert( $wpdb->actionscheduler_claims, array( 'date_created_gmt' => $now->format( 'Y-m-d H:i:s' ) ) );
640
641 return $wpdb->insert_id;
642 }
643
644 /**
645 * Mark actions claimed.
646 *
647 * @param string $claim_id Claim Id.
648 * @param int $limit Number of action to include in claim.
649 * @param \DateTime $before_date Should use UTC timezone.
650 * @param array $hooks Hooks to filter for.
651 * @param string $group Group to filter for.
652 *
653 * @return int The number of actions that were claimed.
654 * @throws \InvalidArgumentException Throws InvalidArgumentException if group doesn't exist.
655 * @throws \RuntimeException Throws RuntimeException if unable to claim action.
656 */
657 protected function claim_actions( $claim_id, $limit, \DateTime $before_date = null, $hooks = array(), $group = '' ) {
658 /**
659 * @var \wpdb $wpdb
660 */
661 global $wpdb;
662
663 $now = as_get_datetime_object();
664 $date = is_null( $before_date ) ? $now : clone $before_date;
665
666 // can't use $wpdb->update() because of the <= condition.
667 $update = "UPDATE {$wpdb->actionscheduler_actions} SET claim_id=%d, last_attempt_gmt=%s, last_attempt_local=%s";
668 $params = array(
669 $claim_id,
670 $now->format( 'Y-m-d H:i:s' ),
671 current_time( 'mysql' ),
672 );
673
674 $where = 'WHERE claim_id = 0 AND scheduled_date_gmt <= %s AND status=%s';
675 $params[] = $date->format( 'Y-m-d H:i:s' );
676 $params[] = self::STATUS_PENDING;
677
678 if ( ! empty( $hooks ) ) {
679 $placeholders = array_fill( 0, count( $hooks ), '%s' );
680 $where .= ' AND hook IN (' . join( ', ', $placeholders ) . ')';
681 $params = array_merge( $params, array_values( $hooks ) );
682 }
683
684 if ( ! empty( $group ) ) {
685
686 $group_id = $this->get_group_id( $group, false );
687
688 // throw exception if no matching group found, this matches ActionScheduler_wpPostStore's behaviour.
689 if ( empty( $group_id ) ) {
690 /* translators: %s: group name */
691 throw new InvalidArgumentException( sprintf( __( 'The group "%s" does not exist.', 'woocommerce' ), $group ) );
692 }
693
694 $where .= ' AND group_id = %d';
695 $params[] = $group_id;
696 }
697
698 /**
699 * Sets the order-by clause used in the action claim query.
700 *
701 * @since x.x.x
702 *
703 * @param string $order_by_sql
704 */
705 $order = apply_filters( 'action_scheduler_claim_actions_order_by', 'ORDER BY attempts ASC, scheduled_date_gmt ASC, action_id ASC' );
706 $params[] = $limit;
707
708 $sql = $wpdb->prepare( "{$update} {$where} {$order} LIMIT %d", $params ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders
709 $rows_affected = $wpdb->query( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
710 if ( false === $rows_affected ) {
711 throw new \RuntimeException( __( 'Unable to claim actions. Database error.', 'woocommerce' ) );
712 }
713
714 return (int) $rows_affected;
715 }
716
717 /**
718 * Get the number of active claims.
719 *
720 * @return int
721 */
722 public function get_claim_count() {
723 global $wpdb;
724
725 $sql = "SELECT COUNT(DISTINCT claim_id) FROM {$wpdb->actionscheduler_actions} WHERE claim_id != 0 AND status IN ( %s, %s)";
726 $sql = $wpdb->prepare( $sql, array( self::STATUS_PENDING, self::STATUS_RUNNING ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
727
728 return (int) $wpdb->get_var( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
729 }
730
731 /**
732 * Return an action's claim ID, as stored in the claim_id column.
733 *
734 * @param string $action_id Action ID.
735 * @return mixed
736 */
737 public function get_claim_id( $action_id ) {
738 /**
739 * @var \wpdb $wpdb
740 */
741 global $wpdb;
742
743 $sql = "SELECT claim_id FROM {$wpdb->actionscheduler_actions} WHERE action_id=%d";
744 $sql = $wpdb->prepare( $sql, $action_id ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
745
746 return (int) $wpdb->get_var( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
747 }
748
749 /**
750 * Retrieve the action IDs of action in a claim.
751 *
752 * @param int $claim_id Claim ID.
753 * @return int[]
754 */
755 public function find_actions_by_claim_id( $claim_id ) {
756 /**
757 * @var \wpdb $wpdb
758 */
759 global $wpdb;
760
761 $action_ids = array();
762 $before_date = isset( $this->claim_before_date ) ? $this->claim_before_date : as_get_datetime_object();
763 $cut_off = $before_date->format( 'Y-m-d H:i:s' );
764
765 $sql = $wpdb->prepare(
766 "SELECT action_id, scheduled_date_gmt FROM {$wpdb->actionscheduler_actions} WHERE claim_id = %d",
767 $claim_id
768 );
769
770 // Verify that the scheduled date for each action is within the expected bounds (in some unusual
771 // cases, we cannot depend on MySQL to honor all of the WHERE conditions we specify).
772 foreach ( $wpdb->get_results( $sql ) as $claimed_action ) { // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
773 if ( $claimed_action->scheduled_date_gmt <= $cut_off ) {
774 $action_ids[] = absint( $claimed_action->action_id );
775 }
776 }
777
778 return $action_ids;
779 }
780
781 /**
782 * Release actions from a claim and delete the claim.
783 *
784 * @param ActionScheduler_ActionClaim $claim Claim object.
785 */
786 public function release_claim( ActionScheduler_ActionClaim $claim ) {
787 /**
788 * @var \wpdb $wpdb
789 */
790 global $wpdb;
791 $wpdb->update( $wpdb->actionscheduler_actions, array( 'claim_id' => 0 ), array( 'claim_id' => $claim->get_id() ), array( '%d' ), array( '%d' ) );
792 $wpdb->delete( $wpdb->actionscheduler_claims, array( 'claim_id' => $claim->get_id() ), array( '%d' ) );
793 }
794
795 /**
796 * Remove the claim from an action.
797 *
798 * @param int $action_id Action ID.
799 *
800 * @return void
801 */
802 public function unclaim_action( $action_id ) {
803 /**
804 * @var \wpdb $wpdb
805 */
806 global $wpdb;
807 $wpdb->update(
808 $wpdb->actionscheduler_actions,
809 array( 'claim_id' => 0 ),
810 array( 'action_id' => $action_id ),
811 array( '%s' ),
812 array( '%d' )
813 );
814 }
815
816 /**
817 * Mark an action as failed.
818 *
819 * @param int $action_id Action ID.
820 * @throws \InvalidArgumentException Throw an exception if action was not updated.
821 */
822 public function mark_failure( $action_id ) {
823 /**
824 * @var \wpdb $wpdb
825 */
826 global $wpdb;
827 $updated = $wpdb->update(
828 $wpdb->actionscheduler_actions,
829 array( 'status' => self::STATUS_FAILED ),
830 array( 'action_id' => $action_id ),
831 array( '%s' ),
832 array( '%d' )
833 );
834 if ( empty( $updated ) ) {
835 throw new \InvalidArgumentException( sprintf( __( 'Unidentified action %s', 'woocommerce' ), $action_id ) ); //phpcs:ignore WordPress.WP.I18n.MissingTranslatorsComment
836 }
837 }
838
839 /**
840 * Add execution message to action log.
841 *
842 * @param int $action_id Action ID.
843 *
844 * @return void
845 */
846 public function log_execution( $action_id ) {
847 /**
848 * @var \wpdb $wpdb
849 */
850 global $wpdb;
851
852 $sql = "UPDATE {$wpdb->actionscheduler_actions} SET attempts = attempts+1, status=%s, last_attempt_gmt = %s, last_attempt_local = %s WHERE action_id = %d";
853 $sql = $wpdb->prepare( $sql, self::STATUS_RUNNING, current_time( 'mysql', true ), current_time( 'mysql' ), $action_id ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
854 $wpdb->query( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
855 }
856
857 /**
858 * Mark an action as complete.
859 *
860 * @param int $action_id Action ID.
861 *
862 * @return void
863 * @throws \InvalidArgumentException Throw an exception if action was not updated.
864 */
865 public function mark_complete( $action_id ) {
866 /**
867 * @var \wpdb $wpdb
868 */
869 global $wpdb;
870 $updated = $wpdb->update(
871 $wpdb->actionscheduler_actions,
872 array(
873 'status' => self::STATUS_COMPLETE,
874 'last_attempt_gmt' => current_time( 'mysql', true ),
875 'last_attempt_local' => current_time( 'mysql' ),
876 ),
877 array( 'action_id' => $action_id ),
878 array( '%s' ),
879 array( '%d' )
880 );
881 if ( empty( $updated ) ) {
882 throw new \InvalidArgumentException( sprintf( __( 'Unidentified action %s', 'woocommerce' ), $action_id ) ); //phpcs:ignore WordPress.WP.I18n.MissingTranslatorsComment
883 }
884 }
885
886 /**
887 * Get an action's status.
888 *
889 * @param int $action_id Action ID.
890 *
891 * @return string
892 * @throws \InvalidArgumentException Throw an exception if not status was found for action_id.
893 * @throws \RuntimeException Throw an exception if action status could not be retrieved.
894 */
895 public function get_status( $action_id ) {
896 /**
897 * @var \wpdb $wpdb
898 */
899 global $wpdb;
900 $sql = "SELECT status FROM {$wpdb->actionscheduler_actions} WHERE action_id=%d";
901 $sql = $wpdb->prepare( $sql, $action_id ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
902 $status = $wpdb->get_var( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
903
904 if ( null === $status ) {
905 throw new \InvalidArgumentException( __( 'Invalid action ID. No status found.', 'woocommerce' ) );
906 } elseif ( empty( $status ) ) {
907 throw new \RuntimeException( __( 'Unknown status found for action.', 'woocommerce' ) );
908 } else {
909 return $status;
910 }
911 }
912 }
913