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

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

638 lines 28.9 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\Helpers\Helper;
6 use FluentCart\App\Models\ProductVariation;
7 use FluentCart\App\Models\ShippingClass;
8 use FluentCart\App\Modules\FluentPlayer\ProductVideoSettings;
9 use FluentCart\App\Services\DateTime\DateTime;
10 use FluentCart\App\Http\Rules\RequiredWhenRule;
11 use FluentCart\Framework\Foundation\RequestGuard;
12 use FluentCart\Framework\Support\Arr;
13
14 class ProductUpdateRequest extends RequestGuard
15 {
16 /**
17 * Drop the subscription fields from any variant saved as one-time, so a
18 * product switched back to a single payment does not keep billing settings
19 * the merchant can no longer see.
20 *
21 * This does NOT fill missing keys. The block below it — which rebuilt
22 * `other_info` and `fulfillment_type` from defaults, as ProductRequest still
23 * does on create — stays commented out deliberately: on an update the payload
24 * is a partial row, so rebuilding the column from defaults would overwrite a
25 * stored subscription's interval and setup fee on an unrelated edit. Callers
26 * therefore have to send a complete variant row; the editor builds one in
27 * Models/Product/productUpdatePayload.js by merging each change record with
28 * the variation it came from.
29 *
30 * @return array The request data, with dead subscription fields removed.
31 */
32 public function beforeValidation()
33 {
34 $data = $this->all();
35
36 $subscriptionFields = ['trial_days', 'times', 'repeat_interval', 'billing_summary', 'manage_setup_fee', 'signup_fee', 'signup_fee_name', 'setup_fee_per_item'];
37 foreach (Arr::get($data, 'variants', []) as $index => $variant) {
38 if (Arr::get($variant, 'other_info.payment_type') === 'onetime') {
39 foreach ($subscriptionFields as $field) {
40 unset($data['variants'][$index]['other_info'][$field]);
41 }
42 }
43 }
44
45 return $data;
46 //
47 // $fulfilmentType = Arr::get(
48 // $data,
49 // 'detail.fulfillment_type',
50 // Arr::get($data, 'detail.fulfillment_type', 'physical')
51 // );
52 //
53 // $variants = Arr::wrap(
54 // Arr::get($data, 'variants', [])
55 // );
56 //
57 // foreach ($variants as $index => &$variant) {
58 // $variant['fulfillment_type'] = $variant['fulfillment_type'] ?? $fulfilmentType;
59 //
60 // $variant['other_info'] = Arr::wrap(Arr::get($variant, 'other_info')) ?? [];
61 //
62 // // get payment_type from $variant
63 // $paymentType = Arr::get($variant, 'other_info.payment_type', 'onetime');
64 //
65 // $variant['shipping_class'] = Arr::get($variant, 'shipping_class', null);
66 //
67 // $variant['other_info'] = [
68 // 'payment_type' => $paymentType,
69 // 'times' => Arr::get($variant, 'other_info.times', ''),
70 // 'trial_days' => Arr::get($variant, 'other_info.trial_days', ''),
71 // //'purchasable' => Arr::get($variant, 'other_info.purchasable', ''),
72 // 'repeat_interval' => Arr::get($variant, 'other_info.repeat_interval', 'yearly'),
73 // 'billing_summary' => Arr::get($variant, 'other_info.billing_summary', ''),
74 // 'manage_setup_fee' => Arr::get($variant, 'other_info.manage_setup_fee', 'no'),
75 // 'signup_fee_name' => Arr::get($variant, 'other_info.signup_fee_name', ''),
76 // 'signup_fee' => Arr::get($variant, 'other_info.signup_fee', ''),
77 // 'setup_fee_per_item' => Arr::get($variant, 'other_info.setup_fee_per_item', 'no'),
78 // 'installment' => Arr::get($variant, 'other_info.installment', 'no'),
79 // ];
80 //
81 // $data['variants'][$index] = $variant;
82 // }
83 //
84 // return $data;
85 }
86
87 function validateShippingClassId($attribute, $value): ?string
88 {
89 static $checked = [];
90
91 if (empty($value)) {
92 return null;
93 }
94
95 // Return cached result if we've already checked this value
96 if (isset($checked[$value])) {
97 return $checked[$value];
98 }
99
100 if (!is_numeric($value)) {
101 $checked[$value] = __("Invalid Shipping Class.", 'fluent-cart');
102 return $checked[$value];
103 }
104
105 if (empty(ShippingClass::query()->find($value))) {
106 $checked[$value] = __("Invalid Shipping Class.", 'fluent-cart');
107 return $checked[$value];
108 }
109
110 return null;
111
112 }
113
114 function validateTaxClassId($attribute, $value): ?string
115 {
116 static $checked = [];
117
118 if (empty($value)) {
119 return null;
120 }
121
122 // Return cached result if we've already checked this value
123 if (isset($checked[$value])) {
124 return $checked[$value];
125 }
126
127 if (!is_numeric($value)) {
128 $checked[$value] = __("Invalid Tax Class.", 'fluent-cart');
129 return $checked[$value];
130 }
131
132 if (empty(\FluentCart\App\Models\TaxClass::query()->find($value))) {
133 $checked[$value] = __("Invalid Tax Class.", 'fluent-cart');
134 return $checked[$value];
135 }
136
137 return null;
138
139 }
140
141 function validateTaxClassSlug($attribute, $value): ?string
142 {
143 static $checked = [];
144
145 if (empty($value)) {
146 return null;
147 }
148
149 $value = sanitize_text_field($value);
150
151 if (isset($checked[$value])) {
152 return $checked[$value];
153 }
154
155 if (empty(\FluentCart\App\Models\TaxClass::query()->where('slug', $value)->first())) {
156 $checked[$value] = __("Invalid Tax Class.", 'fluent-cart');
157 return $checked[$value];
158 }
159
160 return null;
161 }
162
163 public function validatePostDate($attribute, $value): ?string
164 {
165 if ($this->get('post_status') !== 'future') {
166 return null;
167 }
168
169 if (empty($value)) {
170 return __("The post date is required when status is scheduled.", 'fluent-cart');
171 }
172 $currentTime = DateTime::gmtNow();
173 try {
174 $postDate = DateTime::anyTimeToGmt($value);
175 } catch (\Exception $exception) {
176 return __("The post date is invalid.", 'fluent-cart');
177 }
178
179 if ($postDate < $currentTime) {
180 return sprintf(__("The post date must be in the future.", 'fluent-cart'));
181 }
182 return null;
183 }
184
185
186 /**
187 * @return array
188 */
189 public function rules(): array
190 {
191 $data = $this->all();
192 $hasDetail = isset($data['detail']) && is_array($data['detail']);
193 $hasVariants = isset($data['variants']) && is_array($data['variants']);
194
195 if (!$hasDetail && !$hasVariants) {
196 $rules = [];
197 if (isset($data['post_title'])) {
198 $rules['post_title'] = 'sanitizeText|maxLength:200';
199 }
200 if (isset($data['post_excerpt'])) {
201 $rules['post_excerpt'] = ['nullable', 'string'];
202 }
203 if (isset($data['post_status'])) {
204 $rules['post_status'] = ['string', function ($attribute, $value) {
205 if (!in_array($value, ['publish', 'draft', 'future', 'private'], true)) {
206 return __('Invalid post status provided.', 'fluent-cart');
207 }
208 return null;
209 }];
210 }
211 if (isset($data['post_status']) && $data['post_status'] === 'future') {
212 $rules['post_date'] = function ($attribute, $value) {
213 return $this->validatePostDate($attribute, $value);
214 };
215 }
216 return $rules;
217 }
218
219 $variationType = Arr::get($data, 'detail.variation_type', 'simple');
220 $rules = [
221 'post_title' => 'required|sanitizeText|maxLength:200',
222 'post_excerpt' => [
223 'nullable',
224 'string',
225 'validate_excerpt' => function ($attr, $value) {
226 $words = explode(' ', $value);
227 $excerpt_length = (int)apply_filters('excerpt_length', 55);
228 if (count($words) > $excerpt_length) {
229 return sprintf(
230 /* translators: %d: The maximum number of words allowed */
231 __("The excerpt field cannot contain more than %d words.", 'fluent-cart'),
232 $excerpt_length
233 );
234 }
235 return null;
236 }
237 ],
238 'post_status' => ['required', 'string'],
239 'post_date' => function ($attribute, $value) {
240 return $this->validatePostDate($attribute, $value);
241 },
242 'comment_status' => 'nullable|sanitizeText|maxLength:100',
243 'detail.fulfillment_type' => 'required|sanitizeText|maxLength:100',
244 'detail.variation_type' => 'required|sanitizeText|maxLength:100',
245 'detail.min_price' => 'nullable|numeric',
246 'detail.max_price' => 'nullable|numeric',
247 'detail.stock_availability' => 'nullable|sanitizeText',
248 'detail.manage_stock' => 'nullable|numeric',
249 'detail.manage_downloadable' => 'nullable|numeric',
250 'detail.other_info' => 'nullable|array',
251 'detail.other_info.group_pricing_by' => 'nullable|sanitizeText|in:payment_type,repeat_interval,none',
252 'detail.other_info.sold_individually' => 'nullable|sanitizeText|in:yes,no',
253 'detail.other_info.use_pricing_table' => 'nullable|sanitizeText',
254 'detail.other_info.shipping_class' => ['nullable', function ($attribute, $value) {
255 return $this->validateShippingClassId($attribute, $value);
256 }],
257 'detail.other_info.tax_class' => ['nullable', function ($attribute, $value) {
258 return $this->validateTaxClassId($attribute, $value);
259 }],
260 'detail.other_info.tax_exempt' => 'nullable|sanitizeText|in:yes,no',
261 'detail.other_info.active_editor' => 'nullable|sanitizeText',
262 'detail.other_info.reviews_enabled' => 'nullable|sanitizeText|in:yes,no',
263 'detail.other_info.fluent_player_video' => 'nullable|array',
264 'product_terms' => 'nullable|array',
265 'product_terms.*' => 'nullable|array',
266 'product_terms.*.*' => 'nullable|numeric',
267
268 // 'variants' => 'required_if:post_status,publish,future',
269 'variants.*.variation_title' => 'required|sanitizeText|maxLength:200',
270 'variants.*.fulfillment_type' => ['sanitizeText', function ($attribute, $value) {
271 if (!in_array($value, ['physical', 'digital'])) {
272 return __('Invalid fulfillment type.', 'fluent-cart');
273 }
274 return null;
275 }],
276 'variants.*.post_id' => 'required|numeric',
277 'variants.*.item_price' => 'nullable|numeric|min:0',
278 'variants.*.compare_price' => [
279 'nullable',
280 'numeric',
281 function ($attribute, $value) {
282 // Zero or empty means no compare-at price, which the storefront
283 // also treats as "none" — nothing to compare.
284 if ($value === null || $value === '' || (float) $value <= 0) {
285 return null;
286 }
287
288 $index = explode('.', $attribute)[1];
289 $itemPrice = $this->get("variants.$index.item_price");
290
291 // The editor posts only what changed, so a compare-price-only edit
292 // carries no item_price. Falling back to 0 made the comparison
293 // vacuous and the rule passed for any value; read the stored price
294 // for that variation instead, which is what the row is really
295 // being compared against.
296 if ($itemPrice === null || $itemPrice === '') {
297 $variantId = $this->get("variants.$index.id");
298
299 $itemPrice = $variantId
300 ? ProductVariation::query()->where('id', $variantId)->value('item_price')
301 : 0;
302 }
303
304 if ((float) $value < (float) $itemPrice) {
305 return sprintf(__("Compare price must be greater than or equal to item price.", 'fluent-cart'));
306 }
307
308 return null;
309 },
310 ],
311 'variants.*.manage_cost' => 'nullable|sanitizeText|maxLength:10',
312 // Variant tax classes are stored as slugs, not numeric IDs like the
313 // older product-detail tax field.
314 'variants.*.other_info.tax_class' => ['nullable', function ($attribute, $value) {
315 return $this->validateTaxClassSlug($attribute, $value);
316 }],
317 'variants.*.other_info.tax_exempt' => 'nullable|sanitizeText|in:yes,no',
318 // 'variants.*.shipping_class' => ['nullable', 'numeric', function ($attribute, $value) {
319 // return $this->validateShippingClassId($attribute, $value);
320 // }],
321 // 'variants.*.item_cost' => 'required_if:variants.*.manage_cost,true',
322 'variants.*.serial_index' => 'nullable|numeric',
323 // 'variants.*.downloadable' => 'nullable|sanitizeText|maxLength:10',
324 // Outside the variation_type gate below: the other_info rules there only
325 // register for 'simple', but a simple_variations save posts variants too.
326 'variants.*.other_info.times' => [
327 function ($attribute, $value) {
328 $index = explode('.', $attribute)[1];
329
330 return Helper::installmentTimesError($this->get("variants.$index.other_info"));
331 },
332 ],
333 ];
334
335 if ($variationType === 'simple') {
336 $variantsOtherInfoRules = [
337
338 'variants.*.other_info' => 'required|array',
339 'variants.*.other_info.description' => 'nullable|sanitizeTextArea|maxLength:255',
340 'variants.*.other_info.payment_type' => 'required|sanitizeText|in:onetime,subscription',
341 'variants.*.other_info.times' => [
342 function ($attribute, $value) {
343 $index = explode('.', $attribute)[1];
344 if ($this->get("variants.$index.other_info.payment_type") !== 'subscription') {
345 return null;
346 }
347 if (!empty($value) && !is_numeric($value)) {
348 return __('Times must be a number.', 'fluent-cart');
349 }
350 return Helper::installmentTimesError($this->get("variants.$index.other_info"));
351 },
352 ],
353 'variants.*.other_info.trial_days' => [
354 function ($attribute, $value) {
355 $index = explode('.', $attribute)[1];
356 if ($this->get("variants.$index.other_info.payment_type") !== 'subscription') {
357 return null;
358 }
359 if (!empty($value) && !is_numeric($value)) {
360 return __('Trial days must be a number.', 'fluent-cart');
361 }
362 if (!empty($value) && $value > 365) {
363 return __('Trial period cannot exceed 365 days.', 'fluent-cart');
364 }
365 return null;
366 },
367 ],
368 'variants.*.other_info.repeat_interval' => [
369 RequiredWhenRule::make(
370 'variants.*.other_info.payment_type',
371 'subscription',
372 esc_html__('Interval is required.', 'fluent-cart')
373 ),
374 'sanitizeText',
375 'maxLength:100',
376 ],
377 'variants.*.other_info.billing_summary' => 'nullable|sanitizeTextArea|maxLength:255',
378 'variants.*.other_info.manage_setup_fee' => [
379 RequiredWhenRule::make(
380 'variants.*.other_info.payment_type',
381 'subscription',
382 esc_html__('Setup Fee option is required.', 'fluent-cart')
383 ),
384 'sanitizeText',
385 'maxLength:100',
386 ],
387 'variants.*.other_info.signup_fee' => [
388 RequiredWhenRule::make(
389 'variants.*.other_info.manage_setup_fee',
390 'yes',
391 esc_html__('Setup Fee Amount is required.', 'fluent-cart')
392 ),
393 ],
394 'variants.*.other_info.signup_fee_name' => [
395 RequiredWhenRule::make(
396 'variants.*.other_info.manage_setup_fee',
397 'yes',
398 esc_html__('Setup Fee Name is required.', 'fluent-cart')
399 ),
400 'sanitizeText',
401 'maxLength:100',
402 ],
403 ];
404 $rules = array_merge($rules, $variantsOtherInfoRules);
405
406 }
407
408
409 // $stockValidations = [
410 // 'variants.*.manage_stock' => 'nullable|numeric',
411 // 'variants.*.stock_status' => 'required_if:detail.manage_stock,1|sanitizeText|maxLength:50',
412 // 'variants.*.total_stock' => 'required|numeric',
413 // 'variants.*.available' => 'required|numeric',
414 // 'variants.*.committed' => 'required|numeric',
415 // 'variants.*.on_hold' => 'required|numeric',
416 // ];
417 //
418 // if($this->get('detail.manage_stock') == 1) {
419 // $rules = array_merge($rules, $stockValidations);
420 // }
421 return $rules;
422 }
423
424 public function afterValidation($validator): array
425 {
426 $data = $this->get();
427 foreach (Arr::get($data, 'variants', []) as $index => &$variant) {
428 if (empty($variant['item_price'])) {
429 $variant['item_price'] = 0;
430 }
431 }
432 return $data;
433 }
434
435
436 /**
437 * @return array
438 */
439 public function messages(): array
440 {
441 $messages = [
442 'post_title.required' => esc_html__('Title is required.', 'fluent-cart'),
443 'post_title.max' => esc_html__('Title may not be greater than 200 characters.', 'fluent-cart'),
444 'detail.fulfillment_type.required' => esc_html__('Fulfilment Type is required.', 'fluent-cart'),
445 'detail.variation_type.required' => esc_html__('Variation Type is required.', 'fluent-cart'),
446 'variants.required_if' => esc_html__('Pricing is required when status is publish or scheduled.', 'fluent-cart'),
447 'variants.*.variation_title.required' => esc_html__('Title is required.', 'fluent-cart'),
448 'variants.*.variation_title.max' => esc_html__('Title may not be greater than 200 characters.', 'fluent-cart'),
449 'variants.*.item_price.required' => esc_html__('Price is required.', 'fluent-cart'),
450 'variants.*.item_price.numeric' => esc_html__('Price must be a number.', 'fluent-cart'),
451 'variants.*.item_price.min' => esc_html__('Price must be a positive number greater than 0.', 'fluent-cart'),
452
453 ];
454
455 $variationType = Arr::get($this->all(), 'detail.variation_type', 'simple');
456 if ($variationType === 'simple') {
457 $otherInfoMessages = [
458 'variants.*.stock_status.required_if' => esc_html__('Stock status is required.', 'fluent-cart'),
459 'variants.*.item_cost.required_if' => esc_html__('Item cost is required.', 'fluent-cart'),
460 'variants.*.other_info.description.max' => esc_html__('Description may not be greater than 255 characters.', 'fluent-cart'),
461 'variants.*.other_info.payment_type.required' => esc_html__('Payment Type is required.', 'fluent-cart'),
462 'variants.*.other_info.times.required_if' => esc_html__('Times is required.', 'fluent-cart'),
463 ];
464
465 $messages = array_merge($messages, $otherInfoMessages);
466 }
467
468 return $messages;
469 }
470
471
472 /**
473 * @return array
474 */
475 public function sanitize(): array
476 {
477 return $this->getSanitizersForExistingFields();
478 }
479
480 /**
481 * Only return sanitizers for fields that actually exist in the request
482 *
483 * @return array
484 */
485 private function getSanitizersForExistingFields(): array
486 {
487 $data = $this->all();
488 $sanitizers = [];
489
490 // Basic fields
491 $basicFieldMap = [
492 'ID' => 'intval',
493 'post_title' => 'sanitize_text_field',
494 'post_name' => 'sanitize_text_field',
495 'post_status' => 'sanitize_text_field',
496 'comment_status' => 'sanitize_text_field',
497 'post_date' => 'sanitize_text_field',
498 'post_excerpt' => 'wp_strip_all_tags',
499 'post_content' => 'wp_kses_post',
500 'metaValue' => function ($value) { return $value; },
501 ];
502
503 foreach ($basicFieldMap as $field => $sanitizer) {
504 if (array_key_exists($field, $data)) {
505 $sanitizers[$field] = $sanitizer;
506 }
507 }
508
509 // Detail fields
510 if (isset($data['detail']) && is_array($data['detail'])) {
511 $detailFieldMap = [
512 'detail.id' => 'intval',
513 'detail.post_id' => 'intval',
514 'detail.fulfillment_type' => 'sanitize_text_field',
515 'detail.variation_type' => 'sanitize_key',
516 'detail.stock_availability' => 'sanitize_key',
517 'detail.manage_stock' => 'intval',
518 'detail.manage_downloadable' => 'intval',
519 'detail.default_variation_id' => 'intval',
520 'detail.other_info.group_pricing_by' => 'sanitize_text_field',
521 'detail.other_info.sold_individually' => 'sanitize_text_field',
522 'detail.other_info.use_pricing_table' => 'sanitize_text_field',
523 'detail.other_info.shipping_class' => 'intval',
524 'detail.other_info.tax_class' => 'intval',
525 'detail.other_info.tax_exempt' => 'sanitize_text_field',
526 'detail.other_info.active_editor' => 'sanitize_text_field',
527 'detail.other_info.reviews_enabled' => 'sanitize_text_field',
528 'detail.other_info.fluent_player_video' => function ($value) {
529 return ProductVideoSettings::sanitize($value);
530 },
531 ];
532
533 foreach ($detailFieldMap as $field => $sanitizer) {
534 if (Arr::has($data, $field)) {
535 $sanitizers[$field] = $sanitizer;
536 }
537 }
538 }
539
540 // Product terms
541 if (isset($data['product_terms']) && is_array($data['product_terms'])) {
542 $sanitizers['product_terms.*.product-categories'] = 'sanitize_text_field';
543 $sanitizers['product_terms.*.product-brands'] = 'sanitize_text_field';
544 }
545
546 // Gallery
547 if (isset($data['gallery']) && is_array($data['gallery'])) {
548 $sanitizers['gallery.*.id'] = 'intval';
549 $sanitizers['gallery.*.url'] = function ($value) {
550 return empty($value) ? '' : sanitize_url($value);
551 };
552 $sanitizers['gallery.*.title'] = 'sanitize_text_field';
553 }
554
555 // Variants
556 if (isset($data['variants']) && is_array($data['variants'])) {
557 foreach ($data['variants'] as $index => $variant) {
558 $variantFieldMap = [
559 "variants.$index.id" => 'intval',
560 "variants.$index.rowId" => 'intval',
561 "variants.$index.post_id" => 'intval',
562 "variants.$index.variation_title" => 'sanitize_text_field',
563 "variants.$index.item_price" => 'floatval',
564 "variants.$index.compare_price" => 'floatval',
565 "variants.$index.item_cost" => 'floatval',
566 "variants.$index.manage_cost" => 'sanitize_text_field',
567 "variants.$index.total_stock" => 'intval',
568 "variants.$index.available" => 'intval',
569 "variants.$index.shipping_class" => function ($value) {
570 return $value ? intval($value) : null;
571 },
572 "variants.$index.committed" => 'intval',
573 "variants.$index.on_hold" => 'intval',
574 "variants.$index.manage_stock" => 'intval',
575 "variants.$index.stock_status" => 'sanitize_key',
576 "variants.$index.serial_index" => 'intval',
577 "variants.$index.downloadable" => 'sanitize_text_field',
578 "variants.$index.fulfillment_type" => 'sanitize_text_field',
579 "variants.$index.sku" => function ($value) {
580 // empty() treats the string "0" as empty too, which would silently
581 // convert a legitimate sku of "0" to NULL — sku is a textual
582 // identifier (VARCHAR), not a numeric flag. Match the explicit
583 // '' / null check ProductResource::update() already uses for the
584 // same reason when it clears a sku.
585 return ($value === '' || $value === null) ? null : sanitize_text_field($value);
586 },
587 ];
588
589 foreach ($variantFieldMap as $field => $sanitizer) {
590 if (Arr::has($data, $field)) {
591 $sanitizers[$field] = $sanitizer;
592 }
593 }
594
595 // Variant media
596 if (isset($variant['media']) && is_array($variant['media'])) {
597 $sanitizers["variants.$index.media.*.id"] = 'intval';
598 $sanitizers["variants.$index.media.*.url"] = function ($value) {
599 return empty($value) ? '' : sanitize_url($value);
600 };
601 $sanitizers["variants.$index.media.*.title"] = 'sanitize_text_field';
602 }
603
604 // Variant other_info
605 if (isset($variant['other_info'])) {
606 $sanitizers["variants.$index.other_info"] = function ($value) {
607 return is_array($value) ? $value : [];
608 };
609
610 if (is_array($variant['other_info'])) {
611 $otherInfoFieldMap = [
612 "variants.$index.other_info.description" => function ($value) { return $value; },
613 "variants.$index.other_info.payment_type" => 'sanitize_text_field',
614 "variants.$index.other_info.times" => 'sanitize_text_field',
615 "variants.$index.other_info.repeat_interval" => 'sanitize_text_field',
616 "variants.$index.other_info.billing_summary" => 'sanitize_text_field',
617 "variants.$index.other_info.manage_setup_fee" => 'sanitize_text_field',
618 "variants.$index.other_info.signup_fee" => 'floatval',
619 "variants.$index.other_info.signup_fee_name" => 'sanitize_text_field',
620 "variants.$index.other_info.installment" => 'sanitize_text_field',
621 "variants.$index.other_info.tax_class" => 'sanitize_text_field',
622 "variants.$index.other_info.tax_exempt" => 'sanitize_text_field',
623 ];
624
625 foreach ($otherInfoFieldMap as $field => $sanitizer) {
626 if (Arr::has($data, $field)) {
627 $sanitizers[$field] = $sanitizer;
628 }
629 }
630 }
631 }
632 }
633 }
634
635 return $sanitizers;
636 }
637 }
638