PluginProbe
ووسلام – همگام سازی ووکامرس و باسلام / 1.8.6
ووسلام – همگام سازی ووکامرس و باسلام v1.8.6
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.6, at JobManager.php

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