PluginProbe
CartFlows – Funnel Builder & Checkout Plugin for WooCommerce / 1.1.12
CartFlows – Funnel Builder & Checkout Plugin for WooCommerce v1.1.12
3.3.0 3.2.1 3.2.0 3.1.4 3.1.3 3.1.2 3.1.1 3.1.0 3.0.1 trunk 1.0.4 1.1.0 1.1.0.1 1.1.1 1.1.10 1.1.11 1.1.12 1.1.13 1.1.14 1.1.15 1.1.16 1.1.17 1.1.18 1.1.19 1.1.2 All 162 releases
cartflows / classes / batch-process / helpers / class-wp-background-process.php

class-wp-background-process.php in CartFlows – Funnel Builder & Checkout Plugin for WooCommerce 1.1.12, at classes/batch-process/helpers/class-wp-background-process.php

516 lines 11.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * WP Background Process
4 *
5 * @see https://github.com/A5hleyRich/wp-background-processing/
6 * @package WP-Background-Processing
7 */
8
9 if ( ! class_exists( 'WP_Background_Process' ) ) {
10
11 /**
12 * Abstract WP_Background_Process class.
13 *
14 * @abstract
15 * @extends WP_Async_Request
16 */
17 abstract class WP_Background_Process extends WP_Async_Request {
18
19 /**
20 * Action
21 *
22 * (default value: 'background_process')
23 *
24 * @var string
25 * @access protected
26 */
27 protected $action = 'background_process';
28
29 /**
30 * Start time of current process.
31 *
32 * (default value: 0)
33 *
34 * @var int
35 * @access protected
36 */
37 protected $start_time = 0;
38
39 /**
40 * Cron_hook_identifier
41 *
42 * @var mixed
43 * @access protected
44 */
45 protected $cron_hook_identifier;
46
47 /**
48 * Cron_interval_identifier
49 *
50 * @var mixed
51 * @access protected
52 */
53 protected $cron_interval_identifier;
54
55 /**
56 * Initiate new background process
57 */
58 public function __construct() {
59 parent::__construct();
60
61 $this->cron_hook_identifier = $this->identifier . '_cron';
62 $this->cron_interval_identifier = $this->identifier . '_cron_interval';
63
64 add_action( $this->cron_hook_identifier, array( $this, 'handle_cron_healthcheck' ) );
65 add_filter( 'cron_schedules', array( $this, 'schedule_cron_healthcheck' ) );
66 }
67
68 /**
69 * Dispatch
70 *
71 * @return mixed dispatch event.
72 */
73 public function dispatch() {
74 // Schedule the cron healthcheck.
75 $this->schedule_event();
76
77 // Perform remote post.
78 return parent::dispatch();
79 }
80
81 /**
82 * Push to queue
83 *
84 * @param mixed $data Data.
85 *
86 * @return $this
87 */
88 public function push_to_queue( $data ) {
89 $this->data[] = $data;
90
91 return $this;
92 }
93
94 /**
95 * Save queue
96 *
97 * @return $this
98 */
99 public function save() {
100 $key = $this->generate_key();
101
102 if ( ! empty( $this->data ) ) {
103 update_site_option( $key, $this->data );
104 }
105
106 return $this;
107 }
108
109 /**
110 * Update queue
111 *
112 * @param string $key Key.
113 * @param array $data Data.
114 *
115 * @return $this
116 */
117 public function update( $key, $data ) {
118 if ( ! empty( $data ) ) {
119 update_site_option( $key, $data );
120 }
121
122 return $this;
123 }
124
125 /**
126 * Delete queue
127 *
128 * @param string $key Key.
129 *
130 * @return $this
131 */
132 public function delete( $key ) {
133 delete_site_option( $key );
134
135 return $this;
136 }
137
138 /**
139 * Generate key
140 *
141 * Generates a unique key based on microtime. Queue items are
142 * given a unique key so that they can be merged upon save.
143 *
144 * @param int $length Length.
145 *
146 * @return string
147 */
148 protected function generate_key( $length = 64 ) {
149 $unique = md5( microtime() . rand() );
150 $prepend = $this->identifier . '_batch_';
151
152 return substr( $prepend . $unique, 0, $length );
153 }
154
155 /**
156 * Maybe process queue
157 *
158 * Checks whether data exists within the queue and that
159 * the process is not already running.
160 */
161 public function maybe_handle() {
162 // Don't lock up other requests while processing.
163 session_write_close();
164
165 if ( $this->is_process_running() ) {
166 // Background process already running.
167 wp_die();
168 }
169
170 if ( $this->is_queue_empty() ) {
171 // No data to process.
172 wp_die();
173 }
174
175 check_ajax_referer( $this->identifier, 'nonce' );
176
177 $this->handle();
178
179 wp_die();
180 }
181
182 /**
183 * Is queue empty
184 *
185 * @return bool
186 */
187 protected function is_queue_empty() {
188 global $wpdb;
189
190 $table = $wpdb->options;
191 $column = 'option_name';
192
193 if ( is_multisite() ) {
194 $table = $wpdb->sitemeta;
195 $column = 'meta_key';
196 }
197
198 $key = $this->identifier . '_batch_%';
199
200 $count = $wpdb->get_var(
201 $wpdb->prepare(
202 "
203 SELECT COUNT(*)
204 FROM {$table}
205 WHERE {$column} LIKE %s
206 ",
207 $key
208 )
209 );
210
211 return ( $count > 0 ) ? false : true;
212 }
213
214 /**
215 * Is process running
216 *
217 * Check whether the current process is already running
218 * in a background process.
219 */
220 protected function is_process_running() {
221 if ( get_site_transient( $this->identifier . '_process_lock' ) ) {
222 // Process already running.
223 return true;
224 }
225
226 return false;
227 }
228
229 /**
230 * Lock process
231 *
232 * Lock the process so that multiple instances can't run simultaneously.
233 * Override if applicable, but the duration should be greater than that
234 * defined in the time_exceeded() method.
235 */
236 protected function lock_process() {
237 $this->start_time = time(); // Set start time of current process.
238
239 $lock_duration = ( property_exists( $this, 'queue_lock_time' ) ) ? $this->queue_lock_time : 60; // 1 minute
240 $lock_duration = apply_filters( $this->identifier . '_queue_lock_time', $lock_duration );
241
242 set_site_transient( $this->identifier . '_process_lock', microtime(), $lock_duration );
243 }
244
245 /**
246 * Unlock process
247 *
248 * Unlock the process so that other instances can spawn.
249 *
250 * @return $this
251 */
252 protected function unlock_process() {
253 delete_site_transient( $this->identifier . '_process_lock' );
254
255 return $this;
256 }
257
258 /**
259 * Get batch
260 *
261 * @return stdClass Return the first batch from the queue
262 */
263 protected function get_batch() {
264 global $wpdb;
265
266 $table = $wpdb->options;
267 $column = 'option_name';
268 $key_column = 'option_id';
269 $value_column = 'option_value';
270
271 if ( is_multisite() ) {
272 $table = $wpdb->sitemeta;
273 $column = 'meta_key';
274 $key_column = 'meta_id';
275 $value_column = 'meta_value';
276 }
277
278 $key = $this->identifier . '_batch_%';
279
280 $query = $wpdb->get_row(
281 $wpdb->prepare(
282 "
283 SELECT *
284 FROM {$table}
285 WHERE {$column} LIKE %s
286 ORDER BY {$key_column} ASC
287 LIMIT 1
288 ",
289 $key
290 )
291 );
292
293 $batch = new stdClass();
294 $batch->key = $query->$column;
295 $batch->data = maybe_unserialize( $query->$value_column );
296
297 return $batch;
298 }
299
300 /**
301 * Handle
302 *
303 * Pass each queue item to the task handler, while remaining
304 * within server memory and time limit constraints.
305 */
306 protected function handle() {
307 $this->lock_process();
308
309 do {
310 $batch = $this->get_batch();
311
312 foreach ( $batch->data as $key => $value ) {
313 $task = $this->task( $value );
314
315 if ( false !== $task ) {
316 $batch->data[ $key ] = $task;
317 } else {
318 unset( $batch->data[ $key ] );
319 }
320
321 if ( $this->time_exceeded() || $this->memory_exceeded() ) {
322 // Batch limits reached.
323 break;
324 }
325 }
326
327 // Update or delete current batch.
328 if ( ! empty( $batch->data ) ) {
329 $this->update( $batch->key, $batch->data );
330 } else {
331 $this->delete( $batch->key );
332 }
333 } while ( ! $this->time_exceeded() && ! $this->memory_exceeded() && ! $this->is_queue_empty() );
334
335 $this->unlock_process();
336
337 // Start next batch or complete process.
338 if ( ! $this->is_queue_empty() ) {
339 $this->dispatch();
340 } else {
341 $this->complete();
342 }
343
344 wp_die();
345 }
346
347 /**
348 * Memory exceeded
349 *
350 * Ensures the batch process never exceeds 90%
351 * of the maximum WordPress memory.
352 *
353 * @return bool
354 */
355 protected function memory_exceeded() {
356 $memory_limit = $this->get_memory_limit() * 0.9; // 90% of max memory
357 $current_memory = memory_get_usage( true );
358 $return = false;
359
360 if ( $current_memory >= $memory_limit ) {
361 $return = true;
362 }
363
364 return apply_filters( $this->identifier . '_memory_exceeded', $return );
365 }
366
367 /**
368 * Get memory limit
369 *
370 * @return int
371 */
372 protected function get_memory_limit() {
373 if ( function_exists( 'ini_get' ) ) {
374 $memory_limit = ini_get( 'memory_limit' );
375 } else {
376 // Sensible default.
377 $memory_limit = '128M';
378 }
379
380 if ( ! $memory_limit || -1 === $memory_limit ) {
381 // Unlimited, set to 32GB.
382 $memory_limit = '32000M';
383 }
384
385 return intval( $memory_limit ) * 1024 * 1024;
386 }
387
388 /**
389 * Time exceeded.
390 *
391 * Ensures the batch never exceeds a sensible time limit.
392 * A timeout limit of 30s is common on shared hosting.
393 *
394 * @return bool
395 */
396 protected function time_exceeded() {
397 $finish = $this->start_time + apply_filters( $this->identifier . '_default_time_limit', 20 ); // 20 seconds
398 $return = false;
399
400 if ( time() >= $finish ) {
401 $return = true;
402 }
403
404 return apply_filters( $this->identifier . '_time_exceeded', $return );
405 }
406
407 /**
408 * Complete.
409 *
410 * Override if applicable, but ensure that the below actions are
411 * performed, or, call parent::complete().
412 */
413 protected function complete() {
414 // Unschedule the cron healthcheck.
415 $this->clear_scheduled_event();
416 }
417
418 /**
419 * Schedule cron healthcheck
420 *
421 * @access public
422 * @param mixed $schedules Schedules.
423 * @return mixed
424 */
425 public function schedule_cron_healthcheck( $schedules ) {
426 $interval = apply_filters( $this->identifier . '_cron_interval', 5 );
427
428 if ( property_exists( $this, 'cron_interval' ) ) {
429 $interval = apply_filters( $this->identifier . '_cron_interval', $this->cron_interval_identifier );
430 }
431
432 // Adds every 5 minutes to the existing schedules.
433 $schedules[ $this->identifier . '_cron_interval' ] = array(
434 'interval' => MINUTE_IN_SECONDS * $interval,
435 'display' => sprintf( __( 'Every %d Minutes', 'cartflows' ), $interval ),
436 );
437
438 return $schedules;
439 }
440
441 /**
442 * Handle cron healthcheck
443 *
444 * Restart the background process if not already running
445 * and data exists in the queue.
446 */
447 public function handle_cron_healthcheck() {
448 if ( $this->is_process_running() ) {
449 // Background process already running.
450 exit;
451 }
452
453 if ( $this->is_queue_empty() ) {
454 // No data to process.
455 $this->clear_scheduled_event();
456 exit;
457 }
458
459 $this->handle();
460
461 exit;
462 }
463
464 /**
465 * Schedule event
466 */
467 protected function schedule_event() {
468 if ( ! wp_next_scheduled( $this->cron_hook_identifier ) ) {
469 wp_schedule_event( time(), $this->cron_interval_identifier, $this->cron_hook_identifier );
470 }
471 }
472
473 /**
474 * Clear scheduled event
475 */
476 protected function clear_scheduled_event() {
477 $timestamp = wp_next_scheduled( $this->cron_hook_identifier );
478
479 if ( $timestamp ) {
480 wp_unschedule_event( $timestamp, $this->cron_hook_identifier );
481 }
482 }
483
484 /**
485 * Cancel Process
486 *
487 * Stop processing queue items, clear cronjob and delete batch.
488 */
489 public function cancel_process() {
490 if ( ! $this->is_queue_empty() ) {
491 $batch = $this->get_batch();
492
493 $this->delete( $batch->key );
494
495 wp_clear_scheduled_hook( $this->cron_hook_identifier );
496 }
497
498 }
499
500 /**
501 * Task
502 *
503 * Override this method to perform any actions required on each
504 * queue item. Return the modified item for further processing
505 * in the next pass through. Or, return false to remove the
506 * item from the queue.
507 *
508 * @param mixed $item Queue item to iterate over.
509 *
510 * @return mixed
511 */
512 abstract protected function task( $item );
513
514 }
515 }
516