PluginProbe
ووسلام – همگام سازی ووکامرس و باسلام / 1.8.8
ووسلام – همگام سازی ووکامرس و باسلام v1.8.8
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 / JobManager.php

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

270 lines 8.0 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 class JobManager
8 {
9 private $jobManagerTableName;
10
11 private const ALLOWED_COLUMNS = [
12 'id', 'job_type', 'status', 'payload',
13 'attempts', 'max_attempts', 'retry_after',
14 'started_at', 'created_at', 'failed_at', 'error_message',
15 ];
16
17 function __construct()
18 {
19 global $wpdb;
20 $this->jobManagerTableName = $wpdb->prefix . 'sync_basalam_job_manager';
21 }
22
23 public function createJob($jobType, $status = 'pending', $payload = null, $maxAttempts = 3)
24 {
25 global $wpdb;
26
27 return $wpdb->insert(
28 $this->jobManagerTableName,
29 array(
30 'job_type' => $jobType,
31 'status' => $status,
32 'payload' => $payload,
33 'attempts' => 0,
34 'max_attempts' => $maxAttempts,
35 'created_at' => time(),
36 )
37 );
38 }
39
40 public function getNextEligibleJob(string $jobType): ?object
41 {
42 global $wpdb;
43
44 return $wpdb->get_row($wpdb->prepare(
45 "SELECT * FROM {$this->jobManagerTableName}
46 WHERE job_type = %s
47 AND status = 'pending'
48 AND (retry_after IS NULL OR retry_after <= %d)
49 ORDER BY id ASC
50 LIMIT 1",
51 $jobType,
52 time()
53 ));
54 }
55
56 public function hasAnyProcessingJob(): bool
57 {
58 return $this->getCountJobs(['status' => 'processing']) > 0;
59 }
60
61 public function getJob($where = array())
62 {
63 global $wpdb;
64
65 if (empty($where)) return null;
66
67 $conditions = [];
68 $values = [];
69
70 foreach ($where as $column => $value) {
71 if (!in_array($column, self::ALLOWED_COLUMNS, true)) {
72 throw new \InvalidArgumentException("Invalid column: {$column}");
73 }
74 $conditions[] = "{$column} = %s";
75 $values[] = $value;
76 }
77
78 $sql = "SELECT * FROM {$this->jobManagerTableName} WHERE " . implode(" AND ", $conditions) . " LIMIT 1";
79
80 return $wpdb->get_row($wpdb->prepare($sql, $values));
81 }
82
83 public function getCountJobs($where = array())
84 {
85 global $wpdb;
86
87 if (empty($where)) return 0;
88
89 $conditions = [];
90 $values = [];
91
92 foreach ($where as $column => $value) {
93 if (!in_array($column, self::ALLOWED_COLUMNS, true)) {
94 throw new \InvalidArgumentException("Invalid column: {$column}");
95 }
96 if (is_array($value)) {
97 $placeholders = array_fill(0, count($value), '%s');
98 $conditions[] = "{$column} IN (" . implode(',', $placeholders) . ")";
99 $values = array_merge($values, $value);
100 } else {
101 $conditions[] = "{$column} = %s";
102 $values[] = $value;
103 }
104 }
105
106 $sql = "SELECT COUNT(*) FROM {$this->jobManagerTableName} WHERE " . implode(" AND ", $conditions);
107
108 return (int) $wpdb->get_var($wpdb->prepare($sql, $values));
109 }
110
111 public function updateJob($jobData, $where = array())
112 {
113 global $wpdb;
114
115 if (empty($where) || empty($jobData)) return false;
116
117 return $wpdb->update($this->jobManagerTableName, $jobData, $where);
118 }
119
120 public function deleteJob($where = array())
121 {
122 global $wpdb;
123
124 if (empty($where)) return false;
125
126 return $wpdb->delete($this->jobManagerTableName, $where);
127 }
128
129 public function ConvertStaleProcessingJobs($timeoutSeconds = 120)
130 {
131 global $wpdb;
132
133 $timeoutTimestamp = time() - $timeoutSeconds;
134
135 $wpdb->query(
136 $wpdb->prepare(
137 "UPDATE {$this->jobManagerTableName}
138 SET status = CASE
139 WHEN attempts + 1 >= max_attempts THEN 'failed'
140 ELSE 'pending'
141 END,
142 attempts = attempts + 1,
143 started_at = NULL,
144 failed_at = CASE
145 WHEN attempts + 1 >= max_attempts THEN %d
146 ELSE failed_at
147 END
148 WHERE status = 'processing'
149 AND started_at IS NOT NULL
150 AND started_at < %d",
151 time(),
152 $timeoutTimestamp
153 )
154 );
155 }
156
157 public function hasProductJobInProgress(int $productId, string $jobType): bool
158 {
159 global $wpdb;
160
161 $jobs = $wpdb->get_results($wpdb->prepare(
162 "SELECT payload FROM {$this->jobManagerTableName}
163 WHERE job_type = %s
164 AND (status = %s OR status = %s)",
165 $jobType,
166 'pending',
167 'processing'
168 ));
169
170 if (empty($jobs)) {
171 return false;
172 }
173
174 foreach ($jobs as $job) {
175 $payload = json_decode($job->payload, true);
176 $jobProductId = $payload['product_id'] ?? $payload;
177
178 if (intval($jobProductId) === intval($productId)) {
179 return true;
180 }
181 }
182
183 return false;
184 }
185
186 public function retryJob(int $jobId, ?string $errorMessage = null): bool
187 {
188 global $wpdb;
189
190 $job = $wpdb->get_row($wpdb->prepare(
191 "SELECT * FROM {$this->jobManagerTableName} WHERE id = %d",
192 $jobId
193 ));
194
195 if (!$job) return false;
196
197 $newAttempts = intval($job->attempts) + 1;
198
199 $errorMessages = [];
200 if (!empty($job->error_message)) {
201 $decoded = json_decode($job->error_message, true);
202 if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) $errorMessages = $decoded;
203 }
204
205 if ($errorMessage) $errorMessages[$newAttempts] = $errorMessage;
206
207 $encodedErrors = json_encode($errorMessages, JSON_UNESCAPED_UNICODE);
208
209 if ($newAttempts >= intval($job->max_attempts)) {
210 $this->updateJob(
211 [
212 'status' => 'failed',
213 'error_message' => $encodedErrors,
214 'failed_at' => time(),
215 'started_at' => 0,
216 'attempts' => $newAttempts,
217 ],
218 ['id' => $jobId]
219 );
220 return false;
221 }
222
223 // Progressive exponential backoff: 30s, 60s, 120s, 240s, ...
224 $delaySeconds = 30 * (int) pow(2, $newAttempts - 1);
225 $retryAfter = time() + $delaySeconds;
226
227 // Atomic DELETE + INSERT inside a transaction so a crash can't lose the job.
228 $wpdb->query('START TRANSACTION');
229 try {
230 $wpdb->delete($this->jobManagerTableName, ['id' => $jobId]);
231
232 $wpdb->insert(
233 $this->jobManagerTableName,
234 [
235 'job_type' => $job->job_type,
236 'status' => 'pending',
237 'payload' => $job->payload,
238 'attempts' => $newAttempts,
239 'max_attempts' => $job->max_attempts,
240 'error_message' => $encodedErrors,
241 'created_at' => $job->created_at,
242 'retry_after' => $retryAfter,
243 'started_at' => 0,
244 ]
245 );
246
247 $wpdb->query('COMMIT');
248 } catch (\Exception $e) {
249 $wpdb->query('ROLLBACK');
250 throw $e;
251 }
252
253 return true;
254 }
255
256 public function failJob(int $jobId, ?string $errorMessage = null): bool
257 {
258 return $this->updateJob(
259 [
260 'status' => 'failed',
261 'error_message' => $errorMessage,
262 'failed_at' => time(),
263 'started_at' => 0,
264 ],
265 ['id' => $jobId]
266 );
267 }
268
269 }
270