PluginProbe
Elementor Website Builder – more than just a page builder / 3.14.0-beta4
Elementor Website Builder – more than just a page builder v3.14.0-beta4
4.3.0-beta2 4.3.0-beta1 4.2.4 4.2.3 4.2.2 4.2.1 4.2.0 4.1.5 4.2.0-beta2 4.2.0-dev2 4.2.0-beta1 4.1.4 4.1.3 4.1.2 4.1.1 4.1.0 4.1.0-beta3 4.1.0-dev3 4.0.9 4.1.0-beta2 4.1.0-dev2 4.0.8 4.1.0-beta1 4.1.0-dev1 4.0.7 All 451 releases
elementor / core / base / background-process / wp-background-process.php

wp-background-process.php in Elementor Website Builder – more than just a page builder 3.14.0-beta4, at core/base/background-process/wp-background-process.php

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