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 / OrderRequest.php

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

269 lines 13.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\Framework\Foundation\RequestGuard;
7 use FluentCart\Framework\Support\Arr;
8
9 class OrderRequest extends RequestGuard
10 {
11
12 /**
13 * @return array
14 */
15 public function rules(): array
16 {
17 return [
18 'status' => 'nullable|sanitizeText|maxLength:50',
19 'invoice_no' => 'nullable|sanitizeText|maxLength:100',
20 'fulfillment_type' => 'nullable|sanitizeText|maxLength:50',
21 'type' => 'nullable|sanitizeText|maxLength:50',
22 'payment_method' => 'nullable|sanitizeText|maxLength:50',
23 'payment_method_title' => 'nullable|sanitizeText|maxLength:50',
24 'payment_status' => 'nullable|sanitizeText|maxLength:50',
25 'currency' => 'nullable|sanitizeText|maxLength:10',
26 'subtotal' => 'numeric',
27 'discount_tax' => 'numeric',
28 'manual_discount_total' => 'numeric',
29 'coupon_discount_total' => 'numeric',
30 'shipping_tax' => 'numeric',
31 // min/max close the silent-corruption window: 1e19 passes `numeric`
32 // but wraps to a negative BIGINT through a float-to-int cast, and a
33 // negative shipping charge has no meaning. The bound matches the
34 // Helper::roundCent() guard (float's exact-integer range).
35 'shipping_total' => 'numeric|min:0|max:9000000000000000',
36 'tax_total' => 'numeric',
37 'total_amount' => 'numeric',
38 'rate' => 'numeric',
39 'note' => 'nullable|sanitizeTextArea|maxLength:5000',
40 'uuid' => 'nullable|sanitizeText|maxLength:100',
41 'ip_address' => 'nullable|sanitizeText|maxLength:100',
42 'completed_at' => 'nullable|sanitizeText|maxLength:100',
43 'refunded_at' => 'nullable|sanitizeText|maxLength:100',
44 'customer_id' => 'required|numeric',
45 'user_tz' => 'nullable|sanitizeText|maxLength:50',
46
47 'order_items' => 'required|array',
48 "order_items.*.id" => 'numeric|min:1',
49 "order_items.*.order_id" => 'numeric|min:1',
50 "order_items.*.post_id" => 'numeric|min:1',
51 "order_items.*.variation_id" => 'numeric|min:1',
52 "order_items.*.object_id" => 'numeric|min:1',
53 "order_items.*.fulfillment_type" => 'nullable|sanitizeText',
54 "order_items.*.payment_type" => 'nullable|sanitizeText|maxLength:100',
55 "order_items.*.quantity" => 'numeric|min:1',
56 "order_items.*.post_title" => 'nullable|sanitizeText|maxLength:255',
57 "order_items.*.title" => 'nullable|sanitizeText|maxLength:255',
58 "order_items.*.price" => 'numeric',
59 "order_items.*.unit_price" => 'numeric',
60 "order_items.*.shipping_charge" => 'nullable|numeric',
61 "order_items.*.item_cost" => 'numeric',
62 "order_items.*.item_total" => 'numeric',
63 "order_items.*.tax_amount" => 'numeric',
64 "order_items.*.discount_total" => 'numeric',
65 "order_items.*.total" => 'numeric',
66 "order_items.*.line_total" => 'numeric',
67 "order_items.*.cart_index" => 'nullable|numeric',
68 "order_items.*.rate" => 'nullable|numeric',
69 "order_items.*.line_meta" => 'nullable|array',
70 "order_items.*.other_info" => 'nullable|array',
71
72 "discount.type" => 'nullable|sanitizeText|maxLength:100',
73 "discount.value" => 'nullable|numeric',
74 "discount.label" => 'nullable|sanitizeText|maxLength:100',
75 "discount.reason" => 'nullable|sanitizeText|maxLength:100',
76 "discount.action" => 'nullable|sanitizeText|maxLength:100',
77
78 'shipping' => 'nullable|array',
79 "shipping.*.type" => 'nullable|sanitizeText|maxLength:100',
80 "shipping.*.rate_name" => 'nullable|sanitizeText|maxLength:100',
81 "shipping.*.custom_price" => 'nullable|numeric',
82
83 'deletedItems' => 'nullable|array',
84 'tax_behavior' => 'nullable|numeric|min:0',
85 'tax_lines' => 'nullable|array',
86 'tax_lines.*.rate_id' => 'nullable|numeric|min:0',
87 'tax_lines.*.tax_amount' => 'nullable|numeric|min:0',
88 'tax_lines.*.label' => 'nullable|sanitizeText',
89 'tax_lines.*.is_compound'=> 'nullable',
90
91 // `applied_coupon` is the admin order screen handing back, untouched, what
92 // POST coupons/apply returned: a map KEYED BY COUPON CODE whose rows are
93 // CouponServiceAdmin discount data (see ensureCouponExistInDiscountData()),
94 // NOT fct_applied_coupons rows. AdminOrderProcessor::insertAppliedCoupons()
95 // reads the code keys plus `id` and `discount` and builds its insert rows
96 // from the Coupon model, so those two are the whole load-bearing contract;
97 // everything else in the map is display metadata.
98 //
99 // The previous rules described fct_applied_coupons columns (coupon_id, code,
100 // discounted_amount, stackable) that no caller has ever sent. They were inert
101 // while the validator skipped absent wildcard children, and became a hard
102 // 422 on every coupon order once it started materializing them.
103 //
104 // The per-row closure is the backstop, not decoration: whether the wildcard
105 // rules below can fire at all depends on the validator materializing absent
106 // children, so on its own `applied_coupon.*.id => required` is silently
107 // unenforced on older framework builds. insertAppliedCoupons() subscripts
108 // ['id'] unguarded, so an entry without one writes a null coupon_id.
109 'applied_coupon' => ['nullable', 'array', function ($attribute, $value) {
110 if (!is_array($value)) {
111 return null; // the `array` rule already reports this
112 }
113
114 foreach ($value as $code => $row) {
115 $couponId = is_array($row) ? Arr::get($row, 'id') : null;
116
117 if (!is_numeric($couponId) || (int) $couponId < 1) {
118 return sprintf(
119 /* translators: %1$s: the coupon code the admin applied to the order. */
120 __('The applied coupon "%1$s" is missing its coupon id.', 'fluent-cart'),
121 sanitize_text_field((string) $code)
122 );
123 }
124 }
125
126 return null;
127 }],
128 "applied_coupon.*.id" => 'required|numeric|min:1',
129 // Bounded for the same reason as shipping_total above: sanitize() routes this
130 // through Helper::roundCent(), which throws outside float's exact-integer
131 // range, and a negative coupon discount has no meaning.
132 "applied_coupon.*.discount" => 'required|numeric|min:0|max:9000000000000000',
133 "applied_coupon.*.title" => 'nullable|sanitizeText|maxLength:192',
134 "applied_coupon.*.type" => 'nullable|sanitizeText|maxLength:100',
135 "applied_coupon.*.amount" => 'nullable|numeric',
136 "applied_coupon.*.actual_amount" => 'nullable|numeric',
137 "applied_coupon.*.unit_amount" => 'nullable|numeric',
138 "applied_coupon.*.actual_quantity" => 'nullable|numeric',
139 'trigger' => 'nullable|string',
140 ];
141 }
142
143
144 /**
145 * @return array
146 */
147 public function messages(): array
148 {
149 return [
150 'customer_id.required' => esc_html__('Customer selection is required', 'fluent-cart'),
151 'order_items.required' => esc_html__('Item selection is required', 'fluent-cart'),
152 ];
153 }
154
155
156 /**
157 * @return array
158 */
159 public function sanitize()
160 {
161 return [
162 'id' => 'intval',
163 'status' => 'sanitize_text_field',
164 'invoice_no' => 'sanitize_text_field',
165 'fulfillment_type' => 'sanitize_text_field',
166 'type' => 'sanitize_text_field',
167 'customer_id' => 'intval',
168 'payment_method' => 'sanitize_text_field',
169 'payment_method_title' => 'sanitize_text_field',
170 'payment_status' => 'sanitize_text_field',
171 'currency' => 'sanitize_text_field',
172 'subtotal' => 'floatval',
173 'discount_tax' => 'floatval',
174 'manual_discount_total' => 'floatval',
175 'coupon_discount_total' => 'floatval',
176 'shipping_tax' => 'floatval',
177 // Cents column: normalize at the boundary so every consumer of this request
178 // receives a whole-cent int. floatval alone let a client-computed 19.99 * 100
179 // arrive as 1998.9999999999998, which any later int cast would truncate.
180 //
181 // Wrapped in a closure, NOT passed as [Helper::class, 'roundCent']: an array
182 // value in this map is a LIST of callbacks, iterated one by one
183 // (vendor/wpfluent/framework/src/WPFluent/Support/Sanitizer.php:456-464), so the
184 // array-callable form would try to call Helper() as a function.
185 'shipping_total' => function ($value) {
186 return Helper::roundCent($value);
187 },
188 'tax_total' => 'floatval',
189 'tax_behavior' => 'intval',
190 'total_amount' => 'floatval',
191 'rate' => 'sanitize_text_field',
192 'note' => 'sanitize_text_field',
193 'uuid' => 'sanitize_text_field',
194 'ip_address' => 'sanitize_text_field',
195 'billing_address_id' => 'intval',
196 'shipping_address_id' => 'intval',
197 'completed_at' => 'sanitize_text_field',
198 'refunded_at' => 'sanitize_text_field',
199 'user_tz' => 'sanitize_text_field',
200
201 "order_items.*.id" => 'intval',
202 "order_items.*.order_id" => 'intval',
203 "order_items.*.post_id" => 'intval',
204 "order_items.*.object_id" => 'intval',
205 "order_items.*.payment_type" => 'sanitize_text_field',
206 "order_items.*.quantity" => 'intval',
207 "order_items.*.post_title" => 'sanitize_text_field',
208 "order_items.*.title" => 'sanitize_text_field',
209 "order_items.*.shipping_charge" => 'intval',
210 "order_items.*.price" => 'floatval',
211 "order_items.*.unit_price" => 'floatval',
212 "order_items.*.item_cost" => 'floatval',
213 "order_items.*.item_total" => 'floatval',
214 "order_items.*.tax_amount" => 'floatval',
215 "order_items.*.discount_total" => 'floatval',
216 "order_items.*.total" => 'floatval',
217 "order_items.*.line_total" => 'floatval',
218 "order_items.*.cart_index" => 'intval',
219 "order_items.*.rate" => 'floatval',
220 "order_items.*.line_meta" => function ($value) {
221 return is_array($value) ? $value : [];
222 },
223 "order_items.*.other_info" => function ($value) {
224 return is_array($value) ? $value : [];
225 },
226
227 "discount.type" => 'sanitize_text_field',
228 "discount.value" => 'floatval',
229 "discount.label" => 'sanitize_text_field',
230 "discount.reason" => 'sanitize_text_field',
231 "discount.action" => 'sanitize_text_field',
232
233 "shipping.*.type" => 'sanitize_text_field',
234 "shipping.*.rate_name" => 'sanitize_text_field',
235 "shipping.*.custom_price" => 'floatval',
236
237 "deletedItems" => function ($value) {
238 return is_array($value) ? $value : [];
239 },
240 "tax_lines" => function ($value) {
241 return is_array($value) ? $value : [];
242 },
243 "tax_lines.*.rate_id" => 'intval',
244 "tax_lines.*.tax_amount" => 'intval',
245 "tax_lines.*.label" => 'sanitize_text_field',
246 "tax_lines.*.is_compound"=> function ($value) {
247 return (bool) $value;
248 },
249
250 // Mirrors rules(): the coupons/apply discount-data shape, keyed by coupon code.
251 "applied_coupon.*.id" => 'intval',
252 // Already cents (CouponServiceAdmin rounds the distributed discount to two
253 // decimals in cents) — normalize the float artifact without scaling. A bare
254 // intval() here truncates, so a 9.99 discount would persist a cent short.
255 "applied_coupon.*.discount" => function ($value) {
256 return Helper::roundCent($value);
257 },
258 "applied_coupon.*.title" => 'sanitize_text_field',
259 "applied_coupon.*.type" => 'sanitize_text_field',
260 "applied_coupon.*.amount" => 'intval',
261 "applied_coupon.*.actual_amount" => 'floatval',
262 "applied_coupon.*.unit_amount" => 'intval',
263 "applied_coupon.*.actual_quantity" => 'intval',
264 'trigger' => 'sanitize_text_field',
265 ];
266
267 }
268 }
269