PluginProbe
ووسلام – همگام سازی ووکامرس و باسلام / 1.9.2
ووسلام – همگام سازی ووکامرس و باسلام v1.9.2
1.10.19 1.10.20 1.10.18 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 All 53 releases
sync-basalam / AsyncBackgroundProcess.php

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

394 lines 9.2 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, 'handleAsyncRequest'));
31 add_action('wp_ajax_nopriv_' . $this->identifier, array($this, 'handleAsyncRequest'));
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 handleAsyncRequest()
130 {
131 session_write_close();
132 if (!$this->isProcessing()) {
133 $this->handle();
134 }
135 }
136
137 public function handleCronHealthcheck()
138 {
139 if ($this->isQueueEmpty()) {
140
141 $this->clearScheduledEvent();
142 return;
143 }
144
145 $this->triggerAsyncRequest();
146 }
147
148 protected function handle()
149 {
150
151 $this->lockProcess();
152
153 $batch = $this->getBatch();
154
155 if (empty($batch)) {
156 $this->unlockProcess();
157 return;
158 }
159
160 $startTime = time();
161
162 foreach ($batch as $key => $item) {
163 if ($this->timeExceeded($startTime)) {
164 break;
165 }
166
167 if ($this->memoryExceeded()) {
168 break;
169 }
170
171 $item = $this->task($item);
172
173 if (false === $item) {
174 unset($batch[$key]);
175 } else {
176 $batch[$key] = $item;
177 }
178 }
179
180 if (!empty($batch)) {
181
182 $this->updateBatch($batch);
183 } else {
184
185 $this->deleteBatch();
186 }
187
188 $this->unlockProcess();
189
190 if (!$this->isQueueEmpty()) {
191
192 $this->dispatch();
193 } else {
194
195 $this->complete();
196 $this->clearScheduledEvent();
197 }
198 }
199
200 abstract protected function task($item);
201
202 protected function complete() {}
203
204 protected function getBatch()
205 {
206 global $wpdb;
207
208 $table = $wpdb->options;
209 $keyPattern = $this->identifier . '_batch_%';
210
211 $query = $wpdb->prepare("
212 SELECT option_name, option_value
213 FROM {$table}
214 WHERE option_name LIKE %s
215 ORDER BY option_name ASC
216 LIMIT 1
217 ", $keyPattern);
218
219 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom plugin table; no object cache for these operational queries.
220 $batch = $wpdb->get_row($query);
221
222 if (empty($batch)) return array();
223
224 $batchData = maybe_unserialize($batch->option_value);
225
226 if (!is_array($batchData)) return array();
227
228 return array_slice($batchData, 0, $this->batchSize, true);
229 }
230
231 protected function updateBatch($batch)
232 {
233 $batches = $this->getBatches();
234
235 if (!empty($batches)) {
236 $firstBatch = array_shift($batches);
237 update_option($firstBatch->option_name, $batch, false);
238 }
239 }
240
241 protected function deleteBatch()
242 {
243 $batches = $this->getBatches();
244
245 if (!empty($batches)) {
246 $firstBatch = array_shift($batches);
247 delete_option($firstBatch->option_name);
248 }
249 }
250
251 protected function getBatches()
252 {
253 global $wpdb;
254
255 $table = $wpdb->options;
256 $keyPattern = $this->identifier . '_batch_%';
257
258 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom plugin table; no object cache for these operational queries.
259 return $wpdb->get_results(
260 $wpdb->prepare("
261 SELECT option_name
262 FROM {$table}
263 WHERE option_name LIKE %s
264 ORDER BY option_name ASC
265 ", $keyPattern)
266 );
267 }
268
269 protected function getBatchKey()
270 {
271 $key = $this->identifier . '_batch_' . md5(microtime() . mt_rand());
272
273 $count = 1;
274 while (get_option($key) !== false) {
275 $key = $this->identifier . '_batch_' . md5(microtime() . mt_rand() . $count);
276 $count++;
277 }
278
279 return $key;
280 }
281
282 protected function isQueueEmpty()
283 {
284 $batches = $this->getBatches();
285 return empty($batches);
286 }
287
288 protected function lockProcess()
289 {
290 $lockKey = $this->identifier . '_lock';
291 $lockDuration = 60;
292
293 $lock = get_transient($lockKey);
294
295 if ($lock) return false;
296
297 set_transient($lockKey, microtime(true), $lockDuration);
298
299 return true;
300 }
301
302 protected function unlockProcess()
303 {
304 delete_transient($this->identifier . '_lock');
305 }
306
307 protected function isProcessing()
308 {
309 return (bool) get_transient($this->identifier . '_lock');
310 }
311
312 protected function timeExceeded($startTime)
313 {
314 $timeLimit = ini_get('max_execution_time');
315
316 if (empty($timeLimit) || $timeLimit > $this->timeLimit) {
317 $timeLimit = $this->timeLimit;
318 }
319
320 $timeLimit = $timeLimit - 5;
321
322 return (time() - $startTime) > $timeLimit;
323 }
324
325 protected function memoryExceeded()
326 {
327 $memoryLimit = ini_get('memory_limit');
328
329 if ($memoryLimit == '-1') return false;
330
331 $memoryLimit = $this->convertToBytes($memoryLimit);
332 $currentMemory = memory_get_usage(true);
333
334 $buffer = 10 * 1024 * 1024;
335
336 return ($currentMemory + $buffer) > $memoryLimit;
337 }
338
339 protected function convertToBytes($value)
340 {
341 $value = strtolower(trim($value));
342 $bytes = (int) $value;
343
344 if (strpos($value, 'g') !== false) {
345 $bytes *= 1024 * 1024 * 1024;
346 } elseif (strpos($value, 'm') !== false) {
347 $bytes *= 1024 * 1024;
348 } elseif (strpos($value, 'k') !== false) {
349 $bytes *= 1024;
350 }
351
352 return $bytes;
353 }
354
355 protected function clearScheduledEvent()
356 {
357 $timestamp = wp_next_scheduled($this->cronHook);
358
359 if ($timestamp) {
360 wp_unschedule_event($timestamp, $this->cronHook);
361 }
362 }
363
364 public function cancel()
365 {
366 $batches = $this->getBatches();
367
368 foreach ($batches as $batch) {
369 delete_option($batch->option_name);
370 }
371
372 $this->clearScheduledEvent();
373
374 $this->unlockProcess();
375 }
376
377 public function isActive()
378 {
379 if (get_transient($this->identifier . '_lock')) return true;
380
381 $batches = $this->getBatches();
382
383 $hasScheduledCron = wp_next_scheduled($this->cronHook) !== false;
384
385 return !empty($batches) || $hasScheduledCron;
386 }
387
388 public function countBatches()
389 {
390 $batches = $this->getBatches();
391 return count($batches);
392 }
393 }
394