PluginProbe
ووسلام – همگام سازی ووکامرس و باسلام / 1.10.17
ووسلام – همگام سازی ووکامرس و باسلام v1.10.17
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 / includes / Services / Products / UpdateSingleProductService.php

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

278 lines 10.7 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 use SyncBasalam\Config\Endpoints;
6 use SyncBasalam\Services\ApiServiceManager;
7 use SyncBasalam\Jobs\Exceptions\RetryableException;
8 use SyncBasalam\Jobs\Exceptions\NonRetryableException;
9 use SyncBasalam\Utilities\ProductMetaKey;
10 use SyncBasalam\Services\VendorSyncPolicy;
11
12 defined('ABSPATH') || exit;
13
14 class UpdateSingleProductService
15 {
16 private $apiservice;
17 private $variationsService;
18
19 public function __construct()
20 {
21 $this->apiservice = syncBasalamContainer()->get(ApiServiceManager::class);
22 $this->variationsService = syncBasalamContainer()->get(UpdateProductVariationsService::class);
23 }
24
25 public function updateProductInBasalam($productData, $productId)
26 {
27 if (!get_post_type($productId) === 'product') throw NonRetryableException::invalidData('نوع post �
28 حصول نیست.');
29
30 $vendorSyncPolicy = syncBasalamContainer()->get(VendorSyncPolicy::class);
31 if (!$vendorSyncPolicy->canUpdate()) {
32 throw NonRetryableException::invalidData($vendorSyncPolicy->getRestrictionMessage(false));
33 }
34
35 $productData = apply_filters('sync_basalam_product_data_before_update', $productData, $productId);
36 $productData = $vendorSyncPolicy->restrictUpdatePayload($productData, false);
37
38 do_action('sync_basalam_before_update_product_api', $productId, $productData);
39
40 $syncBasalamProductId = get_post_meta($productId, ProductMetaKey::basalamProductId(), true);
41 ProductConnection::assertUnique($productId, $syncBasalamProductId);
42
43 // Variable products whose variations are all connected to Basalam are updated with one
44 // request per variation, so price and stock must not be part of the product payload.
45 if ($this->shouldUpdateVariationsSeparately($productId, $productData)) {
46 $this->variationsService->updateVariations($syncBasalamProductId, $productData['variants'], $productId);
47
48 unset($productData['variants'], $productData['primary_price'], $productData['stock']);
49
50 if (!$this->hasProductFieldsToUpdate($productData)) {
51 return $this->finishUpdate($productId, [], '�
52 تغیرهای �
53 حصول با �
54 وفقیت بروزرسانی شدند.');
55 }
56 }
57 $url = sprintf(Endpoints::PRODUCT_UPDATE, $syncBasalamProductId);
58
59 $maxDescriptionRetries = 3;
60 $descriptionRetry = 0;
61
62 while (true) {
63 try {
64 $request = $this->apiservice->patch($url, $productData);
65 break;
66 } catch (RetryableException $e) {
67 throw $e;
68 } catch (NonRetryableException $e) {
69 if ($descriptionRetry < $maxDescriptionRetries && $this->stripForbiddenDescription($e, $productData, $productId, $descriptionRetry)) {
70 $descriptionRetry++;
71 continue;
72 }
73 throw $e;
74 } catch (\Exception $e) {
75 throw new \Exception(esc_html('خطا در ارتباط با API باسلا�
76 : ' . $e->getMessage()));
77 }
78 }
79
80 $body = $request['body'] ?? '';
81
82 if (is_string($body)) $body = json_decode($body, true);
83
84 if ($request['status_code'] != 200) {
85 if ($request['status_code'] == 403) throw NonRetryableException::unauthorized("این �
86 حصول �
87 تعلق به غرفه فعلی نیست.");
88
89 if (!is_array($body)) $body = [];
90
91 if (isset($body['messages'][0]['message'])) $message = $body['messages'][0]['message'];
92 elseif (isset($body[0]['message'])) $message = $body[0]['message'];
93 else $message = '';
94
95 if (isset($body['messages'][0]['fields'][0])) $field = $body['messages'][0]['fields'][0];
96 elseif (isset($body[0]['fields'][0])) $field = $body[0]['fields'][0];
97 else $field = '';
98
99 $errorMessage = $message ?: 'درخواست با خطا �
100 واجه شد.';
101 if ($field) $errorMessage .= ' (فیلد: ' . $field . ')';
102
103 throw NonRetryableException::permanent(esc_html($errorMessage));
104 }
105
106 if (is_wp_error($request)) throw NonRetryableException::permanent('خطایی در ارتباط با سرور رخ داد.');
107
108 $product = \wc_get_product($productId);
109 if ($product && $product->is_type('variable')) {
110 $variations = $product->get_children();
111 if (isset($body['variants'])) {
112 $wcVariations = [];
113 $attributes = $product->get_attributes();
114
115 foreach ($variations as $variationId) {
116 $variation = \wc_get_product($variationId);
117 $attributeValues = [];
118
119 foreach ($attributes as $attributeName => $attribute) {
120 if ($attribute->get_variation()) {
121 $cleanAttributeName = str_replace('attribute_', '', $attributeName);
122 $value = $variation->get_attribute($cleanAttributeName);
123
124 $value = urldecode($value);
125 $value = trim($value);
126 $value = mb_strtolower($value, 'UTF-8');
127 $value = str_replace(['ي', 'ك'], ['ی', 'ک'], $value);
128 $value = str_replace(['-', '_', '', ''], ' ', $value);
129 $value = preg_replace('/\s+/', ' ', $value);
130
131 if (!empty($value)) $attributeValues[] = $value;
132 }
133 }
134
135 if (!empty($attributeValues)) {
136 $key = implode("_", $attributeValues);
137 $wcVariations[$key] = $variationId;
138 }
139 }
140
141 $syncBasalamVariations = [];
142 foreach ($body['variants'] as $variant) {
143 $attributeValues = [];
144 if (!empty($variant['properties'])) {
145 foreach ($variant['properties'] as $property) {
146 $val = $property['value']['title'];
147
148 $val = trim($val);
149 $val = mb_strtolower($val, 'UTF-8');
150 $val = str_replace(['ي', 'ك'], ['ی', 'ک'], $val);
151 $val = str_replace(['-', '_', '', ''], ' ', $val);
152 $val = preg_replace('/\s+/', ' ', $val);
153
154 if (!empty($val)) $attributeValues[] = $val;
155 }
156 }
157
158 if (!empty($attributeValues)) {
159 $key = implode("_", $attributeValues);
160 $syncBasalamVariations[$key] = $variant['id'];
161 }
162 }
163
164 foreach ($wcVariations as $key => $wcVarId) {
165 if (isset($syncBasalamVariations[$key])) {
166 update_post_meta($wcVarId, 'sync_basalam_variation_id', $syncBasalamVariations[$key]);
167 }
168 }
169 }
170 }
171
172 return $this->finishUpdate($productId, $body, 'فرایند بروزرسانی �
173 حصول با �
174 وفقیت انجا�
175 شد.');
176 }
177
178 private function finishUpdate($productId, $body, string $message): array
179 {
180 update_post_meta($productId, ProductMetaKey::basalamProductSyncStatus(), 'synced');
181
182 $result = [
183 'success' => true,
184 'message' => $message,
185 'status_code' => 200,
186 ];
187
188 do_action('sync_basalam_after_update_product_api', $productId, $body, $result);
189
190 return $result;
191 }
192
193 private function shouldUpdateVariationsSeparately($productId, array $productData): bool
194 {
195 if (empty($productData['variants']) || !is_array($productData['variants'])) return false;
196
197 $product = \wc_get_product($productId);
198 if (!$product || !$product->is_type('variable')) return false;
199
200 return UpdateProductVariationsService::allVariantsHaveBasalamId($productData['variants']);
201 }
202
203 private function hasProductFieldsToUpdate(array $productData): bool
204 {
205 $identifiers = ['id' => true, 'type' => true];
206
207 return !empty(array_diff_key($productData, $identifiers));
208 }
209
210 private function stripForbiddenDescription(NonRetryableException $e, array &$productData, int $productId, int $attempt): bool
211 {
212 if (!isset($productData['description']) || !is_string($productData['description'])) return false;
213
214 $values = DescriptionErrorSanitizer::extractDescriptionValues($e->getResponseData());
215 if (empty($values)) return false;
216
217 $cleaned = DescriptionErrorSanitizer::sanitize($productData['description'], $values);
218 if ($cleaned === $productData['description']) return false;
219
220 $productData['description'] = $cleaned;
221
222 return true;
223 }
224
225 public function updateProductStatus($productId, $status)
226 {
227 $vendorSyncPolicy = syncBasalamContainer()->get(VendorSyncPolicy::class);
228 if (!$vendorSyncPolicy->canUpdateProductStatus()) {
229 throw NonRetryableException::invalidData($vendorSyncPolicy->getRestrictionMessage(false));
230 }
231
232 $syncBasalamProductId = get_post_meta($productId, ProductMetaKey::basalamProductId(), true);
233
234 ProductConnection::assertUnique($productId, $syncBasalamProductId);
235
236 $url = sprintf(Endpoints::PRODUCT_UPDATE, $syncBasalamProductId);
237
238 $data = ["status" => $status];
239
240 $data = apply_filters('sync_basalam_product_status_data_before_update', $data, $productId, $status);
241
242 do_action('sync_basalam_before_update_product_status', $productId, $status, $data);
243
244 try {
245 $request = $this->apiservice->patch($url, $data);
246 } catch (RetryableException $e) {
247 throw $e;
248 } catch (NonRetryableException $e) {
249 throw $e;
250 } catch (\Exception $e) {
251 throw NonRetryableException::permanent(esc_html($e->getMessage()));
252 }
253
254 if (!is_wp_error($request)) {
255 update_post_meta($productId, ProductMetaKey::basalamProductSyncStatus(), 'synced');
256 update_post_meta($productId, ProductMetaKey::basalamProductStatus(), $status);
257
258 $result = [
259 'success' => true,
260 'message' => 'وضعیت �
261 حصول با �
262 وفقیت در باسلا�
263 تغییر کرد.',
264 'status_code' => 200,
265 ];
266
267 do_action('sync_basalam_after_update_product_status', $productId, $status, $result);
268
269 return $result;
270 }
271
272 throw NonRetryableException::permanent("تغییر وضعیت �
273 حصول در باسلا�
274 نا�
275 وفق بود.");
276 }
277 }
278