PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.6.4
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.6.4
1.6.4 1.6.3 1.6.2 1.6.1 1.6.0 1.5.4 1.5.5 1.5.3 1.5.2 1.5.1 1.5.0 1.4.2 1.4.1 1.4.0 1.3.28 1.3.27 1.3.26 1.3.25 1.3.23 1.3.22 1.3.21 1.3.20 1.3.19 trunk 1.2.0 All 47 releases
fluent-cart / app / Services / Async / DummyProductService.php

DummyProductService.php in FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler 1.6.4, at app/Services/Async/DummyProductService.php

369 lines 13.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentCart\App\Services\Async;
4
5 use FluentCart\App\Helpers\Helper;
6 use FluentCart\App\Models\AttributeGroup;
7 use FluentCart\App\Models\AttributeTerm;
8 use FluentCart\App\Models\Product;
9 use FluentCart\App\Services\AdvancedVariationService;
10 use FluentCart\App\Services\DateTime\DateTime;
11 use FluentCart\Framework\Support\Arr;
12 use FluentCart\Framework\Support\Str;
13
14 class DummyProductService
15 {
16 public static function create(string $category, $index)
17 {
18 $instance = new self();
19 $filePath = $instance->getFilePath($category);
20 if (!file_exists($filePath)) {
21 return new \WP_Error(
22 400,
23 __('Products for this category is not found', 'fluent-cart'),
24 );
25 }
26 $json = file_get_contents($filePath);
27 try {
28 $products = json_decode($json, true);
29
30 $product = Arr::get($products, $index);
31 if (empty($product)) {
32 return new \WP_Error(
33 404,
34 __('Product Not Found', 'fluent-cart'),
35 );
36 }
37 $instance->insert($product);
38
39 return true;
40
41 } catch (\Exception $exception) {
42 return new \WP_Error(
43 400,
44 __('Products for this category is not found', 'fluent-cart'),
45 );
46 }
47 }
48
49 public static function createAll(string $category)
50 {
51 $instance = new self();
52 $filePath = $instance->getFilePath($category);
53 if (!file_exists($filePath)) {
54 return new \WP_Error(
55 400,
56 __('Products for this category is not found', 'fluent-cart'),
57 );
58 }
59 $json = file_get_contents($filePath);
60 try {
61 $products = json_decode($json, true);
62 foreach ($products as $product) {
63 $instance->insert($product);
64 }
65 return true;
66
67 } catch (\Exception $exception) {
68 return new \WP_Error(
69 400,
70 __('Products for this category is not found', 'fluent-cart'),
71 );
72 }
73 }
74
75
76 protected function insert($product)
77 {
78
79 $productName = Str::slug($product['post_title'], '-', null);
80 $now = DateTime::gmtNow();
81 $createdDate = $now->format('Y-m-d H:i:s');
82 $productNameSuffix = $now->format('d-m-Y-H-i-s');
83
84 $data = [
85 'post_author' => get_current_user_id(),
86 'post_date' => $createdDate,
87 'post_date_gmt' => $createdDate,
88 'post_content_filtered' => '',
89 'post_status' => 'publish',
90 'post_type' => 'fluent-products',
91 'comment_status' => 'open',
92 'ping_status' => 'closed',
93 'post_password' => '',
94 'post_name' => $productName . '-' . $productNameSuffix,
95 'to_ping' => '',
96 'pinged' => '',
97 'post_modified' => $createdDate,
98 'post_modified_gmt' => $createdDate,
99 'post_parent' => 0,
100 'menu_order' => 0,
101 'post_mime_type' => '',
102 ];
103 $product = array_merge($product, $data);
104
105 $detail = $product['detail'];
106 $variantData = Arr::get($product, 'variants', []);
107 $attributeOptions = Arr::get($product, 'attribute_options', []);
108 $variantImages = Arr::get($product, 'variant_images', []);
109 $imageKeyGroups = Arr::get($product, 'image_key_groups', []);
110 $defaultVariantData = Arr::get($product, 'default_variant_data', []);
111 $priceKeyGroups = Arr::get($product, 'price_key_groups', []);
112 $variantPrices = Arr::get($product, 'variant_prices', []);
113 $variationType = Arr::get($detail, 'variation_type', 'simple');
114 $isAdvanced = $variationType === Helper::PRODUCT_TYPE_ADVANCE_VARIATION;
115
116 $galleryImages = [];
117 if (isset($product['gallery']) && is_array($product['gallery'])) {
118 $galleryImages = $product['gallery'];
119 }
120 $categories = Arr::get($product, 'categories');
121
122 // Validate and resolve advanced variation inputs BEFORE writing any product
123 // rows — invalid JSON must not leave a published product without variants.
124 $resolved = [];
125 if ($isAdvanced) {
126 if (empty($attributeOptions)) {
127 throw new \Exception(__('No attribute options defined for advanced variation.', 'fluent-cart'));
128 }
129 $resolved = $this->resolveAttributeOptions($attributeOptions);
130 if (empty($resolved['options'])) {
131 throw new \Exception(__('Failed to resolve attribute options for advanced variation.', 'fluent-cart'));
132 }
133 }
134
135 $product = Product::query()->create(
136 Arr::except($product, ['detail', 'variants', 'gallery', 'attribute_options', 'variant_images', 'image_key_groups', 'default_variant_data', 'price_key_groups', 'variant_prices'])
137 );
138
139 $product->update(['guid' => get_permalink($product->ID)]);
140
141 $this->attachTerms($categories, $product->ID);
142
143 if (!empty($galleryImages)) {
144 ImageAttachService::attachImageToProduct($product->toArray(), $galleryImages, $isAdvanced);
145
146 $galleryImageWithId = get_post_meta($product->ID, 'fluent-products-gallery-image', true);
147 $firstImageId = Arr::get($galleryImageWithId, '0.id');
148 if (!empty($firstImageId)) {
149 set_post_thumbnail($product->ID, $firstImageId);
150 }
151 }
152
153 if ($isAdvanced) {
154 try {
155 $this->insertAdvancedVariation($product, $detail, $resolved, $variantImages, $imageKeyGroups, $defaultVariantData, $variantPrices, $priceKeyGroups);
156 } catch (\Exception $exception) {
157 wp_delete_post($product->ID, true);
158 throw $exception;
159 }
160 return;
161 }
162
163 $variants = $product->variants()->createMany($variantData);
164
165 foreach ($variants as $index => $variant) {
166 $images = Arr::get($variantData, $index . '.images', []);
167 if (is_array($images) && count($images)) {
168 ImageAttachService::attachImageToVariant($variant->id, $images);
169 }
170 }
171
172 $detail['post_id'] = $product->ID;
173 $detail['default_variation_id'] = $variants->first()->id;
174 $product->detail()->create($detail);
175
176 }
177
178 private function insertAdvancedVariation(Product $product, array $detail, array $resolved, array $variantImages, array $imageKeyGroups, array $defaultVariantData, array $variantPrices = [], array $priceKeyGroups = []): void
179 {
180 $optionsForSync = $resolved['options'];
181 $termIdToSlug = $resolved['termIdToSlug'];
182 $termIdToGroupSlug = $resolved['termIdToGroupSlug'];
183
184 $detail['post_id'] = $product->ID;
185 $productDetail = $product->detail()->create($detail);
186
187 try {
188 $result = AdvancedVariationService::syncVariantOption($product->ID, ['options' => $optionsForSync]);
189 $variants = Arr::get($result, 'data');
190
191 if (empty($variants) || $variants->isEmpty()) {
192 throw new \Exception(Arr::get($result, 'message', __('Failed to generate variants.', 'fluent-cart')));
193 }
194 } catch (\Exception $exception) {
195 $productDetail->delete();
196 throw $exception;
197 }
198
199 $productDetail->update(['default_variation_id' => $variants->first()->id]);
200
201 foreach ($variants as $variant) {
202 $needsTermResolution = (!empty($variantImages) && !empty($imageKeyGroups))
203 || (!empty($variantPrices) && !empty($priceKeyGroups));
204
205 $termsByGroupSlug = [];
206 if ($needsTermResolution) {
207 foreach (explode('_', (string) $variant->variation_identifier) as $termIdStr) {
208 $termId = (int) $termIdStr;
209 $groupSlug = Arr::get($termIdToGroupSlug, $termId);
210 $termSlug = Arr::get($termIdToSlug, $termId);
211 if ($groupSlug && $termSlug) {
212 $termsByGroupSlug[$groupSlug] = $termSlug;
213 }
214 }
215 }
216
217 $variantUpdate = [];
218 if (!empty($defaultVariantData)) {
219 $variantUpdate = [
220 'item_price' => Arr::get($defaultVariantData, 'item_price', 0),
221 'compare_price' => Arr::get($defaultVariantData, 'compare_price', 0),
222 'total_stock' => Arr::get($defaultVariantData, 'total_stock', 1),
223 'available' => Arr::get($defaultVariantData, 'available', 1),
224 'stock_status' => Arr::get($defaultVariantData, 'stock_status', 'in-stock'),
225 ];
226 }
227
228 if (!empty($variantPrices) && !empty($priceKeyGroups)) {
229 $priceParts = [];
230 foreach ($priceKeyGroups as $groupSlug) {
231 $priceParts[] = Arr::get($termsByGroupSlug, $groupSlug, '');
232 }
233 $priceOverride = Arr::get($variantPrices, implode('/', $priceParts), []);
234 if (!empty($priceOverride)) {
235 $variantUpdate['item_price'] = Arr::get($priceOverride, 'item_price', $variantUpdate['item_price'] ?? 0);
236 $variantUpdate['compare_price'] = Arr::get($priceOverride, 'compare_price', $variantUpdate['compare_price'] ?? 0);
237 }
238 }
239
240 if (!empty($variantUpdate)) {
241 $variant->update($variantUpdate);
242 }
243
244 if (empty($variantImages) || empty($imageKeyGroups)) {
245 continue;
246 }
247
248 $keyParts = [];
249 foreach ($imageKeyGroups as $groupSlug) {
250 $keyParts[] = Arr::get($termsByGroupSlug, $groupSlug, '');
251 }
252 $imageKey = implode('/', $keyParts);
253
254 $images = Arr::get($variantImages, $imageKey, []);
255 if (!empty($images)) {
256 ImageAttachService::attachImageToVariant($variant->id, $images, true);
257 }
258 }
259 }
260
261 private function resolveAttributeOptions(array $attributeOptions): array
262 {
263 $optionsForSync = [];
264 $termIdToSlug = [];
265 $termIdToGroupSlug = [];
266
267 foreach ($attributeOptions as $groupConfig) {
268 $groupDef = Arr::get($groupConfig, 'group', []);
269 $groupSlug = Arr::get($groupDef, 'slug', '');
270 $groupTitle = Arr::get($groupDef, 'title', '');
271 $groupType = Arr::get($groupDef, 'type', '');
272 if (empty($groupSlug)) {
273 continue;
274 }
275
276 $group = AttributeGroup::query()->where('slug', $groupSlug)->first();
277 if (empty($group)) {
278 if (empty($groupTitle)) {
279 continue;
280 }
281 $createData = [
282 'slug' => $groupSlug,
283 'title' => $groupTitle,
284 'serial' => 10,
285 ];
286 if (!empty($groupType)) {
287 $createData['settings'] = ['type' => $groupType];
288 }
289 $group = AttributeGroup::query()->create($createData);
290 }
291
292 $termIds = [];
293 foreach (Arr::get($groupConfig, 'terms', []) as $termData) {
294 $termSlug = Arr::get($termData, 'slug', '');
295 if (empty($termSlug)) {
296 continue;
297 }
298
299 $term = AttributeTerm::query()
300 ->where('group_id', $group->id)
301 ->where('slug', $termSlug)
302 ->first();
303
304 if (empty($term)) {
305 $termCreate = [
306 'group_id' => $group->id,
307 'slug' => $termSlug,
308 'title' => Arr::get($termData, 'title', $termSlug),
309 'serial' => 10,
310 ];
311 $termSettings = array_filter([
312 'color' => Arr::get($termData, 'color', ''),
313 'url' => Arr::get($termData, 'url', ''),
314 ]);
315 if (!empty($termSettings)) {
316 $termCreate['settings'] = $termSettings;
317 }
318 $term = AttributeTerm::query()->create($termCreate);
319 }
320
321 $termIds[] = $term->id;
322 $termIdToSlug[$term->id] = $termSlug;
323 $termIdToGroupSlug[$term->id] = $groupSlug;
324 }
325
326 if (!empty($termIds)) {
327 $optionsForSync[] = [
328 'group_id' => $group->id,
329 'variants' => $termIds,
330 ];
331 }
332 }
333
334 return [
335 'options' => $optionsForSync,
336 'termIdToSlug' => $termIdToSlug,
337 'termIdToGroupSlug' => $termIdToGroupSlug,
338 ];
339 }
340
341 public function attachTerms($categories, $postId)
342 {
343 if (!function_exists('wp_create_term')) {
344 require_once(ABSPATH . 'wp-admin/includes/taxonomy.php');
345 }
346
347
348 if (is_string($categories)) {
349 $categories = explode(',', $categories);
350 }
351
352 $termIds = [];
353
354 if (is_array($categories)) {
355 foreach ($categories as $category) {
356 $term = wp_create_term($category, 'product-categories');
357 $termIds[] = $term['term_id'];
358 }
359 }
360 wp_set_post_terms($postId, $termIds, 'product-categories');
361 }
362
363
364 protected function getFilePath(string $category): string
365 {
366 return FLUENTCART_PLUGIN_PATH . 'dummies' . DIRECTORY_SEPARATOR . $category . '.json';
367 }
368
369 }