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

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

415 lines 18.6 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 ProductRequest 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
34 $fulfilmentType = Arr::get(
35 $data,
36 'detail.fulfillment_type',
37 Arr::get($data, 'detail.fulfillment_type', 'physical')
38 );
39
40 $variants = Arr::wrap(
41 Arr::get($data, 'variants', [])
42 );
43
44 foreach ($variants as $index => &$variant) {
45 $variant['fulfillment_type'] = $variant['fulfillment_type'] ?? $fulfilmentType;
46
47 $variant['other_info'] = Arr::wrap(Arr::get($variant, 'other_info')) ?? [];
48
49 // get payment_type from $variant
50 $paymentType = Arr::get($variant, 'other_info.payment_type', 'onetime');
51
52 $variant['shipping_class'] = Arr::get($variant, 'shipping_class', null);
53
54 $variant['other_info'] = [
55 'payment_type' => $paymentType,
56 'times' => Arr::get($variant, 'other_info.times', ''),
57 'trial_days' => Arr::get($variant, 'other_info.trial_days', ''),
58 //'purchasable' => Arr::get($variant, 'other_info.purchasable', ''),
59 'repeat_interval' => Arr::get($variant, 'other_info.repeat_interval', 'yearly'),
60 'billing_summary' => Arr::get($variant, 'other_info.billing_summary', ''),
61 'manage_setup_fee' => Arr::get($variant, 'other_info.manage_setup_fee', 'no'),
62 'signup_fee_name' => Arr::get($variant, 'other_info.signup_fee_name', ''),
63 'signup_fee' => Arr::get($variant, 'other_info.signup_fee', ''),
64 'setup_fee_per_item' => Arr::get($variant, 'other_info.setup_fee_per_item', 'no'),
65 'installment' => Arr::get($variant, 'other_info.installment', 'no'),
66 ];
67
68 $data['variants'][$index] = $variant;
69 }
70
71 return $data;
72 }
73
74 function validateShippingClassId($attribute, $value): ?string
75 {
76 static $checked = [];
77
78 if (empty($value)) {
79 return null;
80 }
81
82 // Return cached result if we've already checked this value
83 if (isset($checked[$value])) {
84 return $checked[$value];
85 }
86
87 if (!is_numeric($value)) {
88 $checked[$value] = __("Invalid Shipping Class.", 'fluent-cart');
89 return $checked[$value];
90 }
91
92 if (empty(ShippingClass::query()->find($value))) {
93 $checked[$value] = __("Invalid Shipping Class.", 'fluent-cart');
94 return $checked[$value];
95 }
96
97 return null;
98
99 }
100
101 function validateTaxClassId($attribute, $value): ?string
102 {
103 static $checked = [];
104
105 if (empty($value)) {
106 return null;
107 }
108
109 // Return cached result if we've already checked this value
110 if (isset($checked[$value])) {
111 return $checked[$value];
112 }
113
114 if (!is_numeric($value)) {
115 $checked[$value] = __("Invalid Tax Class.", 'fluent-cart');
116 return $checked[$value];
117 }
118
119 if (empty(\FluentCart\App\Models\TaxClass::query()->find($value))) {
120 $checked[$value] = __("Invalid Tax Class.", 'fluent-cart');
121 return $checked[$value];
122 }
123
124 return null;
125
126 }
127
128 public function validatePostDate($attribute, $value): ?string
129 {
130 if ($this->get('post_status') !== 'future') {
131 return null;
132 }
133
134 if (empty($value)) {
135 return __("The post date is required when status is scheduled.", 'fluent-cart');
136 }
137 $currentTime = DateTime::gmtNow();
138 try {
139 $postDate = DateTime::anyTimeToGmt($value);
140 } catch (\Exception $exception) {
141 return __("The post date is invalid.", 'fluent-cart');
142 }
143
144 if ($postDate < $currentTime) {
145 return sprintf(__("The post date must be in the future.", 'fluent-cart'));
146 }
147 return null;
148 }
149
150
151 /**
152 * @return array
153 */
154 public function rules(): array
155 {
156
157 $variationType = Arr::get($this->all(), 'detail.variation_type', 'simple');
158 $rules = [
159 'post_title' => 'required|sanitizeText|maxLength:200',
160 'post_excerpt' => [
161 'nullable',
162 'string',
163 'validate_excerpt' => function ($attr, $value) {
164 $words = explode(' ', $value);
165 $excerpt_length = (int)apply_filters('excerpt_length', 55);
166 if (count($words) > $excerpt_length) {
167 return sprintf(
168 /* translators: %d: The maximum number of words allowed */
169 __("The excerpt field cannot contain more than %d words.", 'fluent-cart'),
170 $excerpt_length
171 );
172 }
173 return null;
174 }
175 ],
176 'post_status' => ['required', 'string'],
177 'post_date' => function ($attribute, $value) {
178 return $this->validatePostDate($attribute, $value);
179 },
180 'comment_status' => 'nullable|sanitizeText|maxLength:100',
181 'detail.fulfillment_type' => 'required|sanitizeText|maxLength:100',
182 'detail.variation_type' => 'required|sanitizeText|maxLength:100',
183 'detail.min_price' => 'nullable|numeric',
184 'detail.max_price' => 'nullable|numeric',
185 'detail.stock_availability' => 'nullable|sanitizeText',
186 'detail.manage_stock' => 'nullable|numeric',
187 'detail.manage_downloadable' => 'nullable|numeric',
188 'detail.other_info' => 'nullable|array',
189 'detail.other_info.group_pricing_by' => 'nullable|sanitizeText|in:payment_type,repeat_interval,none',
190 'detail.other_info.sold_individually' => 'nullable|sanitizeText|in:yes,no',
191 'detail.other_info.use_pricing_table' => 'nullable|sanitizeText',
192 'detail.other_info.shipping_class' => ['nullable', function ($attribute, $value) {
193 return $this->validateShippingClassId($attribute, $value);
194 }],
195 'detail.other_info.tax_class' => ['nullable', function ($attribute, $value) {
196 return $this->validateTaxClassId($attribute, $value);
197 }],
198 'detail.other_info.active_editor' => 'nullable|sanitizeText',
199 'product_terms' => 'nullable|array',
200 'product_terms.*' => 'nullable|array',
201 'product_terms.*.*' => 'nullable|numeric',
202
203 // 'variants' => 'required_if:post_status,publish,future',
204 'variants.*.variation_title' => 'required|sanitizeText|maxLength:200',
205 'variants.*.post_id' => 'required|numeric',
206 'variants.*.item_price' => 'nullable|numeric|min:0',
207 'variants.*.compare_price' => [
208 'nullable',
209 'numeric',
210 function ($attribute, $value) {
211 $index = explode('.', $attribute)[1];
212 $itemPrice = $this->get("variants.$index.item_price");
213 if (empty($itemPrice)) {
214 $itemPrice = 0;
215 }
216 if ($value !== null && $value < $itemPrice) {
217 return sprintf(__("Compare price must be greater than or equal to item price.", 'fluent-cart'));
218 }
219 return null;
220 },
221 ],
222 'variants.*.manage_cost' => 'nullable|sanitizeText|maxLength:10',
223 // 'variants.*.shipping_class' => ['nullable', 'numeric', function ($attribute, $value) {
224 // return $this->validateShippingClassId($attribute, $value);
225 // }],
226 // 'variants.*.item_cost' => 'required_if:variants.*.manage_cost,true',
227 'variants.*.serial_index' => 'nullable|numeric',
228 // 'variants.*.downloadable' => 'nullable|sanitizeText|maxLength:10',
229 ];
230
231 if ($variationType === 'simple') {
232 $variantsOtherInfoRules = [
233
234 'variants.*.other_info' => 'required|array',
235 'variants.*.other_info.description' => 'nullable|sanitizeTextArea|maxLength:255',
236 'variants.*.other_info.payment_type' => 'required|sanitizeText|in:onetime,subscription',
237 'variants.*.other_info.times' => 'nullable|sanitizeText|maxLength:50',
238 'variants.*.other_info.trial_days' => 'nullable|sanitizeText|maxLength:365',
239 'variants.*.other_info.repeat_interval' => 'required_if:variants.*.other_info.payment_type,subscription|sanitizeText|maxLength:100',
240 'variants.*.other_info.billing_summary' => 'nullable|sanitizeTextArea|maxLength:255',
241 'variants.*.other_info.manage_setup_fee' => 'required_if:variants.*.other_info.payment_type,subscription|sanitizeText|maxLength:100',
242 'variants.*.other_info.signup_fee' => 'required_if:variants.*.other_info.manage_setup_fee,yes',
243 'variants.*.other_info.signup_fee_name' => 'required_if:variants.*.other_info.manage_setup_fee,yes|sanitizeText|maxLength:100',
244 ];
245 $rules = array_merge($rules, $variantsOtherInfoRules);
246
247 }
248
249
250 // $stockValidations = [
251 // 'variants.*.manage_stock' => 'nullable|numeric',
252 // 'variants.*.stock_status' => 'required_if:detail.manage_stock,1|sanitizeText|maxLength:50',
253 // 'variants.*.total_stock' => 'required|numeric',
254 // 'variants.*.available' => 'required|numeric',
255 // 'variants.*.committed' => 'required|numeric',
256 // 'variants.*.on_hold' => 'required|numeric',
257 // ];
258 //
259 // if($this->get('detail.manage_stock') == 1) {
260 // $rules = array_merge($rules, $stockValidations);
261 // }
262 return $rules;
263 }
264
265 public function afterValidation($validator): array
266 {
267 $data = $this->get();
268 foreach (Arr::get($data, 'variants', []) as $index => &$variant) {
269 if (empty($variant['item_price'])) {
270 $variant['item_price'] = 0;
271 }
272 }
273 return $data;
274 }
275
276
277 /**
278 * @return array
279 */
280 public function messages(): array
281 {
282 $messages = [
283 'post_title.required' => esc_html__('Title is required.', 'fluent-cart'),
284 'post_title.max' => esc_html__('Title may not be greater than 200 characters.', 'fluent-cart'),
285 'detail.fulfillment_type.required' => esc_html__('Fulfilment Type is required.', 'fluent-cart'),
286 'detail.variation_type.required' => esc_html__('Variation Type is required.', 'fluent-cart'),
287 'variants.required_if' => esc_html__('Pricing is required when status is publish or scheduled.', 'fluent-cart'),
288 'variants.*.variation_title.required' => esc_html__('Title is required.', 'fluent-cart'),
289 'variants.*.variation_title.max' => esc_html__('Title may not be greater than 200 characters.', 'fluent-cart'),
290 'variants.*.item_price.required' => esc_html__('Price is required.', 'fluent-cart'),
291 'variants.*.item_price.numeric' => esc_html__('Price must be a number.', 'fluent-cart'),
292 'variants.*.item_price.min' => esc_html__('Price must be a positive number greater than 0.', 'fluent-cart'),
293
294 ];
295
296 $variationType = Arr::get($this->all(), 'detail.variation_type', 'simple');
297 if ($variationType === 'simple') {
298 $otherInfoMessages = [
299 'variants.*.stock_status.required_if' => esc_html__('Stock status is required.', 'fluent-cart'),
300 'variants.*.item_cost.required_if' => esc_html__('Item cost is required.', 'fluent-cart'),
301 'variants.*.other_info.description.max' => esc_html__('Description may not be greater than 255 characters.', 'fluent-cart'),
302 'variants.*.other_info.payment_type.required' => esc_html__('Payment Type is required.', 'fluent-cart'),
303 'variants.*.other_info.times.required_if' => esc_html__('Times is required.', 'fluent-cart'),
304 'variants.*.other_info.repeat_interval.required_if' => esc_html__('Interval is required.', 'fluent-cart'),
305 'variants.*.other_info.signup_fee.required_if' => esc_html__('Setup Fee Amount is required.', 'fluent-cart'),
306 'variants.*.other_info.signup_fee_name.required_if' => esc_html__('Setup Fee Name is required.', 'fluent-cart'),
307 ];
308
309 $messages = array_merge($messages, $otherInfoMessages);
310 }
311
312 return $messages;
313 }
314
315
316 /**
317 * @return array
318 */
319 public function sanitize(): array
320 {
321
322 $sanitizer = [
323 'ID' => 'intval',
324 'post_title' => 'sanitize_text_field',
325 'post_name' => 'sanitize_text_field',
326 'post_status' => 'sanitize_text_field',
327 'comment_status' => 'sanitize_text_field',
328 'post_date' => 'sanitize_text_field',
329 'post_excerpt' => 'wp_strip_all_tags',
330 'post_content' => 'wp_kses_post',
331
332 'detail.id' => 'intval',
333 'detail.post_id' => 'intval',
334 'detail.fulfillment_type' => 'sanitize_text_field',
335 'detail.variation_type' => 'sanitize_key',
336 'detail.stock_availability' => 'sanitize_key',
337 'detail.manage_stock' => 'intval',
338 'detail.manage_downloadable' => 'intval',
339 'detail.default_variation_id' => 'intval',
340 'detail.other_info.group_pricing_by' => 'sanitize_text_field',
341 'detail.other_info.sold_individually' => 'sanitize_text_field',
342 'detail.other_info.use_pricing_table' => 'sanitize_text_field',
343 'detail.other_info.shipping_class' => 'intval',
344 'detail.other_info.tax_class' => 'intval',
345 'detail.other_info.active_editor' => 'sanitize_text_field',
346 'variants.*.id' => 'intval',
347 'variants.*.rowId' => 'intval',
348 'variants.*.post_id' => 'intval',
349 'variants.*.variation_title' => 'sanitize_text_field',
350 'variants.*.item_price' => 'floatval',
351 'variants.*.compare_price' => 'floatval',
352 'variants.*.item_cost' => 'floatval',
353
354 'product_terms.*.product-categories' => 'sanitize_text_field',
355 'product_terms.*.product-brands' => 'sanitize_text_field',
356
357 'gallery.*.id' => 'intval',
358 'gallery.*.url' => function ($value) {
359 if (empty($value)) {
360 return '';
361 }
362
363 return sanitize_url($value);
364 },
365 'gallery.*.title' => 'sanitize_text_field',
366 'metaValue' => function ($value) {
367 return $value;
368 }
369 ];
370
371 $otherInfoSanitizer = [
372 'variants.*.manage_cost' => 'sanitize_text_field',
373 'variants.*.total_stock' => 'intval',
374 'variants.*.available' => 'intval',
375 'variants.*.shipping_class' => 'intval',
376 'variants.*.committed' => 'intval',
377 'variants.*.on_hold' => 'intval',
378 'variants.*.manage_stock' => 'intval',
379 'variants.*.stock_status' => 'sanitize_key',
380 'variants.*.serial_index' => 'intval',
381 'variants.*.media.*.id' => 'intval',
382 'variants.*.media.*.url' => function ($value) {
383 if (empty($value)) {
384 return '';
385 }
386
387 return sanitize_url($value);
388 },
389 'variants.*.media.*.title' => 'sanitize_text_field',
390
391 'variants.*.downloadable' => 'sanitize_text_field',
392
393 'variants.*.other_info' => function ($value) {
394 return is_array($value) ? $value : [];
395 },
396 'variants.*.other_info.description' => function ($value) {
397 return $value;
398 },
399 'variants.*.other_info.payment_type' => 'sanitize_text_field',
400 'variants.*.other_info.times' => 'sanitize_text_field',
401 'variants.*.other_info.repeat_interval' => 'sanitize_text_field',
402 'variants.*.other_info.billing_summary' => 'sanitize_text_field',
403 'variants.*.other_info.manage_setup_fee' => 'sanitize_text_field',
404 'variants.*.other_info.signup_fee' => 'floatval',
405 'variants.*.other_info.signup_fee_name' => 'sanitize_text_field',
406 'variants.*.other_info.installment' => 'sanitize_text_field',
407 ];
408
409
410 $sanitizer = array_merge($sanitizer, $otherInfoSanitizer);
411
412 return $sanitizer;
413 }
414 }
415