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 / app / Http / Requests / ProductUpdateRequest.php

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

636 lines 28.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\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.fluent_player_video' => 'nullable|array',
263 'product_terms' => 'nullable|array',
264 'product_terms.*' => 'nullable|array',
265 'product_terms.*.*' => 'nullable|numeric',
266
267 // 'variants' => 'required_if:post_status,publish,future',
268 'variants.*.variation_title' => 'required|sanitizeText|maxLength:200',
269 'variants.*.fulfillment_type' => ['sanitizeText', function ($attribute, $value) {
270 if (!in_array($value, ['physical', 'digital'])) {
271 return __('Invalid fulfillment type.', 'fluent-cart');
272 }
273 return null;
274 }],
275 'variants.*.post_id' => 'required|numeric',
276 'variants.*.item_price' => 'nullable|numeric|min:0',
277 'variants.*.compare_price' => [
278 'nullable',
279 'numeric',
280 function ($attribute, $value) {
281 // Zero or empty means no compare-at price, which the storefront
282 // also treats as "none" — nothing to compare.
283 if ($value === null || $value === '' || (float) $value <= 0) {
284 return null;
285 }
286
287 $index = explode('.', $attribute)[1];
288 $itemPrice = $this->get("variants.$index.item_price");
289
290 // The editor posts only what changed, so a compare-price-only edit
291 // carries no item_price. Falling back to 0 made the comparison
292 // vacuous and the rule passed for any value; read the stored price
293 // for that variation instead, which is what the row is really
294 // being compared against.
295 if ($itemPrice === null || $itemPrice === '') {
296 $variantId = $this->get("variants.$index.id");
297
298 $itemPrice = $variantId
299 ? ProductVariation::query()->where('id', $variantId)->value('item_price')
300 : 0;
301 }
302
303 if ((float) $value < (float) $itemPrice) {
304 return sprintf(__("Compare price must be greater than or equal to item price.", 'fluent-cart'));
305 }
306
307 return null;
308 },
309 ],
310 'variants.*.manage_cost' => 'nullable|sanitizeText|maxLength:10',
311 // Variant tax classes are stored as slugs, not numeric IDs like the
312 // older product-detail tax field.
313 'variants.*.other_info.tax_class' => ['nullable', function ($attribute, $value) {
314 return $this->validateTaxClassSlug($attribute, $value);
315 }],
316 'variants.*.other_info.tax_exempt' => 'nullable|sanitizeText|in:yes,no',
317 // 'variants.*.shipping_class' => ['nullable', 'numeric', function ($attribute, $value) {
318 // return $this->validateShippingClassId($attribute, $value);
319 // }],
320 // 'variants.*.item_cost' => 'required_if:variants.*.manage_cost,true',
321 'variants.*.serial_index' => 'nullable|numeric',
322 // 'variants.*.downloadable' => 'nullable|sanitizeText|maxLength:10',
323 // Outside the variation_type gate below: the other_info rules there only
324 // register for 'simple', but a simple_variations save posts variants too.
325 'variants.*.other_info.times' => [
326 function ($attribute, $value) {
327 $index = explode('.', $attribute)[1];
328
329 return Helper::installmentTimesError($this->get("variants.$index.other_info"));
330 },
331 ],
332 ];
333
334 if ($variationType === 'simple') {
335 $variantsOtherInfoRules = [
336
337 'variants.*.other_info' => 'required|array',
338 'variants.*.other_info.description' => 'nullable|sanitizeTextArea|maxLength:255',
339 'variants.*.other_info.payment_type' => 'required|sanitizeText|in:onetime,subscription',
340 'variants.*.other_info.times' => [
341 function ($attribute, $value) {
342 $index = explode('.', $attribute)[1];
343 if ($this->get("variants.$index.other_info.payment_type") !== 'subscription') {
344 return null;
345 }
346 if (!empty($value) && !is_numeric($value)) {
347 return __('Times must be a number.', 'fluent-cart');
348 }
349 return Helper::installmentTimesError($this->get("variants.$index.other_info"));
350 },
351 ],
352 'variants.*.other_info.trial_days' => [
353 function ($attribute, $value) {
354 $index = explode('.', $attribute)[1];
355 if ($this->get("variants.$index.other_info.payment_type") !== 'subscription') {
356 return null;
357 }
358 if (!empty($value) && !is_numeric($value)) {
359 return __('Trial days must be a number.', 'fluent-cart');
360 }
361 if (!empty($value) && $value > 365) {
362 return __('Trial period cannot exceed 365 days.', 'fluent-cart');
363 }
364 return null;
365 },
366 ],
367 'variants.*.other_info.repeat_interval' => [
368 RequiredWhenRule::make(
369 'variants.*.other_info.payment_type',
370 'subscription',
371 esc_html__('Interval is required.', 'fluent-cart')
372 ),
373 'sanitizeText',
374 'maxLength:100',
375 ],
376 'variants.*.other_info.billing_summary' => 'nullable|sanitizeTextArea|maxLength:255',
377 'variants.*.other_info.manage_setup_fee' => [
378 RequiredWhenRule::make(
379 'variants.*.other_info.payment_type',
380 'subscription',
381 esc_html__('Setup Fee option is required.', 'fluent-cart')
382 ),
383 'sanitizeText',
384 'maxLength:100',
385 ],
386 'variants.*.other_info.signup_fee' => [
387 RequiredWhenRule::make(
388 'variants.*.other_info.manage_setup_fee',
389 'yes',
390 esc_html__('Setup Fee Amount is required.', 'fluent-cart')
391 ),
392 ],
393 'variants.*.other_info.signup_fee_name' => [
394 RequiredWhenRule::make(
395 'variants.*.other_info.manage_setup_fee',
396 'yes',
397 esc_html__('Setup Fee Name is required.', 'fluent-cart')
398 ),
399 'sanitizeText',
400 'maxLength:100',
401 ],
402 ];
403 $rules = array_merge($rules, $variantsOtherInfoRules);
404
405 }
406
407
408 // $stockValidations = [
409 // 'variants.*.manage_stock' => 'nullable|numeric',
410 // 'variants.*.stock_status' => 'required_if:detail.manage_stock,1|sanitizeText|maxLength:50',
411 // 'variants.*.total_stock' => 'required|numeric',
412 // 'variants.*.available' => 'required|numeric',
413 // 'variants.*.committed' => 'required|numeric',
414 // 'variants.*.on_hold' => 'required|numeric',
415 // ];
416 //
417 // if($this->get('detail.manage_stock') == 1) {
418 // $rules = array_merge($rules, $stockValidations);
419 // }
420 return $rules;
421 }
422
423 public function afterValidation($validator): array
424 {
425 $data = $this->get();
426 foreach (Arr::get($data, 'variants', []) as $index => &$variant) {
427 if (empty($variant['item_price'])) {
428 $variant['item_price'] = 0;
429 }
430 }
431 return $data;
432 }
433
434
435 /**
436 * @return array
437 */
438 public function messages(): array
439 {
440 $messages = [
441 'post_title.required' => esc_html__('Title is required.', 'fluent-cart'),
442 'post_title.max' => esc_html__('Title may not be greater than 200 characters.', 'fluent-cart'),
443 'detail.fulfillment_type.required' => esc_html__('Fulfilment Type is required.', 'fluent-cart'),
444 'detail.variation_type.required' => esc_html__('Variation Type is required.', 'fluent-cart'),
445 'variants.required_if' => esc_html__('Pricing is required when status is publish or scheduled.', 'fluent-cart'),
446 'variants.*.variation_title.required' => esc_html__('Title is required.', 'fluent-cart'),
447 'variants.*.variation_title.max' => esc_html__('Title may not be greater than 200 characters.', 'fluent-cart'),
448 'variants.*.item_price.required' => esc_html__('Price is required.', 'fluent-cart'),
449 'variants.*.item_price.numeric' => esc_html__('Price must be a number.', 'fluent-cart'),
450 'variants.*.item_price.min' => esc_html__('Price must be a positive number greater than 0.', 'fluent-cart'),
451
452 ];
453
454 $variationType = Arr::get($this->all(), 'detail.variation_type', 'simple');
455 if ($variationType === 'simple') {
456 $otherInfoMessages = [
457 'variants.*.stock_status.required_if' => esc_html__('Stock status is required.', 'fluent-cart'),
458 'variants.*.item_cost.required_if' => esc_html__('Item cost is required.', 'fluent-cart'),
459 'variants.*.other_info.description.max' => esc_html__('Description may not be greater than 255 characters.', 'fluent-cart'),
460 'variants.*.other_info.payment_type.required' => esc_html__('Payment Type is required.', 'fluent-cart'),
461 'variants.*.other_info.times.required_if' => esc_html__('Times is required.', 'fluent-cart'),
462 ];
463
464 $messages = array_merge($messages, $otherInfoMessages);
465 }
466
467 return $messages;
468 }
469
470
471 /**
472 * @return array
473 */
474 public function sanitize(): array
475 {
476 return $this->getSanitizersForExistingFields();
477 }
478
479 /**
480 * Only return sanitizers for fields that actually exist in the request
481 *
482 * @return array
483 */
484 private function getSanitizersForExistingFields(): array
485 {
486 $data = $this->all();
487 $sanitizers = [];
488
489 // Basic fields
490 $basicFieldMap = [
491 'ID' => 'intval',
492 'post_title' => 'sanitize_text_field',
493 'post_name' => 'sanitize_text_field',
494 'post_status' => 'sanitize_text_field',
495 'comment_status' => 'sanitize_text_field',
496 'post_date' => 'sanitize_text_field',
497 'post_excerpt' => 'wp_strip_all_tags',
498 'post_content' => 'wp_kses_post',
499 'metaValue' => function ($value) { return $value; },
500 ];
501
502 foreach ($basicFieldMap as $field => $sanitizer) {
503 if (array_key_exists($field, $data)) {
504 $sanitizers[$field] = $sanitizer;
505 }
506 }
507
508 // Detail fields
509 if (isset($data['detail']) && is_array($data['detail'])) {
510 $detailFieldMap = [
511 'detail.id' => 'intval',
512 'detail.post_id' => 'intval',
513 'detail.fulfillment_type' => 'sanitize_text_field',
514 'detail.variation_type' => 'sanitize_key',
515 'detail.stock_availability' => 'sanitize_key',
516 'detail.manage_stock' => 'intval',
517 'detail.manage_downloadable' => 'intval',
518 'detail.default_variation_id' => 'intval',
519 'detail.other_info.group_pricing_by' => 'sanitize_text_field',
520 'detail.other_info.sold_individually' => 'sanitize_text_field',
521 'detail.other_info.use_pricing_table' => 'sanitize_text_field',
522 'detail.other_info.shipping_class' => 'intval',
523 'detail.other_info.tax_class' => 'intval',
524 'detail.other_info.tax_exempt' => 'sanitize_text_field',
525 'detail.other_info.active_editor' => 'sanitize_text_field',
526 'detail.other_info.fluent_player_video' => function ($value) {
527 return ProductVideoSettings::sanitize($value);
528 },
529 ];
530
531 foreach ($detailFieldMap as $field => $sanitizer) {
532 if (Arr::has($data, $field)) {
533 $sanitizers[$field] = $sanitizer;
534 }
535 }
536 }
537
538 // Product terms
539 if (isset($data['product_terms']) && is_array($data['product_terms'])) {
540 $sanitizers['product_terms.*.product-categories'] = 'sanitize_text_field';
541 $sanitizers['product_terms.*.product-brands'] = 'sanitize_text_field';
542 }
543
544 // Gallery
545 if (isset($data['gallery']) && is_array($data['gallery'])) {
546 $sanitizers['gallery.*.id'] = 'intval';
547 $sanitizers['gallery.*.url'] = function ($value) {
548 return empty($value) ? '' : sanitize_url($value);
549 };
550 $sanitizers['gallery.*.title'] = 'sanitize_text_field';
551 }
552
553 // Variants
554 if (isset($data['variants']) && is_array($data['variants'])) {
555 foreach ($data['variants'] as $index => $variant) {
556 $variantFieldMap = [
557 "variants.$index.id" => 'intval',
558 "variants.$index.rowId" => 'intval',
559 "variants.$index.post_id" => 'intval',
560 "variants.$index.variation_title" => 'sanitize_text_field',
561 "variants.$index.item_price" => 'floatval',
562 "variants.$index.compare_price" => 'floatval',
563 "variants.$index.item_cost" => 'floatval',
564 "variants.$index.manage_cost" => 'sanitize_text_field',
565 "variants.$index.total_stock" => 'intval',
566 "variants.$index.available" => 'intval',
567 "variants.$index.shipping_class" => function ($value) {
568 return $value ? intval($value) : null;
569 },
570 "variants.$index.committed" => 'intval',
571 "variants.$index.on_hold" => 'intval',
572 "variants.$index.manage_stock" => 'intval',
573 "variants.$index.stock_status" => 'sanitize_key',
574 "variants.$index.serial_index" => 'intval',
575 "variants.$index.downloadable" => 'sanitize_text_field',
576 "variants.$index.fulfillment_type" => 'sanitize_text_field',
577 "variants.$index.sku" => function ($value) {
578 // empty() treats the string "0" as empty too, which would silently
579 // convert a legitimate sku of "0" to NULL — sku is a textual
580 // identifier (VARCHAR), not a numeric flag. Match the explicit
581 // '' / null check ProductResource::update() already uses for the
582 // same reason when it clears a sku.
583 return ($value === '' || $value === null) ? null : sanitize_text_field($value);
584 },
585 ];
586
587 foreach ($variantFieldMap as $field => $sanitizer) {
588 if (Arr::has($data, $field)) {
589 $sanitizers[$field] = $sanitizer;
590 }
591 }
592
593 // Variant media
594 if (isset($variant['media']) && is_array($variant['media'])) {
595 $sanitizers["variants.$index.media.*.id"] = 'intval';
596 $sanitizers["variants.$index.media.*.url"] = function ($value) {
597 return empty($value) ? '' : sanitize_url($value);
598 };
599 $sanitizers["variants.$index.media.*.title"] = 'sanitize_text_field';
600 }
601
602 // Variant other_info
603 if (isset($variant['other_info'])) {
604 $sanitizers["variants.$index.other_info"] = function ($value) {
605 return is_array($value) ? $value : [];
606 };
607
608 if (is_array($variant['other_info'])) {
609 $otherInfoFieldMap = [
610 "variants.$index.other_info.description" => function ($value) { return $value; },
611 "variants.$index.other_info.payment_type" => 'sanitize_text_field',
612 "variants.$index.other_info.times" => 'sanitize_text_field',
613 "variants.$index.other_info.repeat_interval" => 'sanitize_text_field',
614 "variants.$index.other_info.billing_summary" => 'sanitize_text_field',
615 "variants.$index.other_info.manage_setup_fee" => 'sanitize_text_field',
616 "variants.$index.other_info.signup_fee" => 'floatval',
617 "variants.$index.other_info.signup_fee_name" => 'sanitize_text_field',
618 "variants.$index.other_info.installment" => 'sanitize_text_field',
619 "variants.$index.other_info.tax_class" => 'sanitize_text_field',
620 "variants.$index.other_info.tax_exempt" => 'sanitize_text_field',
621 ];
622
623 foreach ($otherInfoFieldMap as $field => $sanitizer) {
624 if (Arr::has($data, $field)) {
625 $sanitizers[$field] = $sanitizer;
626 }
627 }
628 }
629 }
630 }
631 }
632
633 return $sanitizers;
634 }
635 }
636