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
← All changes | includes/Services/Products/CreateSingleProductService.php +220 -159 1.8.51.10.20 View file →
@@ -1,178 +1,239 @@
1 -<?php
2 -
3 -namespace SyncBasalam\Services\Products;
4 -
5 -use SyncBasalam\Admin\Product\Data\Validators\CreateProductDataValidator;
6 -use SyncBasalam\Admin\Settings\SettingsConfig;
7 -use SyncBasalam\Config\Endpoints;
8 -use SyncBasalam\Services\ApiServiceManager;
9 -use SyncBasalam\Jobs\Exceptions\RetryableException;
10 -use SyncBasalam\Jobs\Exceptions\NonRetryableException;
1 +<?php
2 +
3 +namespace SyncBasalam\Services\Products;
4 +
5 +use SyncBasalam\Admin\Product\Data\Validators\CreateProductDataValidator;
6 +use SyncBasalam\Admin\Settings\SettingsConfig;
7 +use SyncBasalam\Config\Endpoints;
8 +use SyncBasalam\Services\ApiServiceManager;
9 +use SyncBasalam\Jobs\Exceptions\RetryableException;
10 +use SyncBasalam\Jobs\Exceptions\NonRetryableException;
11 11 use SyncBasalam\Utilities\ProductMetaKey;
12 -
13 -defined('ABSPATH') || exit;
14 -
15 -class CreateSingleProductService
16 -{
17 - private $apiservice;
18 - private $validator;
19 -
20 - public function __construct($validator = null)
21 - {
22 - $this->apiservice = syncBasalamContainer()->get(ApiServiceManager::class);
23 - $this->validator = $validator ?: new CreateProductDataValidator();
24 - }
25 -
12 +use SyncBasalam\Services\VendorSyncPolicy;
13 +
14 +defined('ABSPATH') || exit;
15 +
16 +class CreateSingleProductService
17 +{
18 + private $apiservice;
19 + private $validator;
20 +
21 + public function __construct($validator = null)
22 + {
23 + $this->apiservice = syncBasalamContainer()->get(ApiServiceManager::class);
24 + $this->validator = $validator ?: new CreateProductDataValidator();
25 + }
26 +
26 27 public function createProductInBasalam($productData, $productId)
27 28 {
28 - $productData = apply_filters('sync_basalam_product_data_before_create', $productData, $productId);
29 - $this->ValidCreateProductData($productData, $productId);
30 -
31 - do_action('sync_basalam_before_create_product_api', $productId, $productData);
32 -
33 - $vendorId = syncBasalamSettings()->getSettings(SettingsConfig::VENDOR_ID);
34 -
35 - $url = sprintf(Endpoints::PRODUCT_CREATE, $vendorId);
36 -
37 - try {
38 - $request = $this->apiservice->post($url, $productData);
39 - } catch (RetryableException $e) {
40 - throw $e;
41 - } catch (NonRetryableException $e) {
42 - throw $e;
43 - } catch (\Exception $e) {
44 - throw new \Exception($e->getMessage());
29 + $vendorSyncPolicy = syncBasalamContainer()->get(VendorSyncPolicy::class);
30 + if (!$vendorSyncPolicy->canCreate()) {
31 + throw NonRetryableException::invalidData($vendorSyncPolicy->getRestrictionMessage(false));
45 32 }
46 33
47 - if ($request['status_code'] != 201 && isset($request['status_code'])) {
34 + $productData = apply_filters('sync_basalam_product_data_before_create', $productData, $productId);
35 + $this->ValidCreateProductData($productData, $productId);
36 +
37 + do_action('sync_basalam_before_create_product_api', $productId, $productData);
38 +
39 + $vendorId = syncBasalamSettings()->getSettings(SettingsConfig::VENDOR_ID);
40 +
41 + $url = sprintf(Endpoints::PRODUCT_CREATE, $vendorId);
42 +
43 + $maxDescriptionRetries = 3;
44 + $descriptionRetry = 0;
45 + $skuRetry = false;
48 46
49 - $body = $request['body'] ?? '';
47 + while (true) {
48 + try {
49 + $request = $this->apiservice->post($url, $productData);
50 + } catch (RetryableException $e) {
51 + if ($this->retryWithoutDuplicateSku($productData, $e, $skuRetry)) {
52 + continue;
53 + }
50 54
51 - if (is_string($body))
52 - $responseData = json_decode($body, true);
53 - else $responseData = $body;
55 + throw $e;
56 + } catch (NonRetryableException $e) {
57 + if ($this->retryWithoutDuplicateSku($productData, $e, $skuRetry)) {
58 + continue;
59 + }
54 60
55 - if (!is_array($responseData)) $responseData = [];
61 + if ($descriptionRetry < $maxDescriptionRetries && $this->stripForbiddenDescription($e, $productData, $productId, $descriptionRetry)) {
62 + $descriptionRetry++;
63 + continue;
64 + }
65 + throw $e;
66 + } catch (\Exception $e) {
67 + if ($this->retryWithoutDuplicateSku($productData, $e, $skuRetry)) {
68 + continue;
69 + }
56 70
57 - if ($responseData && isset($responseData['messages'][0])) {
58 - $message = $responseData['messages'][0]['message'] ?? 'خطای نامشخص';
59 - $field = $responseData['messages'][0]['fields'][0] ?? '';
60 - } else {
61 - $message = 'درخواست با تایم اوت مواجه شد.';
62 - $field = '';
71 + throw new \Exception(esc_html($e->getMessage()));
63 72 }
64 73
65 - $errorMessage = 'فرایند اضافه کردن محصول ناموفق بود: ' . esc_html($message);
66 - if ($field) $errorMessage .= ' (فیلد: ' . esc_html($field) . ')';
74 + // Some API adapters return a non-2xx response instead of throwing it.
75 + if ($this->retryWithoutDuplicateSku($productData, $request, $skuRetry)) {
76 + continue;
77 + }
67 78
68 - throw new \Exception($errorMessage);
79 + break;
69 80 }
81 +
82 + if ($request['status_code'] != 201 && isset($request['status_code'])) {
83 +
84 + $body = $request['body'] ?? '';
85 +
86 + if (is_string($body))
87 + $responseData = json_decode($body, true);
88 + else $responseData = $body;
89 +
90 + if (!is_array($responseData)) $responseData = [];
91 +
92 + if ($responseData && isset($responseData['messages'][0])) {
93 + $message = $responseData['messages'][0]['message'] ?? 'خطای نامشخص';
94 + $field = $responseData['messages'][0]['fields'][0] ?? '';
95 + } else {
96 + $message = 'درخواست با تایم اوت مواجه شد.';
97 + $field = '';
98 + }
99 +
100 + $errorMessage = 'فرایند اضافه کردن محصول ناموفق بود: ' . esc_html($message);
101 + if ($field) $errorMessage .= ' (فیلد: ' . esc_html($field) . ')';
102 +
103 + throw new \Exception(esc_html($errorMessage));
104 + }
105 +
106 + if (is_wp_error($request)) {
107 + $body = $request['body'] ?? '';
108 +
109 + if (is_string($body))
110 + $responseData = json_decode($body, true);
111 + else $responseData = $body;
112 +
113 + $message = $responseData[0]['message'] ?? 'خطای نامشخص';
114 + throw new \Exception('درخواست موفقیت آمیز نبود: ' . esc_html($message));
115 + }
116 +
117 + $responseData = json_decode($request['body'], true);
118 + if (isset($responseData['id'])) {
119 + if (isset($responseData['variants'])) {
120 + $product = wc_get_product($productId);
121 +
122 + if ($product && $product->is_type('variable')) {
123 + $wcVariations = [];
124 + $attributes = $product->get_attributes();
125 +
126 + foreach ($product->get_children() as $variationId) {
127 + $variation = wc_get_product($variationId);
128 + $attributeValues = [];
129 +
130 + foreach ($attributes as $attributeName => $attribute) {
131 + if ($attribute->get_variation()) {
132 + $cleanAttributeName = str_replace('attribute_', '', $attributeName);
133 + $value = $variation->get_attribute($cleanAttributeName);
134 +
135 + $value = urldecode($value);
136 + $value = trim($value);
137 + $value = mb_strtolower($value, 'UTF-8');
138 + $value = str_replace(['ي', 'ك'], ['ی', 'ک'], $value);
139 + $value = str_replace(['-', '_', '–', '—'], ' ', $value);
140 + $value = preg_replace('/\s+/', ' ', $value);
141 +
142 + if (!empty($value)) {
143 + $attributeValues[] = $value;
144 + }
145 + }
146 + }
147 +
148 + if (!empty($attributeValues)) {
149 + $key = implode("_", $attributeValues);
150 + $wcVariations[$key] = $variationId;
151 + }
152 + }
153 +
154 + $syncBasalamVariations = [];
155 + foreach ($responseData['variants'] as $variant) {
156 + $attributeValues = [];
157 + if (!empty($variant['properties'])) {
158 + foreach ($variant['properties'] as $property) {
159 + $val = trim($property['value']['title']);
160 + $val = mb_strtolower($val, 'UTF-8');
161 + $val = str_replace(['ي', 'ك'], ['ی', 'ک'], $val);
162 + $val = str_replace(['-', '_', '–', '—'], ' ', $val);
163 + $val = preg_replace('/\s+/', ' ', $val);
164 + if (!empty($val)) {
165 + $attributeValues[] = $val;
166 + }
167 + }
168 + }
169 +
170 + if (!empty($attributeValues)) {
171 + $key = implode("_", $attributeValues);
172 + $syncBasalamVariations[$key] = $variant['id'];
173 + }
174 + }
175 +
176 + $variationMapping = [];
177 + foreach ($wcVariations as $key => $wcVarId) {
178 + if (isset($syncBasalamVariations[$key])) {
179 + $variationMapping[$wcVarId] = $syncBasalamVariations[$key];
180 + update_post_meta($wcVarId, 'sync_basalam_variation_id', $syncBasalamVariations[$key]);
181 + }
182 + }
183 + }
184 + }
185 +
186 + update_post_meta($productId, ProductMetaKey::basalamProductId(), $responseData['id']);
187 + update_post_meta($productId, ProductMetaKey::basalamProductStatus(), 2976);
188 + update_post_meta($productId, ProductMetaKey::basalamProductSyncStatus(), 'synced');
189 +
190 + $result = [
191 + 'success' => true,
192 + 'message' => 'محصول با موفقیت به باسلام اضافه شد.',
193 + 'status_code' => 200,
194 + 'basalam_id' => $responseData['id'],
195 + ];
196 +
197 + do_action('sync_basalam_after_create_product_api', $productId, $responseData, $result);
198 +
199 + return $result;
200 + }
201 +
202 + throw new \Exception("فرایند اضافه کردن محصول ناموفق بود");
203 + }
204 +
205 + private function stripForbiddenDescription(NonRetryableException $e, array &$productData, int $productId, int $attempt): bool
206 + {
207 + if (!isset($productData['description']) || !is_string($productData['description'])) return false;
208 +
209 + $values = DescriptionErrorSanitizer::extractDescriptionValues($e->getResponseData());
210 + if (empty($values)) return false;
211 +
212 + $cleaned = DescriptionErrorSanitizer::sanitize($productData['description'], $values);
213 + if ($cleaned === $productData['description']) return false;
214 +
215 + $productData['description'] = $cleaned;
216 +
217 + return true;
218 + }
70 219
71 - if (is_wp_error($request)) {
72 - $body = $request['body'] ?? '';
220 + private function retryWithoutDuplicateSku(array &$productData, $error, bool &$retried): bool
221 + {
222 + if ($retried || !ProductSkuRetry::hasSku($productData)) return false;
223 + if (!ProductSkuRetry::isDuplicateSkuError($error)) return false;
73 224
74 - if (is_string($body))
75 - $responseData = json_decode($body, true);
76 - else $responseData = $body;
225 + $productData = ProductSkuRetry::withoutSkus($productData);
226 + $retried = true;
77 227
78 - $message = $responseData[0]['message'] ?? 'خطای نامشخص';
79 - throw new \Exception('درخواست موفقیت آمیز نبود: ' . esc_html($message));
80 - }
81 -
82 - $responseData = json_decode($request['body'], true);
83 - if (isset($responseData['id'])) {
84 - if (isset($responseData['variants'])) {
85 - $product = wc_get_product($productId);
86 -
87 - if ($product && $product->is_type('variable')) {
88 - $wcVariations = [];
89 - $attributes = $product->get_attributes();
90 -
91 - foreach ($product->get_children() as $variationId) {
92 - $variation = wc_get_product($variationId);
93 - $attributeValues = [];
94 -
95 - foreach ($attributes as $attributeName => $attribute) {
96 - if ($attribute->get_variation()) {
97 - $cleanAttributeName = str_replace('attribute_', '', $attributeName);
98 - $value = $variation->get_attribute($cleanAttributeName);
99 -
100 - $value = urldecode($value);
101 - $value = trim($value);
102 - $value = mb_strtolower($value, 'UTF-8');
103 - $value = str_replace(['ي', 'ك'], ['ی', 'ک'], $value);
104 - $value = str_replace(['-', '_', '–', '—'], ' ', $value);
105 - $value = preg_replace('/\s+/', ' ', $value);
106 -
107 - if (!empty($value)) {
108 - $attributeValues[] = $value;
109 - }
110 - }
111 - }
112 -
113 - if (!empty($attributeValues)) {
114 - $key = implode("_", $attributeValues);
115 - $wcVariations[$key] = $variationId;
116 - }
117 - }
118 -
119 - $syncBasalamVariations = [];
120 - foreach ($responseData['variants'] as $variant) {
121 - $attributeValues = [];
122 - if (!empty($variant['properties'])) {
123 - foreach ($variant['properties'] as $property) {
124 - $val = trim($property['value']['title']);
125 - $val = mb_strtolower($val, 'UTF-8');
126 - $val = str_replace(['ي', 'ك'], ['ی', 'ک'], $val);
127 - $val = str_replace(['-', '_', '–', '—'], ' ', $val);
128 - $val = preg_replace('/\s+/', ' ', $val);
129 - if (!empty($val)) {
130 - $attributeValues[] = $val;
131 - }
132 - }
133 - }
134 -
135 - if (!empty($attributeValues)) {
136 - $key = implode("_", $attributeValues);
137 - $syncBasalamVariations[$key] = $variant['id'];
138 - }
139 - }
140 -
141 - $variationMapping = [];
142 - foreach ($wcVariations as $key => $wcVarId) {
143 - if (isset($syncBasalamVariations[$key])) {
144 - $variationMapping[$wcVarId] = $syncBasalamVariations[$key];
145 - update_post_meta($wcVarId, 'sync_basalam_variation_id', $syncBasalamVariations[$key]);
146 - }
147 - }
148 - }
149 - }
150 -
151 - update_post_meta($productId, ProductMetaKey::basalamProductId(), $responseData['id']);
152 - update_post_meta($productId, ProductMetaKey::basalamProductStatus(), 2976);
153 - update_post_meta($productId, ProductMetaKey::basalamProductSyncStatus(), 'synced');
154 -
155 - $result = [
156 - 'success' => true,
157 - 'message' => 'محصول با موفقیت به باسلام اضافه شد.',
158 - 'status_code' => 200,
159 - 'basalam_id' => $responseData['id'],
160 - ];
161 -
162 - do_action('sync_basalam_after_create_product_api', $productId, $responseData, $result);
163 -
164 - return $result;
165 - }
166 -
167 - throw new \Exception("فرایند اضافه کردن محصول ناموفق بود");
228 + return true;
168 229 }
169 230
170 231 private function ValidCreateProductData(array $productData, int $productId): void
171 - {
172 - $validation = $this->validator->validate($productData, $productId);
173 -
174 - if (!$validation['valid']) {
175 - throw NonRetryableException::invalidData($validation['message']);
176 - }
177 - }
178 -}
232 + {
233 + $validation = $this->validator->validate($productData, $productId);
234 +
235 + if (!$validation['valid']) {
236 + throw NonRetryableException::invalidData(esc_html($validation['message']));
237 + }
238 + }
239 +}