PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.6.6
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.6.6
1.6.6 1.6.5 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 All 49 releases
← All changes | app/Services/Async/DummyProductService.php +207 -12 1.5.0 → 1.6.6 View file →
@@ -1,9 +1,13 @@
1 1 <?php
2 2
3 3 namespace FluentCart\App\Services\Async;
4 4
5 +use FluentCart\App\Helpers\Helper;
6 +use FluentCart\App\Models\AttributeGroup;
7 +use FluentCart\App\Models\AttributeTerm;
5 8 use FluentCart\App\Models\Product;
9 +use FluentCart\App\Services\AdvancedVariationService;
6 10 use FluentCart\App\Services\DateTime\DateTime;
7 11 use FluentCart\Framework\Support\Arr;
8 12 use FluentCart\Framework\Support\Str;
9 13
@@ -96,21 +100,41 @@
96 100 'menu_order' => 0,
97 101 'post_mime_type' => '',
98 102 ];
99 103 $product = array_merge($product, $data);
100 - $detail = $product['detail'];
101 - $variantData = $product['variants'];
102 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 +
103 116 $galleryImages = [];
104 -
105 -
106 117 if (isset($product['gallery']) && is_array($product['gallery'])) {
107 118 $galleryImages = $product['gallery'];
108 119 }
109 120 $categories = Arr::get($product, 'categories');
110 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 +
111 135 $product = Product::query()->create(
112 - Arr::except($product, ['detail', 'variants', 'gallery'])
136 + Arr::except($product, ['detail', 'variants', 'gallery', 'attribute_options', 'variant_images', 'image_key_groups', 'default_variant_data', 'price_key_groups', 'variant_prices'])
113 137 );
114 138
115 139 $product->update(['guid' => get_permalink($product->ID)]);
116 140
@@ -115,20 +139,28 @@
115 139 $product->update(['guid' => get_permalink($product->ID)]);
116 140
117 141 $this->attachTerms($categories, $product->ID);
118 142
119 - /**
120 - * @var Product $product
121 - */
122 -
123 143 if (!empty($galleryImages)) {
124 - ImageAttachService::attachImageToProduct($product->toArray(), $galleryImages);
144 + ImageAttachService::attachImageToProduct($product->toArray(), $galleryImages, $isAdvanced);
125 145
126 146 $galleryImageWithId = get_post_meta($product->ID, 'fluent-products-gallery-image', true);
127 - if (isset($galleryImageWithId[0])) {
128 - set_post_thumbnail($product->ID, Arr::get($galleryImageWithId, '0.id'));
147 + $firstImageId = Arr::get($galleryImageWithId, '0.id');
148 + if (!empty($firstImageId)) {
149 + set_post_thumbnail($product->ID, $firstImageId);
129 150 }
130 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 +
131 163 $variants = $product->variants()->createMany($variantData);
132 164
133 165 foreach ($variants as $index => $variant) {
134 166 $images = Arr::get($variantData, $index . '.images', []);
@@ -140,8 +172,171 @@
140 172 $detail['post_id'] = $product->ID;
141 173 $detail['default_variation_id'] = $variants->first()->id;
142 174 $product->detail()->create($detail);
143 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 + ];
144 339 }
145 340
146 341 public function attachTerms($categories, $postId)
147 342 {