PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.3.19
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.3.19
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
fluent-cart / app / Http / Requests / ProductUpdateRequest.php

ProductUpdateRequest.php in FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler 1.3.19, at app/Http/Requests/ProductUpdateRequest.php

477 lines 21.7 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\Http\Requests;
4
5 use FluentCart\App\Models\ShippingClass;
6 use FluentCart\App\Services\DateTime\DateTime;
7 use FluentCart\Framework\Foundation\RequestGuard;
8 use FluentCart\Framework\Support\Arr;
9
10 class ProductUpdateRequest extends RequestGuard
11 {
12 /**
13 * Prepare and normalize the incoming data before validation.
14 *
15 * This method ensures that each variant in the request payload has the necessary structure
16 * expected for processing, particularly focusing on the `other_info` attribute.
17 *
18 * If `other_info` is missing from a variant (common during data migration scenarios),
19 * this method sets default values to prevent validation or processing errors.
20 *
21 * It also ensures that:
22 * - `fulfillment_type` is consistently applied across all variants, defaulting to 'physical'.
23 * - `payment_type` is set (defaulting to 'onetime') and injected into `other_info`.
24 * - `other_info` is populated with a consistent structure containing default billing and setup fee options.
25 *
26 * @return array The normalized request data ready for validation.
27 */
28 public function beforeValidation()
29 {
30
31
32 $data = $this->all();
33 return $data;
34 //
35 // $fulfilmentType = Arr::get(
36 // $data,
37 // 'detail.fulfillment_type',
38 // Arr::get($data, 'detail.fulfillment_type', 'physical')
39 // );
40 //
41 // $variants = Arr::wrap(
42 // Arr::get($data, 'variants', [])
43 // );
44 //
45 // foreach ($variants as $index => &$variant) {
46 // $variant['fulfillment_type'] = $variant['fulfillment_type'] ?? $fulfilmentType;
47 //
48 // $variant['other_info'] = Arr::wrap(Arr::get($variant, 'other_info')) ?? [];
49 //
50 // // get payment_type from $variant
51 // $paymentType = Arr::get($variant, 'other_info.payment_type', 'onetime');
52 //
53 // $variant['shipping_class'] = Arr::get($variant, 'shipping_class', null);
54 //
55 // $variant['other_info'] = [
56 // 'payment_type' => $paymentType,
57 // 'times' => Arr::get($variant, 'other_info.times', ''),
58 // 'trial_days' => Arr::get($variant, 'other_info.trial_days', ''),
59 // //'purchasable' => Arr::get($variant, 'other_info.purchasable', ''),
60 // 'repeat_interval' => Arr::get($variant, 'other_info.repeat_interval', 'yearly'),
61 // 'billing_summary' => Arr::get($variant, 'other_info.billing_summary', ''),
62 // 'manage_setup_fee' => Arr::get($variant, 'other_info.manage_setup_fee', 'no'),
63 // 'signup_fee_name' => Arr::get($variant, 'other_info.signup_fee_name', ''),
64 // 'signup_fee' => Arr::get($variant, 'other_info.signup_fee', ''),
65 // 'setup_fee_per_item' => Arr::get($variant, 'other_info.setup_fee_per_item', 'no'),
66 // 'installment' => Arr::get($variant, 'other_info.installment', 'no'),
67 // ];
68 //
69 // $data['variants'][$index] = $variant;
70 // }
71 //
72 // return $data;
73 }
74
75 function validateShippingClassId($attribute, $value): ?string
76 {
77 static $checked = [];
78
79 if (empty($value)) {
80 return null;
81 }
82
83 // Return cached result if we've already checked this value
84 if (isset($checked[$value])) {
85 return $checked[$value];
86 }
87
88 if (!is_numeric($value)) {
89 $checked[$value] = __("Invalid Shipping Class.", 'fluent-cart');
90 return $checked[$value];
91 }
92
93 if (empty(ShippingClass::query()->find($value))) {
94 $checked[$value] = __("Invalid Shipping Class.", 'fluent-cart');
95 return $checked[$value];
96 }
97
98 return null;
99
100 }
101
102 function validateTaxClassId($attribute, $value): ?string
103 {
104 static $checked = [];
105
106 if (empty($value)) {
107 return null;
108 }
109
110 // Return cached result if we've already checked this value
111 if (isset($checked[$value])) {
112 return $checked[$value];
113 }
114
115 if (!is_numeric($value)) {
116 $checked[$value] = __("Invalid Tax Class.", 'fluent-cart');
117 return $checked[$value];
118 }
119
120 if (empty(\FluentCart\App\Models\TaxClass::query()->find($value))) {
121 $checked[$value] = __("Invalid Tax Class.", 'fluent-cart');
122 return $checked[$value];
123 }
124
125 return null;
126
127 }
128
129 public function validatePostDate($attribute, $value): ?string
130 {
131 if ($this->get('post_status') !== 'future') {
132 return null;
133 }
134
135 if (empty($value)) {
136 return __("The post date is required when status is scheduled.", 'fluent-cart');
137 }
138 $currentTime = DateTime::gmtNow();
139 try {
140 $postDate = DateTime::anyTimeToGmt($value);
141 } catch (\Exception $exception) {
142 return __("The post date is invalid.", 'fluent-cart');
143 }
144
145 if ($postDate < $currentTime) {
146 return sprintf(__("The post date must be in the future.", 'fluent-cart'));
147 }
148 return null;
149 }
150
151
152 /**
153 * @return array
154 */
155 public function rules(): array
156 {
157
158 $variationType = Arr::get($this->all(), 'detail.variation_type', 'simple');
159 $rules = [
160 'post_title' => 'required|sanitizeText|maxLength:200',
161 'post_excerpt' => [
162 'nullable',
163 'string',
164 'validate_excerpt' => function ($attr, $value) {
165 $words = explode(' ', $value);
166 $excerpt_length = (int)apply_filters('excerpt_length', 55);
167 if (count($words) > $excerpt_length) {
168 return sprintf(
169 /* translators: %d: The maximum number of words allowed */
170 __("The excerpt field cannot contain more than %d words.", 'fluent-cart'),
171 $excerpt_length
172 );
173 }
174 return null;
175 }
176 ],
177 'post_status' => ['required', 'string'],
178 'post_date' => function ($attribute, $value) {
179 return $this->validatePostDate($attribute, $value);
180 },
181 'comment_status' => 'nullable|sanitizeText|maxLength:100',
182 'detail.fulfillment_type' => 'required|sanitizeText|maxLength:100',
183 'detail.variation_type' => 'required|sanitizeText|maxLength:100',
184 'detail.min_price' => 'nullable|numeric',
185 'detail.max_price' => 'nullable|numeric',
186 'detail.stock_availability' => 'nullable|sanitizeText',
187 'detail.manage_stock' => 'nullable|numeric',
188 'detail.manage_downloadable' => 'nullable|numeric',
189 'detail.other_info' => 'nullable|array',
190 'detail.other_info.group_pricing_by' => 'nullable|sanitizeText|in:payment_type,repeat_interval,none',
191 'detail.other_info.sold_individually' => 'nullable|sanitizeText|in:yes,no',
192 'detail.other_info.use_pricing_table' => 'nullable|sanitizeText',
193 'detail.other_info.shipping_class' => ['nullable', function ($attribute, $value) {
194 return $this->validateShippingClassId($attribute, $value);
195 }],
196 'detail.other_info.tax_class' => ['nullable', function ($attribute, $value) {
197 return $this->validateTaxClassId($attribute, $value);
198 }],
199 'detail.other_info.active_editor' => 'nullable|sanitizeText',
200 'product_terms' => 'nullable|array',
201 'product_terms.*' => 'nullable|array',
202 'product_terms.*.*' => 'nullable|numeric',
203
204 // 'variants' => 'required_if:post_status,publish,future',
205 'variants.*.variation_title' => 'required|sanitizeText|maxLength:200',
206 'variants.*.fulfillment_type' => ['sanitizeText', function ($attribute, $value) {
207 if (!in_array($value, ['physical', 'digital'])) {
208 return __('Invalid fulfillment type.', 'fluent-cart');
209 }
210 return null;
211 }],
212 'variants.*.post_id' => 'required|numeric',
213 'variants.*.item_price' => 'nullable|numeric|min:0',
214 'variants.*.compare_price' => [
215 'nullable',
216 'numeric',
217 function ($attribute, $value) {
218 $index = explode('.', $attribute)[1];
219 $itemPrice = $this->get("variants.$index.item_price");
220 if (empty($itemPrice)) {
221 $itemPrice = 0;
222 }
223 if ($value !== null && $value < $itemPrice) {
224 return sprintf(__("Compare price must be greater than or equal to item price.", 'fluent-cart'));
225 }
226 return null;
227 },
228 ],
229 'variants.*.manage_cost' => 'nullable|sanitizeText|maxLength:10',
230 // 'variants.*.shipping_class' => ['nullable', 'numeric', function ($attribute, $value) {
231 // return $this->validateShippingClassId($attribute, $value);
232 // }],
233 // 'variants.*.item_cost' => 'required_if:variants.*.manage_cost,true',
234 'variants.*.serial_index' => 'nullable|numeric',
235 // 'variants.*.downloadable' => 'nullable|sanitizeText|maxLength:10',
236 ];
237
238 if ($variationType === 'simple') {
239 $variantsOtherInfoRules = [
240
241 'variants.*.other_info' => 'required|array',
242 'variants.*.other_info.description' => 'nullable|sanitizeTextArea|maxLength:255',
243 'variants.*.other_info.payment_type' => 'required|sanitizeText|in:onetime,subscription',
244 'variants.*.other_info.times' => 'nullable|sanitizeText|maxLength:50',
245 'variants.*.other_info.trial_days' => 'nullable|sanitizeText|maxLength:365',
246 'variants.*.other_info.repeat_interval' => 'required_if:variants.*.other_info.payment_type,subscription|sanitizeText|maxLength:100',
247 'variants.*.other_info.billing_summary' => 'nullable|sanitizeTextArea|maxLength:255',
248 'variants.*.other_info.manage_setup_fee' => 'required_if:variants.*.other_info.payment_type,subscription|sanitizeText|maxLength:100',
249 'variants.*.other_info.signup_fee' => 'required_if:variants.*.other_info.manage_setup_fee,yes',
250 'variants.*.other_info.signup_fee_name' => 'required_if:variants.*.other_info.manage_setup_fee,yes|sanitizeText|maxLength:100',
251 ];
252 $rules = array_merge($rules, $variantsOtherInfoRules);
253
254 }
255
256
257 // $stockValidations = [
258 // 'variants.*.manage_stock' => 'nullable|numeric',
259 // 'variants.*.stock_status' => 'required_if:detail.manage_stock,1|sanitizeText|maxLength:50',
260 // 'variants.*.total_stock' => 'required|numeric',
261 // 'variants.*.available' => 'required|numeric',
262 // 'variants.*.committed' => 'required|numeric',
263 // 'variants.*.on_hold' => 'required|numeric',
264 // ];
265 //
266 // if($this->get('detail.manage_stock') == 1) {
267 // $rules = array_merge($rules, $stockValidations);
268 // }
269 return $rules;
270 }
271
272 public function afterValidation($validator): array
273 {
274 $data = $this->get();
275 foreach (Arr::get($data, 'variants', []) as $index => &$variant) {
276 if (empty($variant['item_price'])) {
277 $variant['item_price'] = 0;
278 }
279 }
280 return $data;
281 }
282
283
284 /**
285 * @return array
286 */
287 public function messages(): array
288 {
289 $messages = [
290 'post_title.required' => esc_html__('Title is required.', 'fluent-cart'),
291 'post_title.max' => esc_html__('Title may not be greater than 200 characters.', 'fluent-cart'),
292 'detail.fulfillment_type.required' => esc_html__('Fulfilment Type is required.', 'fluent-cart'),
293 'detail.variation_type.required' => esc_html__('Variation Type is required.', 'fluent-cart'),
294 'variants.required_if' => esc_html__('Pricing is required when status is publish or scheduled.', 'fluent-cart'),
295 'variants.*.variation_title.required' => esc_html__('Title is required.', 'fluent-cart'),
296 'variants.*.variation_title.max' => esc_html__('Title may not be greater than 200 characters.', 'fluent-cart'),
297 'variants.*.item_price.required' => esc_html__('Price is required.', 'fluent-cart'),
298 'variants.*.item_price.numeric' => esc_html__('Price must be a number.', 'fluent-cart'),
299 'variants.*.item_price.min' => esc_html__('Price must be a positive number greater than 0.', 'fluent-cart'),
300
301 ];
302
303 $variationType = Arr::get($this->all(), 'detail.variation_type', 'simple');
304 if ($variationType === 'simple') {
305 $otherInfoMessages = [
306 'variants.*.stock_status.required_if' => esc_html__('Stock status is required.', 'fluent-cart'),
307 'variants.*.item_cost.required_if' => esc_html__('Item cost is required.', 'fluent-cart'),
308 'variants.*.other_info.description.max' => esc_html__('Description may not be greater than 255 characters.', 'fluent-cart'),
309 'variants.*.other_info.payment_type.required' => esc_html__('Payment Type is required.', 'fluent-cart'),
310 'variants.*.other_info.times.required_if' => esc_html__('Times is required.', 'fluent-cart'),
311 'variants.*.other_info.repeat_interval.required_if' => esc_html__('Interval is required.', 'fluent-cart'),
312 'variants.*.other_info.signup_fee.required_if' => esc_html__('Setup Fee Amount is required.', 'fluent-cart'),
313 'variants.*.other_info.signup_fee_name.required_if' => esc_html__('Setup Fee Name is required.', 'fluent-cart'),
314 ];
315
316 $messages = array_merge($messages, $otherInfoMessages);
317 }
318
319 return $messages;
320 }
321
322
323 /**
324 * @return array
325 */
326 public function sanitize(): array
327 {
328 return $this->getSanitizersForExistingFields();
329 }
330
331 /**
332 * Only return sanitizers for fields that actually exist in the request
333 *
334 * @return array
335 */
336 private function getSanitizersForExistingFields(): array
337 {
338 $data = $this->all();
339 $sanitizers = [];
340
341 // Basic fields
342 $basicFieldMap = [
343 'ID' => 'intval',
344 'post_title' => 'sanitize_text_field',
345 'post_name' => 'sanitize_text_field',
346 'post_status' => 'sanitize_text_field',
347 'comment_status' => 'sanitize_text_field',
348 'post_date' => 'sanitize_text_field',
349 'post_excerpt' => 'wp_strip_all_tags',
350 'post_content' => 'wp_kses_post',
351 'metaValue' => function ($value) { return $value; },
352 ];
353
354 foreach ($basicFieldMap as $field => $sanitizer) {
355 if (array_key_exists($field, $data)) {
356 $sanitizers[$field] = $sanitizer;
357 }
358 }
359
360 // Detail fields
361 if (isset($data['detail']) && is_array($data['detail'])) {
362 $detailFieldMap = [
363 'detail.id' => 'intval',
364 'detail.post_id' => 'intval',
365 'detail.fulfillment_type' => 'sanitize_text_field',
366 'detail.variation_type' => 'sanitize_key',
367 'detail.stock_availability' => 'sanitize_key',
368 'detail.manage_stock' => 'intval',
369 'detail.manage_downloadable' => 'intval',
370 'detail.default_variation_id' => 'intval',
371 'detail.other_info.group_pricing_by' => 'sanitize_text_field',
372 'detail.other_info.sold_individually' => 'sanitize_text_field',
373 'detail.other_info.use_pricing_table' => 'sanitize_text_field',
374 'detail.other_info.shipping_class' => 'intval',
375 'detail.other_info.tax_class' => 'intval',
376 'detail.other_info.active_editor' => 'sanitize_text_field',
377 ];
378
379 foreach ($detailFieldMap as $field => $sanitizer) {
380 if (Arr::has($data, $field)) {
381 $sanitizers[$field] = $sanitizer;
382 }
383 }
384 }
385
386 // Product terms
387 if (isset($data['product_terms']) && is_array($data['product_terms'])) {
388 $sanitizers['product_terms.*.product-categories'] = 'sanitize_text_field';
389 $sanitizers['product_terms.*.product-brands'] = 'sanitize_text_field';
390 }
391
392 // Gallery
393 if (isset($data['gallery']) && is_array($data['gallery'])) {
394 $sanitizers['gallery.*.id'] = 'intval';
395 $sanitizers['gallery.*.url'] = function ($value) {
396 return empty($value) ? '' : sanitize_url($value);
397 };
398 $sanitizers['gallery.*.title'] = 'sanitize_text_field';
399 }
400
401 // Variants
402 if (isset($data['variants']) && is_array($data['variants'])) {
403 foreach ($data['variants'] as $index => $variant) {
404 $variantFieldMap = [
405 "variants.$index.id" => 'intval',
406 "variants.$index.rowId" => 'intval',
407 "variants.$index.post_id" => 'intval',
408 "variants.$index.variation_title" => 'sanitize_text_field',
409 "variants.$index.item_price" => 'floatval',
410 "variants.$index.compare_price" => 'floatval',
411 "variants.$index.item_cost" => 'floatval',
412 "variants.$index.manage_cost" => 'sanitize_text_field',
413 "variants.$index.total_stock" => 'intval',
414 "variants.$index.available" => 'intval',
415 "variants.$index.shipping_class" => function ($value) {
416 return $value ? intval($value) : null;
417 },
418 "variants.$index.committed" => 'intval',
419 "variants.$index.on_hold" => 'intval',
420 "variants.$index.manage_stock" => 'intval',
421 "variants.$index.stock_status" => 'sanitize_key',
422 "variants.$index.serial_index" => 'intval',
423 "variants.$index.downloadable" => 'sanitize_text_field',
424 "variants.$index.fulfillment_type" => 'sanitize_text_field',
425 "variants.$index.sku" => function ($value) {
426 return empty($value) ? null : sanitize_text_field($value);
427 },
428 ];
429
430 foreach ($variantFieldMap as $field => $sanitizer) {
431 if (Arr::has($data, $field)) {
432 $sanitizers[$field] = $sanitizer;
433 }
434 }
435
436 // Variant media
437 if (isset($variant['media']) && is_array($variant['media'])) {
438 $sanitizers["variants.$index.media.*.id"] = 'intval';
439 $sanitizers["variants.$index.media.*.url"] = function ($value) {
440 return empty($value) ? '' : sanitize_url($value);
441 };
442 $sanitizers["variants.$index.media.*.title"] = 'sanitize_text_field';
443 }
444
445 // Variant other_info
446 if (isset($variant['other_info'])) {
447 $sanitizers["variants.$index.other_info"] = function ($value) {
448 return is_array($value) ? $value : [];
449 };
450
451 if (is_array($variant['other_info'])) {
452 $otherInfoFieldMap = [
453 "variants.$index.other_info.description" => function ($value) { return $value; },
454 "variants.$index.other_info.payment_type" => 'sanitize_text_field',
455 "variants.$index.other_info.times" => 'sanitize_text_field',
456 "variants.$index.other_info.repeat_interval" => 'sanitize_text_field',
457 "variants.$index.other_info.billing_summary" => 'sanitize_text_field',
458 "variants.$index.other_info.manage_setup_fee" => 'sanitize_text_field',
459 "variants.$index.other_info.signup_fee" => 'floatval',
460 "variants.$index.other_info.signup_fee_name" => 'sanitize_text_field',
461 "variants.$index.other_info.installment" => 'sanitize_text_field',
462 ];
463
464 foreach ($otherInfoFieldMap as $field => $sanitizer) {
465 if (Arr::has($data, $field)) {
466 $sanitizers[$field] = $sanitizer;
467 }
468 }
469 }
470 }
471 }
472 }
473
474 return $sanitizers;
475 }
476 }
477