PluginProbe
ووسلام – همگام سازی ووکامرس و باسلام / 1.10.20
ووسلام – همگام سازی ووکامرس و باسلام v1.10.20
1.10.21 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 All 54 releases
sync-basalam / includes / Services / Products / ProductSkuRetry.php

ProductSkuRetry.php in ووسلام – همگام سازی ووکامرس و باسلام 1.10.20, at includes/Services/Products/ProductSkuRetry.php

247 lines 7.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\Services\Products;
4
5 defined('ABSPATH') || exit;
6
7 /**
8 * Helpers for handling Basalam's duplicate-SKU validation error.
9 */
10 final class ProductSkuRetry
11 {
12 /**
13 * Return true when the payload contains a non-empty SKU at any level.
14 */
15 public static function hasSku(array $payload): bool
16 {
17 return self::containsSkuKey($payload);
18 }
19
20 /**
21 * Remove product and variation SKU fields while preserving the rest of the payload.
22 */
23 public static function withoutSkus(array $payload): array
24 {
25 self::removeSkuKeys($payload);
26
27 return $payload;
28 }
29
30 /**
31 * Detect a duplicate-SKU error from either an exception or an API response.
32 *
33 * Basalam has returned this validation in more than one shape, so both the
34 * field names/codes and human-readable messages are considered here.
35 *
36 * @param mixed $source Throwable, API response, response body, or message.
37 */
38 public static function isDuplicateSkuError($source): bool
39 {
40 if (is_array($source) && self::isSuccessfulResponse($source)) return false;
41 if (is_string($source)) {
42 $decoded = json_decode($source, true);
43 if (is_array($decoded) && self::isSuccessfulResponse($decoded)) return false;
44 }
45
46 $parts = [];
47 self::collectText($source, $parts);
48
49 if (is_object($source) && $source instanceof \Throwable) {
50 $parts[] = $source->getMessage();
51
52 if (method_exists($source, 'getResponseData')) {
53 $responseData = $source->getResponseData();
54 if (self::containsSkuField($responseData)) return true;
55
56 self::collectText($responseData, $parts);
57 }
58 }
59
60 if (is_array($source) && self::containsSkuField($source)) return true;
61
62 if (empty($parts)) return false;
63
64 $text = self::normalize(implode(' ', $parts));
65
66 return self::containsSkuReference($text) && self::containsDuplicateMarker($text);
67 }
68
69 private static function containsSkuField($value): bool
70 {
71 if (is_string($value)) {
72 $decoded = json_decode($value, true);
73
74 return is_array($decoded) && self::containsSkuField($decoded);
75 }
76
77 if (!is_array($value)) return false;
78
79 foreach ($value as $key => $item) {
80 if (in_array(strtolower((string) $key), ['fields', 'field', 'parameter', 'path'], true) && self::fieldsContainSku($item)) {
81 return true;
82 }
83
84 if (self::containsSkuField($item)) return true;
85 }
86
87 return false;
88 }
89
90 private static function fieldsContainSku($fields): bool
91 {
92 if (is_string($fields)) return self::containsSkuReference(self::normalize($fields));
93 if (!is_array($fields)) return false;
94
95 foreach ($fields as $field) {
96 if (is_string($field) && self::containsSkuReference(self::normalize($field))) return true;
97 if (is_array($field) && self::fieldsContainSku($field)) return true;
98 }
99
100 return false;
101 }
102
103 private static function containsSkuKey($value): bool
104 {
105 if (!is_array($value)) return false;
106
107 foreach ($value as $key => $item) {
108 if (strcasecmp((string) $key, 'sku') === 0 && self::hasValue($item)) return true;
109 if (is_array($item) && self::containsSkuKey($item)) return true;
110 }
111
112 return false;
113 }
114
115 private static function removeSkuKeys(array &$value): void
116 {
117 foreach ($value as $key => &$item) {
118 if (strcasecmp((string) $key, 'sku') === 0) {
119 unset($value[$key]);
120 continue;
121 }
122
123 if (is_array($item)) self::removeSkuKeys($item);
124 }
125
126 unset($item);
127 }
128
129 private static function hasValue($value): bool
130 {
131 if ($value === null) return false;
132 if (is_string($value)) return trim($value) !== '';
133
134 return $value !== '';
135 }
136
137 private static function isSuccessfulResponse(array $response): bool
138 {
139 if (!array_key_exists('status_code', $response)) return false;
140
141 $statusCode = (int) $response['status_code'];
142
143 return $statusCode >= 200 && $statusCode < 300;
144 }
145
146 private static function collectText($value, array &$parts, string $key = ''): void
147 {
148 if ($key !== '') $parts[] = $key;
149
150 if (is_array($value)) {
151 foreach ($value as $childKey => $childValue) {
152 self::collectText($childValue, $parts, (string) $childKey);
153 }
154
155 return;
156 }
157
158 if (is_string($value)) {
159 $decoded = json_decode($value, true);
160 if (is_array($decoded)) {
161 self::collectText($decoded, $parts);
162 return;
163 }
164
165 $parts[] = $value;
166 return;
167 }
168
169 if (is_scalar($value)) $parts[] = (string) $value;
170
171 if (is_object($value) && method_exists($value, 'get_error_message')) {
172 $parts[] = (string) $value->get_error_message();
173 }
174 }
175
176 private static function normalize(string $text): string
177 {
178 $text = function_exists('mb_strtolower')
179 ? mb_strtolower($text, 'UTF-8')
180 : strtolower($text);
181
182 $text = str_replace(["\xC2\xA0", "\xE2\x80\x8C"], ' ', $text);
183
184 return preg_replace('/[_\-.]+/u', ' ', $text) ?: $text;
185 }
186
187 private static function containsSkuReference(string $text): bool
188 {
189 if (preg_match('/(^|[^a-z0-9])sku([^a-z0-9]|$)/i', $text)) return true;
190 if (strpos($text, 'stock keeping unit') !== false) return true;
191
192 return strpos($text, 'اس کی یو') !== false
193 || strpos($text, 'اسکیو') !== false
194 || strpos($text, 'کد �
195 حصول') !== false
196 || strpos($text, 'کد کالا') !== false
197 || strpos($text, 'شناسه �
198 حصول') !== false
199 || strpos($text, 'شناسه کالا') !== false;
200 }
201
202 private static function containsDuplicateMarker(string $text): bool
203 {
204 foreach ([
205 'duplicate',
206 'duplicated',
207 'already exists',
208 'already exist',
209 'already taken',
210 'already used',
211 'already registered',
212 'sku exists',
213 'sku taken',
214 'sku used',
215 'sku registered',
216 'has been taken',
217 'has been used',
218 'is already registered',
219 'taken',
220 'in use',
221 'must be unique',
222 'unique',
223 'unique constraint',
224 'unique violation',
225 'conflict',
226 'تکراری',
227 'تکرار شده',
228 'قبلا وجود',
229 'قبلاً وجود',
230 'قبلا استفاده',
231 'قبلاً استفاده',
232 'قبلا ثبت',
233 'قبلاً ثبت',
234 'از قبل وجود',
235 'استفاده شده',
236 'ثبت شده',
237 'گرفته شده',
238 'وجود دارد',
239 'یکتا',
240 ] as $marker) {
241 if (strpos($text, $marker) !== false) return true;
242 }
243
244 return false;
245 }
246 }
247