PluginProbe ʕ •ᴥ•ʔ
GiveWP – Donation Plugin and Fundraising Platform / trunk
GiveWP – Donation Plugin and Fundraising Platform vtrunk
4.16.2 4.16.1 4.16.0 4.15.5 4.15.4 4.15.3 4.15.2 4.15.1 4.15.0 2.3.0 2.3.1 2.3.2 2.30.0 2.31.0 2.31.1 2.32.0 2.33.0 2.33.1 2.33.2 2.33.3 2.33.4 2.33.5 2.4.0 2.4.1 2.4.2 2.4.3 2.4.4 2.4.5 2.4.6 2.4.7 2.5.0 2.5.1 2.5.10 2.5.11 2.5.12 2.5.13 2.5.2 2.5.3 2.5.4 2.5.5 2.5.6 2.5.7 2.5.8 2.5.9 2.6.0 2.6.1 2.6.2 2.6.3 2.7.0 2.7.1 2.7.2 2.7.3 2.7.4 2.7.5 2.8.0 2.8.1 2.9.0 2.9.1 2.9.2 2.9.3 2.9.4 2.9.5 2.9.6 2.9.7 3.0.0 3.0.1 3.0.2 3.0.3 3.0.4 3.1.0 3.1.1 3.1.2 3.10.0 3.11.0 3.12.0 3.12.1 3.12.2 3.12.3 3.13.0 3.14.0 3.14.1 3.14.2 3.15.0 3.15.1 3.16.0 3.16.1 3.16.2 3.16.3 3.16.4 3.16.5 3.17.0 3.17.1 3.17.2 3.18.0 3.19.0 3.19.1 3.19.2 3.19.3 3.19.4 3.2.0 3.2.1 3.2.2 3.20.0 3.21.0 3.21.1 3.22.0 3.22.1 3.22.2 3.3.0 3.3.1 3.4.0 3.4.1 3.4.2 3.5.0 3.5.1 3.6.0 3.6.1 3.6.2 3.7.0 3.8.0 3.9.0 4.0.0 4.1.0 4.1.1 4.10.0 4.10.1 4.11.0 4.12.0 4.13.0 4.13.1 4.13.2 4.14.0 4.14.1 4.14.2 4.14.3 4.14.4 4.14.5 4.14.6 4.2.0 4.2.1 4.3.0 4.3.1 4.3.2 4.4.0 4.5.0 4.6.1 4.7.0 4.7.1 4.8.0 4.8.1 4.9.0 trunk 1.9.0 2.0.0 2.0.1 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7 2.1.0 2.1.1 2.1.2 2.1.3 2.1.4 2.1.5 2.1.6 2.1.7 2.1.8 2.10.0 2.10.1 2.10.2 2.10.3 2.10.4 2.11.0 2.11.1 2.11.2 2.11.3 2.12.0 2.12.1 2.12.2 2.12.3 2.13.0 2.13.1 2.13.2 2.13.3 2.13.4 2.14.0 2.15.0 2.16.0 2.16.1 2.17.0 2.17.1 2.17.3 2.18.0 2.18.1 2.19.1 2.19.2 2.19.3 2.19.4 2.19.5 2.19.6 2.19.7 2.19.8 2.2.0 2.2.1 2.2.2 2.2.3 2.2.4 2.2.5 2.2.6 2.20.0 2.20.1 2.20.2 2.21.0 2.21.1 2.21.2 2.21.3 2.21.4 2.22.0 2.22.1 2.22.2 2.22.3 2.23.0 2.23.1 2.23.2 2.24.0 2.24.1 2.24.2 2.25.0 2.25.1 2.25.2 2.25.3 2.26.0 2.27.0 2.27.1 2.27.2 2.27.3 2.28.0 2.29.0 2.29.1 2.29.2
give / src / Framework / WordPressLibraries / WPBackgroundProcess.php
give / src / Framework / WordPressLibraries Last commit date
WPAsyncRequest.php 2 years ago WPBackgroundProcess.php 2 years ago
WPBackgroundProcess.php
538 lines
1 <?php
2
3 namespace Give\Framework\WordPressLibraries;
4
5
6 /**
7 * This is a fork of WP_Background_Process that adds GiveWP namespaces to prevent conflicts with other plugins.
8 *
9 * IMPORTANT: Developers, please be aware that the usage of WPAsyncRequest and WPBackgroundProcess is discouraged as they are included only for legacy purposes.
10 * Instead, it is strongly recommended to use Action Scheduler for any asynchronous processing needs.
11 * Action Scheduler is available, provides a more efficient solution, and is the preferred choice for new development.
12 *
13 * @since 2.32.0
14 */
15 abstract class WPBackgroundProcess extends WPAsyncRequest
16 {
17 /**
18 * Action
19 *
20 * (default value: 'background_process')
21 *
22 * @var string
23 * @access protected
24 */
25 protected $action = 'background_process';
26
27 /**
28 * Start time of current process.
29 *
30 * (default value: 0)
31 *
32 * @var int
33 * @access protected
34 */
35 protected $start_time = 0;
36
37 /**
38 * Cron_hook_identifier
39 *
40 * @var mixed
41 * @access protected
42 */
43 protected $cron_hook_identifier;
44
45 /**
46 * Cron_interval_identifier
47 *
48 * @var mixed
49 * @access protected
50 */
51 protected $cron_interval_identifier;
52
53 /**
54 * Initiate new background process
55 */
56 public function __construct()
57 {
58 parent::__construct();
59
60 $this->cron_hook_identifier = $this->identifier . '_cron';
61 $this->cron_interval_identifier = $this->identifier . '_cron_interval';
62
63 add_action($this->cron_hook_identifier, [$this, 'handle_cron_healthcheck']);
64 add_filter('cron_schedules', [$this, 'schedule_cron_healthcheck']);
65 }
66
67 /**
68 * Dispatch
69 *
70 * @access public
71 * @return void
72 */
73 public function dispatch()
74 {
75 // Schedule the cron healthcheck.
76 $this->schedule_event();
77
78 // Perform remote post.
79 return parent::dispatch();
80 }
81
82 /**
83 * Push to queue
84 *
85 * @param mixed $data Data.
86 *
87 * @return $this
88 */
89 public function push_to_queue($data)
90 {
91 $this->data[] = $data;
92
93 return $this;
94 }
95
96 /**
97 * Save queue
98 *
99 * @return $this
100 */
101 public function save()
102 {
103 $key = $this->generate_key();
104
105 if ( ! empty($this->data)) {
106 update_site_option($key, $this->data);
107 }
108
109 return $this;
110 }
111
112 /**
113 * Update queue
114 *
115 * @param string $key Key.
116 * @param array $data Data.
117 *
118 * @return $this
119 */
120 public function update($key, $data)
121 {
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 {
138 delete_site_option($key);
139
140 return $this;
141 }
142
143 /**
144 * Generate key
145 *
146 * Generates a unique key based on microtime. Queue items are
147 * given a unique key so that they can be merged upon save.
148 *
149 * @param int $length Length.
150 *
151 * @return string
152 */
153 protected function generate_key($length = 64)
154 {
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 {
169 // Don't lock up other requests while processing
170 session_write_close();
171
172 if ($this->is_process_running()) {
173 // Background process already running.
174 wp_die();
175 }
176
177 if ($this->is_queue_empty()) {
178 // No data to process.
179 wp_die();
180 }
181
182 check_ajax_referer($this->identifier, 'nonce');
183
184 $this->handle();
185
186 wp_die();
187 }
188
189 /**
190 * Is queue empty
191 *
192 * @return bool
193 */
194 protected function is_queue_empty()
195 {
196 global $wpdb;
197
198 $table = $wpdb->options;
199 $column = 'option_name';
200
201 if (is_multisite()) {
202 $table = $wpdb->sitemeta;
203 $column = 'meta_key';
204 }
205
206 $key = $this->identifier . '_batch_%';
207
208 $count = $wpdb->get_var(
209 $wpdb->prepare(
210 "
211 SELECT COUNT(*)
212 FROM {$table}
213 WHERE {$column} LIKE %s
214 ",
215 $key
216 )
217 );
218
219 return ($count > 0) ? false : true;
220 }
221
222 /**
223 * Is process running
224 *
225 * Check whether the current process is already running
226 * in a background process.
227 */
228 public function is_process_running()
229 {
230 if (get_site_transient($this->identifier . '_process_lock')) {
231 // Process already running.
232 return true;
233 }
234
235 return false;
236 }
237
238 /**
239 * Lock process
240 *
241 * Lock the process so that multiple instances can't run simultaneously.
242 * Override if applicable, but the duration should be greater than that
243 * defined in the time_exceeded() method.
244 */
245 protected function lock_process()
246 {
247 $this->start_time = time(); // Set start time of current process.
248
249 $lock_duration = (property_exists($this, 'queue_lock_time')) ? $this->queue_lock_time : 60; // 1 minute
250 $lock_duration = apply_filters($this->identifier . '_queue_lock_time', $lock_duration);
251
252 set_site_transient($this->identifier . '_process_lock', microtime(), $lock_duration);
253 }
254
255 /**
256 * Unlock process
257 *
258 * Unlock the process so that other instances can spawn.
259 *
260 * @return $this
261 */
262 protected function unlock_process()
263 {
264 delete_site_transient($this->identifier . '_process_lock');
265
266 return $this;
267 }
268
269 /**
270 * Get batch
271 *
272 * @return stdClass Return the first batch from the queue
273 */
274 protected function get_batch()
275 {
276 global $wpdb;
277
278 $table = $wpdb->options;
279 $column = 'option_name';
280 $key_column = 'option_id';
281 $value_column = 'option_value';
282
283 if (is_multisite()) {
284 $table = $wpdb->sitemeta;
285 $column = 'meta_key';
286 $key_column = 'meta_id';
287 $value_column = 'meta_value';
288 }
289
290 $key = $this->identifier . '_batch_%';
291
292 $query = $wpdb->get_row(
293 $wpdb->prepare(
294 "
295 SELECT *
296 FROM {$table}
297 WHERE {$column} LIKE %s
298 ORDER BY {$key_column} ASC
299 LIMIT 1
300 ",
301 $key
302 )
303 );
304
305 $batch = new \stdClass();
306 $batch->key = $query->$column;
307 $batch->data = maybe_unserialize($query->$value_column);
308
309 return $batch;
310 }
311
312 /**
313 * Handle
314 *
315 * Pass each queue item to the task handler, while remaining
316 * within server memory and time limit constraints.
317 */
318 protected function handle()
319 {
320 $this->lock_process();
321
322 do {
323 $batch = $this->get_batch();
324
325 foreach ($batch->data as $key => $value) {
326 $task = $this->task($value);
327
328 if (false !== $task) {
329 $batch->data[$key] = $task;
330 } else {
331 unset($batch->data[$key]);
332 }
333
334 if ($this->time_exceeded() || $this->memory_exceeded()) {
335 // Batch limits reached.
336 break;
337 }
338 }
339
340 // Update or delete current batch.
341 if ( ! empty($batch->data)) {
342 $this->update($batch->key, $batch->data);
343 } else {
344 $this->delete($batch->key);
345 }
346 } while ( ! $this->time_exceeded() && ! $this->memory_exceeded() && ! $this->is_queue_empty());
347
348 $this->unlock_process();
349
350 // Start next batch or complete process.
351 if ( ! $this->is_queue_empty()) {
352 $this->dispatch();
353 } else {
354 $this->complete();
355 }
356
357 wp_die();
358 }
359
360 /**
361 * Memory exceeded
362 *
363 * Ensures the batch process never exceeds 90%
364 * of the maximum WordPress memory.
365 *
366 * @return bool
367 */
368 protected function memory_exceeded()
369 {
370 $memory_limit = $this->get_memory_limit() * 0.9; // 90% of max memory
371 $current_memory = memory_get_usage(true);
372 $return = false;
373
374 if ($current_memory >= $memory_limit) {
375 $return = true;
376 }
377
378 return apply_filters($this->identifier . '_memory_exceeded', $return);
379 }
380
381 /**
382 * Get memory limit
383 *
384 * @return int
385 */
386 protected function get_memory_limit()
387 {
388 if (function_exists('ini_get')) {
389 $memory_limit = ini_get('memory_limit');
390 } else {
391 // Sensible default.
392 $memory_limit = '128M';
393 }
394
395 if ( ! $memory_limit || -1 === $memory_limit) {
396 // Unlimited, set to 32GB.
397 $memory_limit = '32000M';
398 }
399
400 return intval($memory_limit) * 1024 * 1024;
401 }
402
403 /**
404 * Time exceeded.
405 *
406 * Ensures the batch never exceeds a sensible time limit.
407 * A timeout limit of 30s is common on shared hosting.
408 *
409 * @return bool
410 */
411 protected function time_exceeded()
412 {
413 $finish = $this->start_time + apply_filters($this->identifier . '_default_time_limit', 20); // 20 seconds
414 $return = false;
415
416 if (time() >= $finish) {
417 $return = true;
418 }
419
420 return apply_filters($this->identifier . '_time_exceeded', $return);
421 }
422
423 /**
424 * Complete.
425 *
426 * Override if applicable, but ensure that the below actions are
427 * performed, or, call parent::complete().
428 */
429 protected function complete()
430 {
431 // Unschedule the cron healthcheck.
432 $this->clear_scheduled_event();
433 }
434
435 /**
436 * Schedule cron healthcheck
437 *
438 * @access public
439 *
440 * @param mixed $schedules Schedules.
441 *
442 * @return mixed
443 */
444 public function schedule_cron_healthcheck($schedules)
445 {
446 $interval = apply_filters($this->identifier . '_cron_interval', 5);
447
448 if (property_exists($this, 'cron_interval')) {
449 $interval = apply_filters($this->identifier . '_cron_interval', $this->cron_interval_identifier);
450 }
451
452 // Adds every 5 minutes to the existing schedules.
453 $schedules[$this->identifier . '_cron_interval'] = [
454 'interval' => MINUTE_IN_SECONDS * $interval,
455 'display' => sprintf(__('Every %d Minutes', 'give'), $interval),
456 ];
457
458 return $schedules;
459 }
460
461 /**
462 * Handle cron healthcheck
463 *
464 * Restart the background process if not already running
465 * and data exists in the queue.
466 */
467 public function handle_cron_healthcheck()
468 {
469 if ($this->is_process_running()) {
470 // Background process already running.
471 exit;
472 }
473
474 if ($this->is_queue_empty()) {
475 // No data to process.
476 $this->clear_scheduled_event();
477 exit;
478 }
479
480 $this->handle();
481
482 exit;
483 }
484
485 /**
486 * Schedule event
487 */
488 protected function schedule_event()
489 {
490 if ( ! wp_next_scheduled($this->cron_hook_identifier)) {
491 wp_schedule_event(time(), $this->cron_interval_identifier, $this->cron_hook_identifier);
492 }
493 }
494
495 /**
496 * Clear scheduled event
497 */
498 protected function clear_scheduled_event()
499 {
500 $timestamp = wp_next_scheduled($this->cron_hook_identifier);
501
502 if ($timestamp) {
503 wp_unschedule_event($timestamp, $this->cron_hook_identifier);
504 }
505 }
506
507 /**
508 * Cancel Process
509 *
510 * Stop processing queue items, clear cronjob and delete batch.
511 *
512 */
513 public function cancel_process()
514 {
515 if ( ! $this->is_queue_empty()) {
516 $batch = $this->get_batch();
517
518 $this->delete($batch->key);
519
520 wp_clear_scheduled_hook($this->cron_hook_identifier);
521 }
522 }
523
524 /**
525 * Task
526 *
527 * Override this method to perform any actions required on each
528 * queue item. Return the modified item for further processing
529 * in the next pass through. Or, return false to remove the
530 * item from the queue.
531 *
532 * @param mixed $item Queue item to iterate over.
533 *
534 * @return mixed
535 */
536 abstract protected function task($item);
537 }
538