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