PluginProbe ʕ •ᴥ•ʔ
Pods – Custom Content Types and Fields / 2.8.23.4
Pods – Custom Content Types and Fields v2.8.23.4
2.7.31.4 2.8.23.5 2.9.19.5 3.0.10.5 3.1.4.3 3.2.8.4 3.3.9.2 2.8.23.4 2.9.19.4 3.0.10.4 3.1.4.2 3.2.8.3 3.3.9.1 trunk 1.14.8 2.7.31.3 2.8.23.3 2.9.19.3 3.0.10.3 3.1.4.1 3.2.0 3.2.1 3.2.1.1 3.2.2 3.2.4 3.2.5 3.2.6 3.2.7 3.2.7.1 3.2.8 3.2.8.1 3.2.8.2 3.3.0 3.3.1 3.3.2 3.3.3 3.3.4 3.3.5 3.3.6 3.3.7 3.3.8 3.3.9
pods / tribe-common / src / Tribe / Process / Queue.php
pods / tribe-common / src / Tribe / Process Last commit date
Handler.php 2 weeks ago Post_Thumbnail_Setter.php 2 weeks ago Queue.php 2 weeks ago Tester.php 2 weeks ago
Queue.php
1130 lines
1 <?php
2
3 /**
4 * Class Tribe__Process__Queue
5 *
6 * @since 4.7.12
7 * @since 4.9.5 Removed dependency on `WP_Background_Process` class.
8 *
9 * The base class to process queues asynchronously.
10 */
11 abstract class Tribe__Process__Queue extends Tribe__Process__Handler {
12
13 /**
14 * A constant to allow some "sugar" while using the processing system.
15 * Returning `false` to indicate the successful processing of an item might
16 * not be intuitive.
17 */
18 const ITEM_DONE = false;
19
20 /**
21 * The default action name.
22 *
23 * @var string
24 */
25 protected $action = 'background_process';
26
27 /**
28 * Start time of current process.
29 *
30 * @var int
31 */
32 protected $start_time = 0;
33
34 /**
35 * The process Cron_hook_identifier.
36 *
37 * @var mixed
38 */
39 protected $healthcheck_cron_hook_id;
40
41 /**
42 * The process cron interval identifier.
43 *
44 * @var mixed
45 */
46 protected $healthcheck_cron_interval_id;
47
48 /**
49 * @var string The common identified prefix to all our async process handlers.
50 */
51 protected $prefix = 'tribe_queue';
52
53 /**
54 * @var string The base that should be used to build the queue id.
55 */
56 protected $id_base;
57
58 /**
59 * @var string The queue unique identifier
60 */
61 protected $id;
62
63 /**
64 * @var int How many items this instance processed.
65 */
66 protected $done = 0;
67
68 /**
69 * @var int
70 */
71 protected $original_batch_count = 0;
72
73 /**
74 * @var int The maximum size of a fragment in bytes.
75 */
76 protected $max_frag_size;
77
78 /**
79 * @var bool Whether the current handling is sync or not.
80 */
81 protected $doing_sync = false;
82
83 /**
84 * @var bool Whether the queue `save` method was already called or not.
85 */
86 protected $did_save = false;
87
88 /**
89 * @var string The batch key used by the queue.
90 */
91 protected $batch_key;
92
93 /**
94 * An instance of the feature detection abstraction object.
95 *
96 * @var Tribe__Feature_Detection
97 */
98 protected $feature_detection;
99
100 /**
101 * The default lock time for a queued process.
102 *
103 * @var int
104 */
105 protected $queue_lock_time = 60;
106
107 /**
108 * The amount, in seconds, to check on the queue health.
109 *
110 * @var int
111 */
112 protected $healthcheck_cron_interval = 5;
113
114 /**
115 * Tribe__Process__Queue constructor.
116 *
117 * @since 4.7.12
118 * @since 4.9.5 Pulled method code from the `WP_Background_Process` class.
119 */
120 public function __construct() {
121 $class = get_class( $this );
122 $this->action = call_user_func( [ $class, 'action' ] );
123 $this->feature_detection = tribe( 'feature-detection' );
124
125 parent::__construct();
126
127 $this->healthcheck_cron_hook_id = $this->identifier . '_cron';
128 $this->healthcheck_cron_interval_id = $this->identifier . '_cron_interval';
129
130 add_action( $this->healthcheck_cron_hook_id, [ $this, 'handle_cron_healthcheck' ] );
131 add_filter( 'cron_schedules', [ $this, 'schedule_cron_healthcheck' ] );
132
133 /*
134 * This object might have been built while processing crons so
135 * we hook on the the object cron identifier to handle the task
136 * if the cron-triggered action ever fires.
137 */
138 add_action( $this->identifier, [ $this, 'maybe_handle' ] );
139 }
140
141 /**
142 * Stops a queue that might be running.
143 *
144 * The queue process results are not rolled back (e.g. 200 posts to create, stopped
145 * after 50, those 50 posts will persist).
146 *
147 * @since 4.7.12
148 *
149 * @param string $queue_id The unique identifier of the queue that should be stopped.
150 *
151 * @see Tribe__Process__Queue::save() to get the queue unique id.
152 *
153 * @return bool Whether the queue was correctly stopped, and its information
154 * deleted, or not.
155 */
156 public static function stop_queue( $queue_id ) {
157 $meta = (array) get_transient( $queue_id . '_meta' );
158 delete_transient( $queue_id . '_meta' );
159
160 if ( ! empty( $meta['identifier'] ) ) {
161 delete_transient( $meta['identifier'] . '_process_lock' );
162 }
163
164 return delete_option( $queue_id );
165 }
166
167 /**
168 * Whether a queue process is stuck or not.
169 *
170 * A queue process that has not been doing anything for an amount
171 * of time is considered "stuck".
172 *
173 * @since 4.7.18
174 *
175 * @param string $queue_id The queue process unique identifier.
176 *
177 * @return bool
178 */
179 public static function is_stuck( $queue_id ) {
180 $queue_status = self::get_status_of( $queue_id );
181 $is_stuck = false;
182
183 /**
184 * Filters the maximum allowed time a queue process can go without updates
185 * before being considered stuck.
186 *
187 * @since 4.7.18
188 *
189 * @param int $time_limit A value in seconds, defaults to 5'.
190 */
191 $limit = (float) apply_filters( 'tribe_process_queue_time_limit', 300 );
192
193 if ( ! empty( $queue_status['last_update'] ) && is_numeric( $queue_status['last_update'] ) ) {
194 $is_stuck = time() - (int) $queue_status['last_update'] > $limit;
195 } else {
196 $queue_status['last_update'] = time();
197 set_transient( $queue_id . '_meta', $queue_status->to_array(), DAY_IN_SECONDS );
198 }
199
200 /**
201 * Filters whether a queue is considered "stuck" or not.
202 *
203 * @since 4.7.18
204 *
205 * @param bool $is_stuck
206 * @param string $queue_id
207 * @param Tribe__Data $queue_status
208 */
209 return apply_filters( 'tribe_process_queue_is_stuck', $is_stuck, $queue_id, $queue_status );
210 }
211
212 /**
213 * Returns a queue status and information.
214 *
215 * @since 4.7.12
216 *
217 * @param string $queue_id
218 *
219 * @return Tribe__Data An object containing information about the queue.
220 *
221 * @see Tribe__Process__Queue::save() to get the queue unique id.
222 */
223 public static function get_status_of( $queue_id ) {
224 $meta = (array) get_transient( $queue_id . '_meta' );
225 $data = [
226 'identifier' => $queue_id,
227 'done' => (int) Tribe__Utils__Array::get( $meta, 'done', 0 ),
228 'total' => (int) Tribe__Utils__Array::get( $meta, 'total', 0 ),
229 'fragments' => (int) Tribe__Utils__Array::get( $meta, 'fragments', 0 ),
230 'last_update' => (int) Tribe__Utils__Array::get( $meta, 'last_update', false ),
231 ];
232
233 return new Tribe__Data( $data, 0 );
234 }
235
236 /**
237 * Deletes all queues for a specific action.
238 *
239 * @since 4.7.19
240 *
241 * @param string $action The action (prefix) of the queues to delete.
242 *
243 * @return int The number of delete queues.
244 */
245 public static function delete_all_queues( $action ) {
246 global $wpdb;
247
248 $action = $wpdb->esc_like( 'tribe_queue_' . $action ) . '%';
249
250 $queues = $wpdb->get_col( $wpdb->prepare( "
251 SELECT DISTINCT(option_name)
252 FROM {$wpdb->options}
253 WHERE option_name LIKE %s
254 ", $action ) );
255
256 if ( empty( $queues ) ) {
257 return 0;
258 }
259
260 $deleted = 0;
261
262 foreach ( $queues as $queue ) {
263 $deleted ++;
264 self::delete_queue( $queue );
265 }
266
267 return $deleted;
268 }
269
270 /**
271 * {@inheritdoc}
272 */
273 public function delete( $key ) {
274 self::delete_queue( $key );
275
276 return $this;
277 }
278
279 /**
280 * Deletes a queue batch(es) and meta information.
281 *
282 * @since 4.7.18
283 *
284 * @param string $key
285 */
286 public static function delete_queue( $key ) {
287 global $wpdb;
288
289 $meta_key = $key . '_meta';
290
291 $key = $wpdb->esc_like( $key ) . '%';
292
293 $wpdb->query( $wpdb->prepare( "
294 DELETE
295 FROM {$wpdb->options}
296 WHERE option_name LIKE %s
297 ", $key ) );
298
299 delete_transient( $meta_key );
300 }
301
302 /**
303 * Upates the queue and meta data for the process.
304 *
305 * @since 4.7.12
306 * @since 4.9.5 Pulled method from the `WP_Background_Process` class.
307 *
308 * @param string $key The key of the data to save.
309 * @param array $data The data to save.
310 *
311 * @return $this This process instance.
312 */
313 public function update( $key, $data ) {
314 $meta_key = $this->get_meta_key( $key );
315 $meta = (array) get_transient( $meta_key );
316 $done = $this->original_batch_count - count( $data );
317
318 $update_data = array_merge( $meta, [
319 'done' => $meta['done'] + $done,
320 'last_update' => time(),
321 ] );
322
323 /**
324 * Filters the information that will be updated in the database for this queue type.
325 *
326 * @since 4.7.12
327 *
328 * @param array $update_data
329 * @param self $this
330 */
331 $update_data = apply_filters( "tribe_process_queue_{$this->identifier}_update_data", $update_data, $this );
332
333 set_transient( $meta_key, $update_data, DAY_IN_SECONDS );
334
335 if ( ! empty( $data ) ) {
336 update_option( $key, $data );
337 }
338
339 return $this;
340 }
341
342 /**
343 * Returns the name of the transient that will store the queue meta information
344 * for the specific key.
345 *
346 * @since 4.7.12
347 *
348 * @param string $key
349 *
350 * @return string
351 */
352 public function get_meta_key( $key ) {
353 $key = preg_replace( '/^(.*)_\\d+$/', '$1', $key );
354
355 return $key . '_meta';
356 }
357
358 /**
359 * {@inheritdoc}
360 */
361 public function save() {
362 $key = $this->generate_key();
363
364 $fragments_count = $this->save_split_data( $key, $this->data );
365
366 $save_data = [
367 'identifier' => $this->identifier,
368 'done' => 0,
369 'total' => count( $this->data ),
370 'fragments' => $fragments_count,
371 'last_update' => time(),
372 ];
373
374 /**
375 * Filters the information that will be saved to the database for this queue type.
376 *
377 * @since 4.7.12
378 *
379 * @param array $save_data
380 * @param self $this
381 */
382 $save_data = apply_filters( "tribe_process_queue_{$this->identifier}_save_data", $save_data, $this );
383
384 set_transient( $this->get_meta_key( $key ), $save_data );
385
386 $this->did_save = true;
387 $this->id = $key;
388
389 return $this;
390 }
391
392 /**
393 * Generates the unique key for the queue optionally using the client provided
394 * id.
395 *
396 * @since 4.7.12
397 *
398 * @param int $length The lengthy of the key to generate, longer keys will
399 * add more entropy; default to 64.
400 *
401 * @return string The generated batch key.
402 */
403 protected function generate_key( $length = 64 ) {
404 if ( empty( $this->id_base ) ) {
405 $this->id_base = md5( microtime() . mt_rand() );
406 }
407
408 $prepend = $this->identifier . '_batch_';
409
410 $this->batch_key = substr( $prepend . $this->id_base, 0, $length );
411
412 return $this->batch_key;
413 }
414
415 /**
416 * Saves the queue data to the database taking max_packet_size into account.
417 *
418 * In some instances the serialized size of the data might be bigger than the
419 * database `max_packet_size`; trying to write all the data in one query would
420 * make the db "go away...".
421 * Here we try to read the database `max_packet_size` setting and use that information
422 * to avoid overloading the query.
423 *
424 * @param string $key
425 * @param array $data
426 *
427 * @return int The number of fragments the data was split and stored into.
428 */
429 protected function save_split_data( $key, array $data ) {
430 if ( empty( $data ) ) {
431 return 0;
432 }
433
434 $max_frag_size = $this->get_max_frag_size();
435 // we add a 15% to the size to take the serialization and query overhead into account when fragmenting
436 $serialized_size = strlen( utf8_decode( maybe_serialize( $data ) ) ) * 1.15;
437 $frags_count = (int) ceil( $serialized_size / $max_frag_size );
438 $per_frag = max( (int) floor( count( $data ) / $frags_count ), 1 );
439
440 $split_data = array_chunk( $data, $per_frag );
441
442 if ( empty( $split_data ) ) {
443 return 0;
444 }
445
446 foreach ( $split_data as $i => $iValue ) {
447 $postfix = 0 === $i ? '' : "_{$i}";
448 update_option( $key . $postfix, $split_data[ $i ] );
449 }
450
451 return count( $split_data );
452 }
453
454 /**
455 * Returns the max frag size in bytes.
456 *
457 * The bottleneck here is the database `max_packet_size` so we try to read
458 * it from the database.
459 *
460 * @return int The max size, in bytes, of a data fragment.
461 */
462 protected function get_max_frag_size() {
463 if ( ! empty( $this->max_frag_size ) ) {
464 return $this->max_frag_size;
465 }
466
467 return tribe( 'db' )->get_max_allowed_packet_size();
468 }
469
470 /**
471 * Sets the maximum size, in bytes, of the queue fragments.
472 *
473 * This will prevent the class from trying to read the value from the database.
474 *
475 * @since 4.7.12
476 *
477 * @param int $max_frag_size
478 */
479 public function set_max_frag_size( $max_frag_size ) {
480 $this->max_frag_size = $max_frag_size;
481 }
482
483 /**
484 * Returns the queue unique identifier.
485 *
486 * Mind that an id will only be available after saving a queue.
487 *
488 * @since 4.7.12
489 *
490 * @return string
491 * @throws RuntimeException if trying to get the queue id before saving it.
492 */
493 public function get_id() {
494 if ( null === $this->id ) {
495 // not localized as this is a developer-land error
496 throw new RuntimeException( 'Can only get the id of queue after saving it.' );
497 }
498
499 return $this->id;
500 }
501
502 /**
503 * Sets the queue unique id.
504 *
505 * When using this method the client code takes charge of the queue id uniqueness;
506 * the class will not check it.
507 *
508 * @since 4.7.12
509 *
510 * @param string $queue_id
511 *
512 * @throws RuntimeException If trying to set the queue id after saving it.
513 */
514 public function set_id( $queue_id ) {
515 if ( $this->did_save ) {
516 throw new RuntimeException( 'The queue id can be set only before saving it.' );
517 }
518
519 $queue_id = preg_replace( '/^' . preg_quote( $this->identifier, '/' ) . '_batch_/', '', $queue_id );
520
521 $this->id_base = $queue_id;
522 }
523
524 /**
525 * Overrides the base `dispatch` method to allow for constants and/or environment vars to run
526 * async requests in sync mode.
527 *
528 * @since 4.7.12
529 * @since 4.9.5 Pulled method code from the `WP_Background_Process` class.
530 *
531 * @return mixed
532 */
533 public function dispatch() {
534 if (
535 ( defined( 'TRIBE_NO_ASYNC' ) && true === TRIBE_NO_ASYNC )
536 || true === (bool) getenv( 'TRIBE_NO_ASYNC' )
537 || (bool) tribe_get_request_var( 'tribe_queue_sync', false )
538 || tribe_is_truthy( tribe_get_option( 'tribe_queue_sync', false ) )
539 ) {
540 $result = $this->sync_process();
541 $this->complete();
542
543 return $result;
544 }
545
546 if ( $this->feature_detection->supports_async_process() ) {
547 // Schedule the cron health-check.
548 $this->schedule_event();
549
550 // Perform remote post.
551 return parent::dispatch();
552 }
553
554 /*
555 * If async AJAX-based processing is not available then we "dispatch"
556 * by scheduling a single cron event immediately (as soon as possible)
557 * for this handler cron identifier.
558 */
559 if ( ! wp_next_scheduled( $this->identifier ) ) {
560 // Schedule the event to happen as soon as possible.
561 $scheduled = wp_schedule_single_event( time() - 1, $this->identifier );
562
563 if ( false === $scheduled ) {
564 /** @var Tribe__Log__Logger $logger */
565 $logger = tribe( 'logger' );
566 $class = get_class( $this );
567 $src = call_user_func( [ $class, 'action' ] );
568 $logger->log( 'Could not schedule event for cron-based processing', Tribe__Log::ERROR, $src );
569 }
570 }
571
572 return true;
573 }
574
575 /**
576 * Handles the process immediately, not in an async manner.
577 *
578 * @since 4.7.12
579 *
580 * @return array An array containing the result of each item handling.
581 */
582 public function sync_process() {
583 $result = [];
584 $this->doing_sync = true;
585
586 foreach ( $this->data as $item ) {
587 $result[] = $this->task( $item );
588 }
589
590 return $result;
591 }
592
593 /**
594 * Returns the name of the option used by the queue to store its batch(es).
595 *
596 * Mind that this value will be set only when first saving the queue and it will not be set
597 * in following queue processing.
598 *
599 * @since 4.7.12
600 *
601 * @param int $n The number of a specific batch option name to get; defaults to `0` to get the
602 * option name of the first one.
603 *
604 * @return string
605 *
606 * @throws RuntimeException If trying to get the value before saving the queue or during following
607 * processing.
608 */
609 public function get_batch_key( $n = 0 ) {
610 if ( null === $this->batch_key || ! $this->did_save ) {
611 throw new RuntimeException( 'The batch key will only be set after the queue is first saved' );
612 }
613
614 return empty( $n ) ? $this->batch_key : $this->batch_key . '_' . (int) $n;
615 }
616
617 /**
618 * Returns the queue action identifier.
619 *
620 * @since 4.9.5 Pulled from the `WP_Background_Process` class.
621 *
622 * @return string The queue action identifier.
623 */
624 public function get_identifier() {
625 return $this->identifier;
626 }
627
628 /**
629 * Returns a batch of items to process from the queue.
630 *
631 * @since 4.7.12
632 * @since 4.9.5 Pulled method code from the `WP_Background_Process` class.
633 *
634 * @return stdClass The first batch of items from the queue.
635 */
636 protected function get_batch() {
637 global $wpdb;
638
639 $key = $wpdb->esc_like( $this->identifier . '_batch_' ) . '%';
640
641 $query = $wpdb->get_row( $wpdb->prepare( "
642 SELECT *
643 FROM {$wpdb->options}
644 WHERE option_name LIKE %s
645 ORDER BY option_id ASC
646 LIMIT 1
647 ", $key ) );
648
649 $batch = new stdClass();
650 $batch->key = $query->option_name;
651 $batch->data = maybe_unserialize( $query->option_value );
652
653 $this->original_batch_count = ! empty( $batch->data ) ? count( $batch->data ) : 0;
654
655 return $batch;
656 }
657
658 /**
659 * {@inheritdoc}
660 */
661 protected function get_post_args() {
662 $post_args = parent::get_post_args();
663
664 /**
665 * While sending the data into the body makes sense for the async process it does
666 * not make sense when processing a queue since the data will be stored and read
667 * from the database; furthermore this could raise issues with the max POST size.
668 */
669 $post_args['body'] = [];
670
671 return $post_args;
672 }
673
674 /**
675 * Maybe handle the process request in async or sync mode depending on the
676 * supported mode.
677 *
678 * @param array|null $data_source An optional data source.
679 *
680 * @since 4.9.5
681 */
682 public function maybe_handle( $data_source = null ) {
683 // Don't lock up other requests while processing
684 session_write_close();
685
686 if ( $this->feature_detection->supports_async_process() ) {
687 return $this->maybe_handle_async();
688 }
689
690 return $this->maybe_handle_sync();
691 }
692
693 /**
694 * Push an item to the process queue.
695 *
696 * @since 4.9.5 Pulled from the `WP_Background_Process` class.
697 *
698 * @param mixed $data An item to process.
699 *
700 * @return $this This process instance.
701 */
702 public function push_to_queue( $data ) {
703 $this->data[] = $data;
704
705 return $this;
706 }
707
708 /**
709 * Maybe handle this process request in async mode.
710 *
711 * @since 4.9.5
712 */
713 protected function maybe_handle_async() {
714 if ( $this->is_process_running() ) {
715 // Background process already running.
716 wp_die();
717 }
718
719 if ( $this->is_queue_empty() ) {
720 // No data to process: we're done.
721 $this->complete();
722 wp_die();
723 }
724
725 check_ajax_referer( $this->identifier, 'nonce' );
726
727 $this->handle();
728
729 wp_die();
730 }
731
732 /**
733 * Handle the process request in sync mode.
734 *
735 * @since 4.9.5
736 */
737 protected function maybe_handle_sync() {
738 if ( $this->is_process_running() ) {
739 // Background process already running.
740 return;
741 }
742
743 if ( $this->is_queue_empty() ) {
744 // No data to process: we're done.
745 $this->complete();
746
747 return;
748 }
749
750 $this->handle();
751 }
752
753 /**
754 * Checks whether the queue is empty or not.
755 *
756 * @since 4.9.5 Pulled from the `WP_Background_Process` class.
757 *
758 * @return bool Whether the queue is empty or not.
759 */
760 protected function is_queue_empty() {
761 global $wpdb;
762
763 $key = $wpdb->esc_like( $this->identifier . '_batch_' ) . '%';
764
765 $count = $wpdb->get_var( $wpdb->prepare( "
766 SELECT COUNT(*)
767 FROM {$wpdb->options}
768 WHERE option_name LIKE %s
769 ", $key ) );
770
771 return $count <= 0;
772 }
773
774 /**
775 * Checks whether the process is currently running or not.
776 *
777 * @since 4.9.5 Pulled from the `WP_Background_Process` class.
778 */
779 protected function is_process_running() {
780 if ( get_transient( $this->identifier . '_process_lock' ) ) {
781 return true;
782 }
783
784 return false;
785 }
786
787 /**
788 * Locks the process so that other instances cannot spawn and run.
789 *
790 * Lock the process so that multiple instances can't run simultaneously.
791 * Override if applicable, but the duration should be greater than that
792 * defined in the `time_exceeded()` method.
793 *
794 * @since 4.9.5 Pulled from the `WP_Background_Process` class.
795 */
796 protected function lock_process() {
797 // Set start time of current process.
798 $this->start_time = time();
799
800 $lock_duration = $this->queue_lock_time;
801
802 /**
803 * Filters the duration of the lock acquired by a process instance.
804 *
805 * The lock duration should be larger than the maximum time a process is allowed to run.
806 *
807 * @since 4.9.5
808 *
809 * @param int $lock_duration The lock duration in seconds; defaults to one minute.
810 * @param static $this This process instance.
811 */
812 $lock_duration = apply_filters( $this->identifier . '_queue_lock_time', $lock_duration, $this );
813
814 set_transient( $this->identifier . '_process_lock', microtime(), $lock_duration );
815 }
816
817 /**
818 * Releases the process lock so that other instances can spawn and run.
819 *
820 * @since 4.9.5 Pulled from the `WP_Background_Process` class.
821 *
822 * @return $this This process instance.
823 */
824 protected function unlock_process() {
825 delete_transient( $this->identifier . '_process_lock' );
826
827 return $this;
828 }
829
830 /**
831 * Handles the process request.
832 *
833 * Pass each queue item to the task handler, while remaining
834 * within server memory and time limit constraints.
835 *
836 * @since 4.9.5 Pulled from the `WP_Background_Process` class.
837 *
838 * @param array|null $data_source Unused and kept for compatibility with parent; the queue
839 * data is stored and read from the database.
840 */
841 protected function handle( array $data_source = null ) {
842 $this->lock_process();
843
844 do {
845 $batch = $this->get_batch();
846
847 foreach ( $batch->data as $key => $value ) {
848 $task = $this->task( $value );
849
850 if ( false !== $task ) {
851 $batch->data[ $key ] = $task;
852 } else {
853 unset( $batch->data[ $key ] );
854 }
855
856 if ( $this->time_exceeded() || $this->memory_exceeded() ) {
857 // Batch limits reached.
858 break;
859 }
860 }
861
862 // Update or delete current batch.
863 if ( ! empty( $batch->data ) ) {
864 $this->update( $batch->key, $batch->data );
865 } else {
866 $this->delete( $batch->key );
867 }
868 } while ( ! $this->time_exceeded() && ! $this->memory_exceeded() && ! $this->is_queue_empty() );
869
870 $this->unlock_process();
871
872 // Start next batch or complete process.
873 if ( ! $this->is_queue_empty() ) {
874 $this->dispatch();
875 } else {
876 $this->complete();
877 }
878
879 if ( doing_action( $this->identifier ) ) {
880 /*
881 * We're probably acting in the context of a cron request or
882 * in the context of an explicitly triggered action: let's not
883 * die.
884 */
885 return;
886 }
887
888 wp_die();
889 }
890
891 /**
892 * Checks whether the memory limit was exceeded.
893 *
894 * Ensures the batch process never exceeds 90%
895 * of the maximum WordPress memory.
896 *
897 * @since 4.9.5 Pulled from the `WP_Background_Process` class.
898 *
899 * @return bool
900 */
901 protected function memory_exceeded() {
902 $memory_limit = $this->get_memory_limit() * 0.9; // 90% of max memory
903 $current_memory = memory_get_usage( true );
904 $return = false;
905
906 if ( $current_memory >= $memory_limit ) {
907 $return = true;
908 }
909
910 /**
911 * Filters whether the process did exceed the allowed memory limit or not.
912 *
913 * @since 4.9.5
914 *
915 * @param bool $return Whether the process did exceed the allowed memory limit or not.
916 * @param static $this This process instance.
917 */
918 return apply_filters( $this->identifier . '_memory_exceeded', $return, $this );
919 }
920
921 /**
922 * Returns the memory limit for this process.
923 *
924 * @since 4.9.5 Pulled from the `WP_Background_Process` class.
925 *
926 * @return int The memory limit in bytes.
927 */
928 protected function get_memory_limit() {
929 if ( function_exists( 'ini_get' ) ) {
930 $memory_limit = ini_get( 'memory_limit' );
931 } else {
932 // Sensible default.
933 $memory_limit = '128M';
934 }
935
936 if ( ! $memory_limit || -1 === (int) $memory_limit ) {
937 // Unlimited, set to 32GB.
938 $memory_limit = '32000M';
939 }
940
941 return (int) $memory_limit * 1024 * 1024;
942 }
943
944 /**
945 * Checks whether the execution time was exceeded or not.
946 *
947 * Ensures the batch never exceeds a sensible time limit.
948 * A timeout limit of 30s is common on shared hosting.
949 *
950 * @since 4.9.5 Pulled from the `WP_Background_Process` class.
951 *
952 * @return bool Whether the execution time was exceeded or not.
953 */
954 protected function time_exceeded() {
955 /**
956 * Filters the maximum time the process can operate before continuing in another
957 * request.
958 * We pick a safe default of 20 seconds but this value can be adjusted to suit the system
959 * timeout settings.
960 *
961 * @since 4.9.5
962 *
963 * @param int $default_time_limit The time limit for the process.
964 * @param static $this This process instance.
965 */
966 $time_limit = apply_filters( $this->identifier . '_default_time_limit', 20, $this );
967
968 $finish = $this->start_time + $time_limit;
969 $return = false;
970
971 if ( time() >= $finish ) {
972 $return = true;
973 }
974
975 /**
976 * Filters whether a process instance should be marked as having exceeded the time limit or not.
977 *
978 * @since 4.9.5
979 *
980 * @param bool $return Whether the process did exceed the time limit or not.
981 * @param static $this This process instance.
982 */
983 return apply_filters( $this->identifier . '_time_exceeded', $return );
984 }
985
986 /**
987 * Completes the processing, cleaning up after it.
988 *
989 * Override if applicable, but ensure that the below actions are
990 * performed, or, call parent::complete().
991 *
992 * @since 4.9.5 Pulled from the `WP_Background_Process` class.
993 */
994 protected function complete() {
995 // Unschedule the cron health-check.
996 $this->clear_scheduled_event();
997 }
998
999 /**
1000 * Schedules a cron-based health-check to restart the queue if stuck.
1001 *
1002 * Filters the `cron_schedules` filter to add a check every 5 minutes.
1003 *
1004 * @since 4.9.5 Pulled from the `WP_Background_Process` class.
1005 *
1006 * @param mixed $schedules The cron schedules to check.
1007 *
1008 * @return mixed The updated cron schedules.
1009 */
1010 public function schedule_cron_healthcheck( $schedules ) {
1011 /**
1012 * Filters the number of minutes to schedule the cron health-check.
1013 *
1014 * @since 4.9.5
1015 *
1016 * @param int $interval The number of minutes to schedule the cron health-check; defaults to 5.
1017 * @param static $this This process instance.
1018 */
1019 $interval = apply_filters( $this->identifier . '_cron_interval', $this->healthcheck_cron_interval, $this );
1020
1021 // Adds every 5 minutes to the existing schedules.
1022 $schedules[ $this->identifier . '_cron_interval' ] = [
1023 'interval' => MINUTE_IN_SECONDS * $interval,
1024 'display' => sprintf( __( 'Every %d Minutes', 'tribe-common' ), $interval ),
1025 ];
1026
1027 return $schedules;
1028 }
1029
1030 /**
1031 * Handles the cron health-check.
1032 *
1033 * Restart the background process if not already running
1034 * and data exists in the queue.
1035 *
1036 * @since 4.9.5 Pulled from the `WP_Background_Process` class.
1037 */
1038 public function handle_cron_healthcheck() {
1039 if ( $this->is_process_running() ) {
1040 // Background process already running.
1041 exit;
1042 }
1043
1044 if ( $this->is_queue_empty() ) {
1045 // No data to process.
1046 $this->clear_scheduled_event();
1047 exit;
1048 }
1049
1050 $this->handle();
1051
1052 exit;
1053 }
1054
1055 /**
1056 * Schedules the cron health-check event.
1057 *
1058 * @since 4.9.5 Pulled from the `WP_Background_Process` class.
1059 */
1060 protected function schedule_event() {
1061 if ( ! wp_next_scheduled( $this->healthcheck_cron_hook_id ) ) {
1062 wp_schedule_event( time(), $this->healthcheck_cron_interval_id, $this->healthcheck_cron_hook_id );
1063 }
1064 }
1065
1066 /**
1067 * Clears the scheduled health-check cron event.
1068 *
1069 * @since 4.9.5 Pulled from the `WP_Background_Process` class.
1070 */
1071 protected function clear_scheduled_event() {
1072 $timestamp = wp_next_scheduled( $this->healthcheck_cron_hook_id );
1073
1074 if ( $timestamp ) {
1075 wp_unschedule_event( $timestamp, $this->healthcheck_cron_hook_id );
1076 }
1077 }
1078
1079 /**
1080 * Cancels the current process.
1081 *
1082 * Stops processing queue items and clean up.
1083 *
1084 * @since 4.9.5 Pulled from the `WP_Background_Process` class.
1085 */
1086 public function cancel_process() {
1087 if ( ! $this->is_queue_empty() ) {
1088 $batch = $this->get_batch();
1089
1090 $this->delete( $batch->key );
1091
1092 wp_clear_scheduled_hook( $this->healthcheck_cron_hook_id );
1093 }
1094
1095 }
1096
1097 /**
1098 * Executes the process task on a single item.
1099 *
1100 * Override this method to perform any actions required on each
1101 * queue item. Return the modified item for further processing
1102 * in the next pass through. Or, return false to remove the
1103 * item from the queue.
1104 *
1105 * @since 4.9.5 Pulled from the `WP_Background_Process` class.
1106 *
1107 * @param mixed $item Queue item to iterate over.
1108 *
1109 * @return mixed
1110 */
1111 abstract protected function task( $item );
1112
1113 /**
1114 * Concrete implementation of the base handler method.
1115 *
1116 * Just a proxy to the `sync_process` method.
1117 *
1118 * @since 4.9.5
1119 *
1120 * @param array|null $data_source If not provided the method will read the handler data from the
1121 * request array.
1122 *
1123 * @return array|mixed|null The synchronous process result.
1124 */
1125 public function sync_handle( array $data_source = null ) {
1126 // In the base implementation the data source is unused and read from the database.
1127 return $this->sync_process();
1128 }
1129 }
1130