PluginProbe
ووسلام – همگام سازی ووکامرس و باسلام / 1.10.2
ووسلام – همگام سازی ووکامرس و باسلام v1.10.2
1.10.17 1.10.15 1.10.14 1.10.13 1.10.12 1.10.10 1.10.9 1.10.8 1.10.7 1.10.6 1.10.5 1.10.4 1.10.3 1.10.2 1.10.1 1.10.0 1.9.2 1.9.1 1.9.0 1.8.8 1.8.5 1.8.6 1.8.7 1.8.4 1.7.6 All 50 releases
sync-basalam / AsyncBackgroundProcess.php

AsyncBackgroundProcess.php in ووسلام – همگام سازی ووکامرس و باسلام 1.10.2, at AsyncBackgroundProcess.php

401 lines 9.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace SyncBasalam;
4
5 defined('ABSPATH') || exit;
6
7 abstract class AsyncBackgroundProcess
8 {
9 protected $action = 'async_process';
10
11 protected $identifier;
12
13 protected $data = array();
14
15 protected $timeLimit = 30;
16
17 protected $batchSize = 20;
18
19 protected $cronHook;
20
21 protected $cronInterval = 'every_5_minutes';
22
23 public function __construct()
24 {
25 $this->identifier = $this->getIdentifier();
26 $this->cronHook = $this->identifier . '_cron';
27
28 add_filter('cron_schedules', array($this, 'addCronInterval'));
29
30 add_action('wp_ajax_' . $this->identifier, array($this, 'maybeHandleAsyncRequest'));
31 add_action('wp_ajax_nopriv_' . $this->identifier, array($this, 'maybeHandleAsyncRequest'));
32 add_action($this->cronHook, array($this, 'handleCronHealthcheck'));
33
34 $this->init();
35 }
36
37 protected function init() {}
38
39 protected function getIdentifier()
40 {
41 return $this->action . '_' . substr(md5(get_class($this)), 0, 8);
42 }
43
44 public function addCronInterval($schedules)
45 {
46 $schedules['every_5_minutes'] = array(
47 'interval' => 300,
48 'display' => __('Every 5 Minutes')
49 );
50
51 $schedules['every_minute'] = array(
52 'interval' => 60,
53 'display' => __('Every Minute')
54 );
55
56 return $schedules;
57 }
58
59 public function push($item)
60 {
61 $this->data[] = $item;
62 return $this;
63 }
64
65 public function save()
66 {
67 $key = $this->getBatchKey();
68
69 if (!empty($this->data)) {
70 update_option($key, $this->data, false);
71 }
72
73 $this->data = array();
74
75 return $this;
76 }
77
78 public function dispatch()
79 {
80
81 if (!wp_next_scheduled($this->cronHook)) {
82 wp_schedule_event(time(), $this->cronInterval, $this->cronHook);
83 }
84
85 return $this->triggerAsyncRequest();
86 }
87
88 protected function triggerAsyncRequest()
89 {
90 $url = add_query_arg(
91 array(
92 'action' => $this->identifier,
93 'nonce' => wp_create_nonce($this->identifier)
94 ),
95 admin_url('admin-ajax.php')
96 );
97
98 $args = array(
99 'timeout' => 0.01,
100 'blocking' => false,
101 'body' => array(
102 'action' => $this->identifier,
103 'nonce' => wp_create_nonce($this->identifier)
104 ),
105 'cookies' => $_COOKIE,
106 'sslverify' => apply_filters('https_local_ssl_verify', false),
107 'headers' => array(
108 'X-WP-Async-Request' => $this->identifier
109 )
110 );
111
112 $result = wp_remote_post(esc_url_raw($url), $args);
113
114 if (is_wp_error($result)) return $this->triggerAlternativeAsync();
115
116 return $result;
117 }
118
119 protected function triggerAlternativeAsync()
120 {
121 wp_schedule_single_event(time(), $this->identifier . '_immediate');
122 add_action($this->identifier . '_immediate', array($this, 'handleAsyncRequest'));
123
124 spawn_cron();
125
126 return true;
127 }
128
129 public function maybeHandleAsyncRequest()
130 {
131 check_ajax_referer($this->identifier, 'nonce');
132
133 $this->handleAsyncRequest();
134 }
135
136 public function handleAsyncRequest()
137 {
138 session_write_close();
139 if (!$this->isProcessing()) {
140 $this->handle();
141 }
142 }
143
144 public function handleCronHealthcheck()
145 {
146 if ($this->isQueueEmpty()) {
147
148 $this->clearScheduledEvent();
149 return;
150 }
151
152 $this->triggerAsyncRequest();
153 }
154
155 protected function handle()
156 {
157
158 $this->lockProcess();
159
160 $batch = $this->getBatch();
161
162 if (empty($batch)) {
163 $this->unlockProcess();
164 return;
165 }
166
167 $startTime = time();
168
169 foreach ($batch as $key => $item) {
170 if ($this->timeExceeded($startTime)) {
171 break;
172 }
173
174 if ($this->memoryExceeded()) {
175 break;
176 }
177
178 $item = $this->task($item);
179
180 if (false === $item) {
181 unset($batch[$key]);
182 } else {
183 $batch[$key] = $item;
184 }
185 }
186
187 if (!empty($batch)) {
188
189 $this->updateBatch($batch);
190 } else {
191
192 $this->deleteBatch();
193 }
194
195 $this->unlockProcess();
196
197 if (!$this->isQueueEmpty()) {
198
199 $this->dispatch();
200 } else {
201
202 $this->complete();
203 $this->clearScheduledEvent();
204 }
205 }
206
207 abstract protected function task($item);
208
209 protected function complete() {}
210
211 protected function getBatch()
212 {
213 global $wpdb;
214
215 $table = $wpdb->options;
216 $keyPattern = $this->identifier . '_batch_%';
217
218 $query = $wpdb->prepare("
219 SELECT option_name, option_value
220 FROM {$table}
221 WHERE option_name LIKE %s
222 ORDER BY option_name ASC
223 LIMIT 1
224 ", $keyPattern);
225
226 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom plugin table; no object cache for these operational queries.
227 $batch = $wpdb->get_row($query);
228
229 if (empty($batch)) return array();
230
231 $batchData = maybe_unserialize($batch->option_value);
232
233 if (!is_array($batchData)) return array();
234
235 return array_slice($batchData, 0, $this->batchSize, true);
236 }
237
238 protected function updateBatch($batch)
239 {
240 $batches = $this->getBatches();
241
242 if (!empty($batches)) {
243 $firstBatch = array_shift($batches);
244 update_option($firstBatch->option_name, $batch, false);
245 }
246 }
247
248 protected function deleteBatch()
249 {
250 $batches = $this->getBatches();
251
252 if (!empty($batches)) {
253 $firstBatch = array_shift($batches);
254 delete_option($firstBatch->option_name);
255 }
256 }
257
258 protected function getBatches()
259 {
260 global $wpdb;
261
262 $table = $wpdb->options;
263 $keyPattern = $this->identifier . '_batch_%';
264
265 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom plugin table; no object cache for these operational queries.
266 return $wpdb->get_results(
267 $wpdb->prepare("
268 SELECT option_name
269 FROM {$table}
270 WHERE option_name LIKE %s
271 ORDER BY option_name ASC
272 ", $keyPattern)
273 );
274 }
275
276 protected function getBatchKey()
277 {
278 $key = $this->identifier . '_batch_' . md5(microtime() . mt_rand());
279
280 $count = 1;
281 while (get_option($key) !== false) {
282 $key = $this->identifier . '_batch_' . md5(microtime() . mt_rand() . $count);
283 $count++;
284 }
285
286 return $key;
287 }
288
289 protected function isQueueEmpty()
290 {
291 $batches = $this->getBatches();
292 return empty($batches);
293 }
294
295 protected function lockProcess()
296 {
297 $lockKey = $this->identifier . '_lock';
298 $lockDuration = 60;
299
300 $lock = get_transient($lockKey);
301
302 if ($lock) return false;
303
304 set_transient($lockKey, microtime(true), $lockDuration);
305
306 return true;
307 }
308
309 protected function unlockProcess()
310 {
311 delete_transient($this->identifier . '_lock');
312 }
313
314 protected function isProcessing()
315 {
316 return (bool) get_transient($this->identifier . '_lock');
317 }
318
319 protected function timeExceeded($startTime)
320 {
321 $timeLimit = ini_get('max_execution_time');
322
323 if (empty($timeLimit) || $timeLimit > $this->timeLimit) {
324 $timeLimit = $this->timeLimit;
325 }
326
327 $timeLimit = $timeLimit - 5;
328
329 return (time() - $startTime) > $timeLimit;
330 }
331
332 protected function memoryExceeded()
333 {
334 $memoryLimit = ini_get('memory_limit');
335
336 if ($memoryLimit == '-1') return false;
337
338 $memoryLimit = $this->convertToBytes($memoryLimit);
339 $currentMemory = memory_get_usage(true);
340
341 $buffer = 10 * 1024 * 1024;
342
343 return ($currentMemory + $buffer) > $memoryLimit;
344 }
345
346 protected function convertToBytes($value)
347 {
348 $value = strtolower(trim($value));
349 $bytes = (int) $value;
350
351 if (strpos($value, 'g') !== false) {
352 $bytes *= 1024 * 1024 * 1024;
353 } elseif (strpos($value, 'm') !== false) {
354 $bytes *= 1024 * 1024;
355 } elseif (strpos($value, 'k') !== false) {
356 $bytes *= 1024;
357 }
358
359 return $bytes;
360 }
361
362 protected function clearScheduledEvent()
363 {
364 $timestamp = wp_next_scheduled($this->cronHook);
365
366 if ($timestamp) {
367 wp_unschedule_event($timestamp, $this->cronHook);
368 }
369 }
370
371 public function cancel()
372 {
373 $batches = $this->getBatches();
374
375 foreach ($batches as $batch) {
376 delete_option($batch->option_name);
377 }
378
379 $this->clearScheduledEvent();
380
381 $this->unlockProcess();
382 }
383
384 public function isActive()
385 {
386 if (get_transient($this->identifier . '_lock')) return true;
387
388 $batches = $this->getBatches();
389
390 $hasScheduledCron = wp_next_scheduled($this->cronHook) !== false;
391
392 return !empty($batches) || $hasScheduledCron;
393 }
394
395 public function countBatches()
396 {
397 $batches = $this->getBatches();
398 return count($batches);
399 }
400 }
401