PluginProbe ʕ •ᴥ•ʔ
Everest Forms – Contact Form, Payment Form, Quiz, Survey & Custom Form Builder with AI / trunk
Everest Forms – Contact Form, Payment Form, Quiz, Survey & Custom Form Builder with AI vtrunk
3.5.1 3.5.0 3.4.8 3.4.7 3.4.6 1.1.0 1.1.1 1.1.2 1.1.3 1.1.4 1.1.5 1.1.5.1 1.1.6 1.1.7 1.1.8 1.1.9 1.2.0 1.2.1 1.2.2 1.2.3 1.2.4 1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 1.4.0 1.4.1 1.4.2 1.4.3 1.4.4 1.4.5 1.4.6 1.4.7 1.4.8 1.4.9 1.5.0 1.5.1 1.5.10 1.5.2 1.5.3 1.5.4 1.5.5 1.5.6 1.5.7 1.5.8 1.5.9 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 1.6.5 1.6.6 1.6.6.1 1.6.7 1.7.0 1.7.0.1 1.7.0.2 1.7.0.3 1.7.1 1.7.2 1.7.2.1 1.7.2.2 1.7.3 1.7.4 1.7.5 1.7.5.1 1.7.5.2 1.7.6 1.7.7 1.7.7.1 1.7.7.2 1.7.8 1.7.9 1.8.0 1.8.0.1 1.8.1 1.8.2 1.8.2.1 1.8.2.2 1.8.2.3 1.8.3 1.8.4 1.8.5 1.8.6 1.8.7 1.8.8 1.8.9 1.9.0 1.9.0.1 1.9.1 1.9.2 1.9.3 1.9.4 1.9.4.1 1.9.5 1.9.6 1.9.7 1.9.8 1.9.9 2.0.0 2.0.0.1 2.0.1 2.0.2 2.0.3 2.0.3.1 2.0.4 2.0.4.1 2.0.5 2.0.6 2.0.7 2.0.8 2.0.8.1 2.0.9 3.0.0 3.0.0.1 3.0.1 3.0.2 3.0.3 3.0.3.1 3.0.4 3.0.4.1 3.0.4.2 3.0.5 3.0.5.1 3.0.5.2 3.0.6 3.0.6.1 3.0.7.1 3.0.8 3.0.8.1 3.0.9 3.0.9.1 3.0.9.2 3.0.9.3 3.0.9.4 3.0.9.5 3.1.0 3.1.1 3.1.2 3.2.0 3.2.1 3.2.2 3.2.3 3.2.4 3.2.5 3.2.6 3.3.0 3.4.0 3.4.1 3.4.2 3.4.2.1 3.4.3 3.4.4 3.4.5 trunk 1.0 1.0.1 1.0.2 1.0.3
everest-forms / includes / libraries / wp-background-process.php
everest-forms / includes / libraries Last commit date
wp-async-request.php 8 years ago wp-background-process.php 7 years ago wptt-webfont-loader.php 2 years ago
wp-background-process.php
504 lines
1 <?php // @codingStandardsIgnoreLine.
2 /**
3 * Abstract WP_Background_Process class.
4 *
5 * @package WP-Background-Processing
6 * @extends WP_Async_Request
7 */
8
9 defined( 'ABSPATH' ) || exit;
10
11 /**
12 * Abstract WP_Background_Process class.
13 */
14 abstract class WP_Background_Process extends WP_Async_Request {
15
16 /**
17 * Action
18 *
19 * (default value: 'background_process')
20 *
21 * @var string
22 * @access protected
23 */
24 protected $action = 'background_process';
25
26 /**
27 * Start time of current process.
28 *
29 * (default value: 0)
30 *
31 * @var int
32 * @access protected
33 */
34 protected $start_time = 0;
35
36 /**
37 * Cron_hook_identifier
38 *
39 * @var mixed
40 * @access protected
41 */
42 protected $cron_hook_identifier;
43
44 /**
45 * Cron_interval_identifier
46 *
47 * @var mixed
48 * @access protected
49 */
50 protected $cron_interval_identifier;
51
52 /**
53 * Initiate new background process
54 */
55 public function __construct() {
56 parent::__construct();
57
58 $this->cron_hook_identifier = $this->identifier . '_cron';
59 $this->cron_interval_identifier = $this->identifier . '_cron_interval';
60
61 add_action( $this->cron_hook_identifier, array( $this, 'handle_cron_healthcheck' ) );
62 add_filter( 'cron_schedules', array( $this, 'schedule_cron_healthcheck' ) );
63 }
64
65 /**
66 * Dispatch
67 *
68 * @access public
69 * @return void
70 */
71 public function dispatch() {
72 // Schedule the cron healthcheck.
73 $this->schedule_event();
74
75 // Perform remote post.
76 return parent::dispatch();
77 }
78
79 /**
80 * Push to queue
81 *
82 * @param mixed $data Data.
83 *
84 * @return $this
85 */
86 public function push_to_queue( $data ) {
87 $this->data[] = $data;
88
89 return $this;
90 }
91
92 /**
93 * Save queue
94 *
95 * @return $this
96 */
97 public function save() {
98 $key = $this->generate_key();
99
100 if ( ! empty( $this->data ) ) {
101 update_site_option( $key, $this->data );
102 }
103
104 return $this;
105 }
106
107 /**
108 * Update queue
109 *
110 * @param string $key Key.
111 * @param array $data Data.
112 *
113 * @return $this
114 */
115 public function update( $key, $data ) {
116 if ( ! empty( $data ) ) {
117 update_site_option( $key, $data );
118 }
119
120 return $this;
121 }
122
123 /**
124 * Delete queue
125 *
126 * @param string $key Key.
127 *
128 * @return $this
129 */
130 public function delete( $key ) {
131 delete_site_option( $key );
132
133 return $this;
134 }
135
136 /**
137 * Generate key
138 *
139 * Generates a unique key based on microtime. Queue items are
140 * given a unique key so that they can be merged upon save.
141 *
142 * @param int $length Length.
143 *
144 * @return string
145 */
146 protected function generate_key( $length = 64 ) {
147 $unique = md5( microtime() . rand() );
148 $prepend = $this->identifier . '_batch_';
149
150 return substr( $prepend . $unique, 0, $length );
151 }
152
153 /**
154 * Maybe process queue
155 *
156 * Checks whether data exists within the queue and that
157 * the process is not already running.
158 */
159 public function maybe_handle() {
160 // Don't lock up other requests while processing
161 session_write_close();
162
163 if ( $this->is_process_running() ) {
164 // Background process already running.
165 wp_die();
166 }
167
168 if ( $this->is_queue_empty() ) {
169 // No data to process.
170 wp_die();
171 }
172
173 check_ajax_referer( $this->identifier, 'nonce' );
174
175 $this->handle();
176
177 wp_die();
178 }
179
180 /**
181 * Is queue empty
182 *
183 * @return bool
184 */
185 protected function is_queue_empty() {
186 global $wpdb;
187
188 $table = $wpdb->options;
189 $column = 'option_name';
190
191 if ( is_multisite() ) {
192 $table = $wpdb->sitemeta;
193 $column = 'meta_key';
194 }
195
196 $key = $this->identifier . '_batch_%';
197
198 $count = $wpdb->get_var( $wpdb->prepare( "
199 SELECT COUNT(*)
200 FROM {$table}
201 WHERE {$column} LIKE %s
202 ", $key ) );
203
204 return ! ( $count > 0 );
205 }
206
207 /**
208 * Is process running
209 *
210 * Check whether the current process is already running
211 * in a background process.
212 */
213 protected function is_process_running() {
214 if ( get_site_transient( $this->identifier . '_process_lock' ) ) {
215 // Process already running.
216 return true;
217 }
218
219 return false;
220 }
221
222 /**
223 * Lock process
224 *
225 * Lock the process so that multiple instances can't run simultaneously.
226 * Override if applicable, but the duration should be greater than that
227 * defined in the time_exceeded() method.
228 */
229 protected function lock_process() {
230 $this->start_time = time(); // Set start time of current process.
231
232 $lock_duration = ( property_exists( $this, 'queue_lock_time' ) ) ? $this->queue_lock_time : 60; // 1 minute
233 $lock_duration = apply_filters( $this->identifier . '_queue_lock_time', $lock_duration );
234
235 set_site_transient( $this->identifier . '_process_lock', microtime(), $lock_duration );
236 }
237
238 /**
239 * Unlock process
240 *
241 * Unlock the process so that other instances can spawn.
242 *
243 * @return $this
244 */
245 protected function unlock_process() {
246 delete_site_transient( $this->identifier . '_process_lock' );
247
248 return $this;
249 }
250
251 /**
252 * Get batch
253 *
254 * @return stdClass Return the first batch from the queue
255 */
256 protected function get_batch() {
257 global $wpdb;
258
259 $table = $wpdb->options;
260 $column = 'option_name';
261 $key_column = 'option_id';
262 $value_column = 'option_value';
263
264 if ( is_multisite() ) {
265 $table = $wpdb->sitemeta;
266 $column = 'meta_key';
267 $key_column = 'meta_id';
268 $value_column = 'meta_value';
269 }
270
271 $key = $this->identifier . '_batch_%';
272
273 $query = $wpdb->get_row( $wpdb->prepare( "
274 SELECT *
275 FROM {$table}
276 WHERE {$column} LIKE %s
277 ORDER BY {$key_column} ASC
278 LIMIT 1
279 ", $key ) );
280
281 $batch = new stdClass();
282 $batch->key = $query->$column;
283 $batch->data = maybe_unserialize( $query->$value_column );
284
285 return $batch;
286 }
287
288 /**
289 * Handle
290 *
291 * Pass each queue item to the task handler, while remaining
292 * within server memory and time limit constraints.
293 */
294 protected function handle() {
295 $this->lock_process();
296
297 do {
298 $batch = $this->get_batch();
299
300 foreach ( $batch->data as $key => $value ) {
301 $task = $this->task( $value );
302
303 if ( false !== $task ) {
304 $batch->data[ $key ] = $task;
305 } else {
306 unset( $batch->data[ $key ] );
307 }
308
309 if ( $this->time_exceeded() || $this->memory_exceeded() ) {
310 // Batch limits reached.
311 break;
312 }
313 }
314
315 // Update or delete current batch.
316 if ( ! empty( $batch->data ) ) {
317 $this->update( $batch->key, $batch->data );
318 } else {
319 $this->delete( $batch->key );
320 }
321 } while ( ! $this->time_exceeded() && ! $this->memory_exceeded() && ! $this->is_queue_empty() );
322
323 $this->unlock_process();
324
325 // Start next batch or complete process.
326 if ( ! $this->is_queue_empty() ) {
327 $this->dispatch();
328 } else {
329 $this->complete();
330 }
331
332 wp_die();
333 }
334
335 /**
336 * Memory exceeded
337 *
338 * Ensures the batch process never exceeds 90%
339 * of the maximum WordPress memory.
340 *
341 * @return bool
342 */
343 protected function memory_exceeded() {
344 $memory_limit = $this->get_memory_limit() * 0.9; // 90% of max memory
345 $current_memory = memory_get_usage( true );
346 $return = false;
347
348 if ( $current_memory >= $memory_limit ) {
349 $return = true;
350 }
351
352 return apply_filters( $this->identifier . '_memory_exceeded', $return );
353 }
354
355 /**
356 * Get memory limit
357 *
358 * @return int
359 */
360 protected function get_memory_limit() {
361 if ( function_exists( 'ini_get' ) ) {
362 $memory_limit = ini_get( 'memory_limit' );
363 } else {
364 // Sensible default.
365 $memory_limit = '128M';
366 }
367
368 if ( ! $memory_limit || -1 === $memory_limit ) {
369 // Unlimited, set to 32GB.
370 $memory_limit = '32000M';
371 }
372
373 return intval( $memory_limit ) * 1024 * 1024;
374 }
375
376 /**
377 * Time exceeded.
378 *
379 * Ensures the batch never exceeds a sensible time limit.
380 * A timeout limit of 30s is common on shared hosting.
381 *
382 * @return bool
383 */
384 protected function time_exceeded() {
385 $finish = $this->start_time + apply_filters( $this->identifier . '_default_time_limit', 20 ); // 20 seconds
386 $return = false;
387
388 if ( time() >= $finish ) {
389 $return = true;
390 }
391
392 return apply_filters( $this->identifier . '_time_exceeded', $return );
393 }
394
395 /**
396 * Complete.
397 *
398 * Override if applicable, but ensure that the below actions are
399 * performed, or, call parent::complete().
400 */
401 protected function complete() {
402 // Unschedule the cron healthcheck.
403 $this->clear_scheduled_event();
404 }
405
406 /**
407 * Schedule cron healthcheck
408 *
409 * @access public
410 * @param mixed $schedules Schedules.
411 * @return mixed
412 */
413 public function schedule_cron_healthcheck( $schedules ) {
414 $interval = apply_filters( $this->identifier . '_cron_interval', 5 );
415
416 if ( property_exists( $this, 'cron_interval' ) ) {
417 $interval = apply_filters( $this->identifier . '_cron_interval', $this->cron_interval );
418 }
419
420 // Adds every 5 minutes to the existing schedules.
421 $schedules[ $this->identifier . '_cron_interval' ] = array(
422 'interval' => MINUTE_IN_SECONDS * $interval,
423 'display' => sprintf( __( 'Every %d Minutes' ), $interval ),
424 );
425
426 return $schedules;
427 }
428
429 /**
430 * Handle cron healthcheck
431 *
432 * Restart the background process if not already running
433 * and data exists in the queue.
434 */
435 public function handle_cron_healthcheck() {
436 if ( $this->is_process_running() ) {
437 // Background process already running.
438 exit;
439 }
440
441 if ( $this->is_queue_empty() ) {
442 // No data to process.
443 $this->clear_scheduled_event();
444 exit;
445 }
446
447 $this->handle();
448
449 exit;
450 }
451
452 /**
453 * Schedule event
454 */
455 protected function schedule_event() {
456 if ( ! wp_next_scheduled( $this->cron_hook_identifier ) ) {
457 wp_schedule_event( time(), $this->cron_interval_identifier, $this->cron_hook_identifier );
458 }
459 }
460
461 /**
462 * Clear scheduled event
463 */
464 protected function clear_scheduled_event() {
465 $timestamp = wp_next_scheduled( $this->cron_hook_identifier );
466
467 if ( $timestamp ) {
468 wp_unschedule_event( $timestamp, $this->cron_hook_identifier );
469 }
470 }
471
472 /**
473 * Cancel Process
474 *
475 * Stop processing queue items, clear cronjob and delete batch.
476 *
477 */
478 public function cancel_process() {
479 if ( ! $this->is_queue_empty() ) {
480 $batch = $this->get_batch();
481
482 $this->delete( $batch->key );
483
484 wp_clear_scheduled_hook( $this->cron_hook_identifier );
485 }
486
487 }
488
489 /**
490 * Task
491 *
492 * Override this method to perform any actions required on each
493 * queue item. Return the modified item for further processing
494 * in the next pass through. Or, return false to remove the
495 * item from the queue.
496 *
497 * @param mixed $item Queue item to iterate over.
498 *
499 * @return mixed
500 */
501 abstract protected function task( $item );
502
503 }
504