PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.6.5
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.6.5
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 trunk All 48 releases
← All changes | app/Services/Async/DummyProductService.php +209 -13 1.3.21 → 1.6.5 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
@@ -94,40 +98,69 @@
94 98 'post_modified_gmt' => $createdDate,
95 99 'post_parent' => 0,
96 100 'menu_order' => 0,
97 101 'post_mime_type' => '',
98 - 'guid' => get_site_url() . '/?items=' . $productName . '-' . $productNameSuffix
99 102 ];
100 103 $product = array_merge($product, $data);
101 - $detail = $product['detail'];
102 - $variantData = $product['variants'];
103 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 +
104 116 $galleryImages = [];
105 -
106 -
107 117 if (isset($product['gallery']) && is_array($product['gallery'])) {
108 118 $galleryImages = $product['gallery'];
109 119 }
110 120 $categories = Arr::get($product, 'categories');
111 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 +
112 135 $product = Product::query()->create(
113 - 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'])
114 137 );
115 138
139 + $product->update(['guid' => get_permalink($product->ID)]);
140 +
116 141 $this->attachTerms($categories, $product->ID);
117 142
118 - /**
119 - * @var Product $product
120 - */
121 -
122 143 if (!empty($galleryImages)) {
123 - ImageAttachService::attachImageToProduct($product->toArray(), $galleryImages);
144 + ImageAttachService::attachImageToProduct($product->toArray(), $galleryImages, $isAdvanced);
124 145
125 146 $galleryImageWithId = get_post_meta($product->ID, 'fluent-products-gallery-image', true);
126 - if (isset($galleryImageWithId[0])) {
127 - 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);
128 150 }
129 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 +
130 163 $variants = $product->variants()->createMany($variantData);
131 164
132 165 foreach ($variants as $index => $variant) {
133 166 $images = Arr::get($variantData, $index . '.images', []);
@@ -139,8 +172,171 @@
139 172 $detail['post_id'] = $product->ID;
140 173 $detail['default_variation_id'] = $variants->first()->id;
141 174 $product->detail()->create($detail);
142 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 + ];
143 339 }
144 340
145 341 public function attachTerms($categories, $postId)
146 342 {