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 / api / Resource / ProductDetailResource.php

ProductDetailResource.php in FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler 1.6.4, at api/Resource/ProductDetailResource.php

256 lines 11.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentCart\Api\Resource;
4
5 use FluentCart\Api\Meta;
6 use FluentCart\App\Events\StockChanged;
7 use FluentCart\App\Helpers\Helper;
8 use FluentCart\App\Helpers\ProductAdminHelper;
9 use FluentCart\App\Models\ProductDetail;
10 use FluentCart\Framework\Database\Orm\Builder;
11 use FluentCart\Framework\Support\Arr;
12
13 class ProductDetailResource extends BaseResourceApi
14 {
15
16 public static function getQuery(): Builder
17 {
18 return ProductDetail::query();
19 }
20
21 public static function get(array $params = [])
22 {
23 //
24 }
25
26 /**
27 * Find product detail by its id.
28 *
29 * @param int $id The id of the product detail.
30 * @param array $data Additional data for finding product (optional).
31 *
32 */
33 public static function find($id, $data = [])
34 {
35 return static::getQuery()->find($id);
36 }
37
38 /**
39 * Create a new product detail with the given data.
40 *
41 * @param array $data Array containing the necessary parameters.
42 *
43 * $data = [
44 * 'post_id' => (int) Required. The product ID.
45 * 'fulfillment_type' => (string) Required. The fulfillment type default:physical.
46 * 'variation_type' => (string) Required. The variation type default:simple.
47 * 'manage_stock' => (int) Required. The manage stock default:1.
48 * ];
49 */
50 public static function create($data, $params = [])
51 {
52 $isCreated = static::getQuery()->create($data);
53
54 if ($isCreated) {
55 return static::makeSuccessResponse(
56 $isCreated,
57 __('Product has been created successfully', 'fluent-cart')
58 );
59 }
60
61 return static::makeErrorResponse([
62 ['code' => 400, 'message' => __('Product creation failed!', 'fluent-cart')]
63 ]);
64 }
65
66 /**
67 * Update a product detail with the given data.
68 * @param int $id The id of the product detail to be updated.
69 * @param array $data Array containing the necessary parameters.
70 *
71 * $data = [
72 * 'id' => (int) Required. The detail id.
73 * 'post_id' => (int) Required. The product ID.
74 * 'fulfillment_type' => (string) Required. The fulfillment type.
75 * 'variation_type' => (string) Required. The variation type.
76 * 'default_variation_id' => (int) Required. The default variation ID.
77 * 'manage_stock' => (int) Required. The manage stock default:1.
78 * ];
79 * @param array $params Additional parameters for the update process.
80 * $params = [
81 * 'action' => (string) Required. This param will help to update detail based on the specific action i.e: variant_modified(Triggers when variant modified which covers all mutations), change_variation_type(Triggers when variation type will change).
82 * ];
83 */
84 public static function update($data, $id, $params = [])
85 {
86 $data ??= [];
87
88 if (!$id) {
89 return static::makeErrorResponse([
90 ['code' => 403, 'message' => __('Please edit a valid product!', 'fluent-cart')]
91 ]);
92 }
93
94 $detail = static::getQuery()->find($id);
95
96 if (!$detail) {
97 return static::makeErrorResponse([
98 ['code' => 404, 'message' => __('Product not found, please reload the page and try again!', 'fluent-cart')]
99 ]);
100 }
101
102 $triggeredAction = Arr::get($params, 'action');
103
104 // Advanced Variations is terminal: once a product uses it, variation_type
105 // can never be changed to Simple / Simple Variations — the attribute
106 // config and generated combinations are the product's source of truth and
107 // a downgrade would orphan them. Guarded on ANY update path that writes
108 // variation_type (not just the change_variation_type action) — the full
109 // product save also sends variation_type and would otherwise bypass this —
110 // and for any API client, not just the disabled admin dropdown. Only an
111 // actual downgrade is blocked: re-saving the same advanced type, or an
112 // update that omits variation_type, passes through untouched.
113 if (
114 Arr::has($data, 'variation_type')
115 && $detail->variation_type === Helper::PRODUCT_TYPE_ADVANCE_VARIATION
116 && Arr::get($data, 'variation_type') !== Helper::PRODUCT_TYPE_ADVANCE_VARIATION
117 ) {
118 return static::makeErrorResponse([
119 ['code' => 422, 'message' => __('A product using Advanced Variations cannot be switched back to Simple or Simple Variations.', 'fluent-cart')]
120 ]);
121 }
122
123 // Stock & Price Range Handling
124 if ($triggeredAction === 'variant_modified') {
125 $manageStock = Arr::has($data, 'manage_stock') ? Arr::get($data, 'manage_stock') : $detail->manage_stock;
126
127 if (!$manageStock) {
128 $data['stock_availability'] = Helper::IN_STOCK;
129 } else {
130 $hasInStock = \FluentCart\App\Models\ProductVariation::query()
131 ->where('post_id', $detail->post_id)
132 ->where('stock_status', 'in-stock')
133 ->exists();
134 $data['stock_availability'] = $hasInStock ? Helper::IN_STOCK : Helper::OUT_OF_STOCK;
135 }
136 }
137
138 if ($triggeredAction === 'change_variation_type' && Arr::get($data, 'variation_type') === 'simple') {
139 $variationIds = Arr::get($data, 'variation_ids', []);
140 if (!empty($detail->post_id) && count($variationIds) > 0) {
141 ProductAdminHelper::deleteOrphanVariant(
142 $detail->post_id,
143 $variationIds,
144 __("the product variation type was changed to 'Simple'", 'fluent-cart')
145 );
146
147 // The surviving variant (variationIds[0]) is kept, but a Simple
148 // product has no editor control to view, replace, or clear its
149 // image (VariantTitleMedia only shows for simple_variations /
150 // advanced_variations). Leaving that image attached would let it
151 // keep rendering on the storefront gallery with no way for the
152 // merchant to find or remove it, so it is cleared with the same
153 // switch the admin UI now warns about.
154 //
155 // variation_ids is client-supplied, so confirm the surviving id
156 // actually belongs to this product before deleting its media —
157 // otherwise a caller could point it at an unrelated product's
158 // variation and wipe that variation's image instead.
159 $keptVariantBelongsToProduct = \FluentCart\App\Models\ProductVariation::query()
160 ->where('id', $variationIds[0])
161 ->where('post_id', $detail->post_id)
162 ->exists();
163
164 if ($keptVariantBelongsToProduct) {
165 Meta::deleteVariationMedia($variationIds[0]);
166 }
167 }
168 }
169
170 // Switching INTO Advanced Variations (from Simple or Simple Variations)
171 // deletes the existing variants now. They have no place in an
172 // attribute-based product (the merchant builds fresh combinations from
173 // attribute options), and Advanced Variations is terminal so there is
174 // nothing to preserve them for — matching the destructive admin confirm
175 // ("delete all current variations ... cannot be undone") and the editor
176 // clearing them client-side. An empty keep-list deletes every variant for
177 // the product; an unconfigured advanced product is hidden on the
178 // storefront until the merchant generates combinations, so the empty
179 // variant set never leaks. Keyed on the non-advanced -> advanced
180 // transition itself, NOT the change_variation_type action, so the side
181 // effect is identical on every write path that sets variation_type — the
182 // dedicated detail endpoint AND the full pricing save (which calls update()
183 // with action=variant_modified). Otherwise a full save or API client could
184 // land a product on Advanced Variations without the deletion, leaving
185 // inconsistent variant state. Mirrors the downgrade guard above.
186 if (
187 Arr::get($data, 'variation_type') === Helper::PRODUCT_TYPE_ADVANCE_VARIATION
188 && $detail->variation_type !== Helper::PRODUCT_TYPE_ADVANCE_VARIATION
189 && !empty($detail->post_id)
190 ) {
191 ProductAdminHelper::deleteOrphanVariant(
192 $detail->post_id,
193 [],
194 __("the product variation type was changed to 'Advanced Variations'", 'fluent-cart')
195 );
196 }
197
198 $data['min_price'] = Arr::get($data, 'min_price') ?: ($detail->min_price ?? 0);
199 $data['max_price'] = Arr::get($data, 'max_price') ?: ($detail->max_price ?? 0);
200
201 // Handle Default Variation. Only act when the caller actually supplied the
202 // key: a partial update that never mentions it must leave the stored value
203 // alone, while an explicitly empty value still clears it.
204 if (Arr::has($data, 'default_variation_id')) {
205 if (empty(Arr::get($data, 'default_variation_id'))) {
206 $data['default_variation_id'] = NULL;
207 }
208 } else {
209 unset($data['default_variation_id']);
210 }
211
212 // Handle other_info merge
213 if (Arr::has($data, 'other_info')) {
214 $existingOtherInfo = $detail->other_info ?? [];
215 $newOtherInfo = Arr::get($data, 'other_info', []);
216
217 // Merge existing with new data (new data overwrites existing)
218 $mergedOtherInfo = array_merge($existingOtherInfo, $newOtherInfo);
219
220 // Handle subscription-specific logic
221 if (Arr::get($mergedOtherInfo, 'payment_type') == 'subscription' && Arr::get($mergedOtherInfo, 'manage_setup_fee') == 'yes') {
222 // Cents in, and $mergedOtherInfo may carry the already-cents stored
223 // value when the caller did not resend signup_fee — roundCent is
224 // idempotent, so neither case is rescaled.
225 $signupFee = Helper::roundCent(Arr::get($mergedOtherInfo, 'signup_fee', 0));
226 $mergedOtherInfo['signup_fee'] = $signupFee;
227 }
228
229 $data['other_info'] = $mergedOtherInfo;
230 }
231
232 $isUpdated = $detail->update($data);
233
234 if ($isUpdated) {
235 return static::makeSuccessResponse($isUpdated, __('Product pricing has been changed!', 'fluent-cart'));
236 }
237
238 return static::makeErrorResponse([
239 ['code' => 400, 'message' => __('Product update failed.', 'fluent-cart')]
240 ]);
241 }
242
243 /**
244 * Delete product detail and its associated data.
245 *
246 * @param int $id The id of the product detail to be deleted.
247 * @param array $params Additional parameters for the deletion process.
248 *
249 */
250 public static function delete($id, $params = [])
251 {
252 //
253 }
254
255 }
256