PluginProbe
CartFlows – Funnel Builder & Checkout Plugin for WooCommerce / 3.1.1
CartFlows – Funnel Builder & Checkout Plugin for WooCommerce v3.1.1
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 / importer / batch-process / helpers / class-wp-background-process.php

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

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