PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.7.1
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.7.1
1.7.1 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 All 51 releases
← All changes | app/Http/Requests/OrderRequest.php +68 -41 1.6.2 → 1.7.1 View file →
@@ -1,10 +1,12 @@
1 1 <?php
2 2
3 3 namespace FluentCart\App\Http\Requests;
4 4
5 +use FluentCart\App\Helpers\CartHelper;
5 6 use FluentCart\App\Helpers\Helper;
6 7 use FluentCart\Framework\Foundation\RequestGuard;
8 +use FluentCart\Framework\Support\Arr;
7 9
8 10 class OrderRequest extends RequestGuard
9 11 {
10 12
@@ -50,9 +52,9 @@
50 52 "order_items.*.variation_id" => 'numeric|min:1',
51 53 "order_items.*.object_id" => 'numeric|min:1',
52 54 "order_items.*.fulfillment_type" => 'nullable|sanitizeText',
53 55 "order_items.*.payment_type" => 'nullable|sanitizeText|maxLength:100',
54 - "order_items.*.quantity" => 'numeric|min:1',
56 + "order_items.*.quantity" => 'numeric|min:1|max:' . CartHelper::maxQuantity(),
55 57 "order_items.*.post_title" => 'nullable|sanitizeText|maxLength:255',
56 58 "order_items.*.title" => 'nullable|sanitizeText|maxLength:255',
57 59 "order_items.*.price" => 'numeric',
58 60 "order_items.*.unit_price" => 'numeric',
@@ -86,28 +88,57 @@
86 88 'tax_lines.*.tax_amount' => 'nullable|numeric|min:0',
87 89 'tax_lines.*.label' => 'nullable|sanitizeText',
88 90 'tax_lines.*.is_compound'=> 'nullable',
89 91
90 - 'applied_coupon' => 'nullable|array',
91 - "applied_coupon.*.id" => 'nullable|numeric|min:1',
92 - "applied_coupon.*.order_id" => 'nullable|numeric|min:1',
93 - "applied_coupon.*.coupon_id" => 'required|numeric|min:1',
94 - //"applied_coupon.*.title" => 'required|string|max:100',
95 - "applied_coupon.*.code" => 'required|sanitizeText|maxLength:100',
96 - //"applied_coupon.*.status" => 'required|string|max:100',
97 - //"applied_coupon.*.type" => 'required|string|max:100',
98 - "applied_coupon.*.amount" => 'nullable|numeric',
99 - "applied_coupon.*.discounted_amount" => 'required|numeric',
100 - "applied_coupon.*.discount" => 'nullable|numeric',
101 - "applied_coupon.*.stackable" => 'required|numeric',
102 - "applied_coupon.*.priority" => 'nullable|numeric',
103 - "applied_coupon.*.max_uses" => 'nullable|numeric',
104 - "applied_coupon.*.use_count" => 'nullable|numeric',
105 - "applied_coupon.*.max_per_customer" => 'nullable|numeric|min:1',
106 - "applied_coupon.*.min_purchase_amount" => 'nullable|numeric',
107 - "applied_coupon.*.max_discount_amount" => 'nullable|numeric',
108 - "applied_coupon.*.notes" => 'nullable|sanitizeTextArea|maxLength:100',
109 - 'trigger' => 'nullable|string',
92 + // `applied_coupon` is the admin order screen handing back, untouched, what
93 + // POST coupons/apply returned: a map KEYED BY COUPON CODE whose rows are
94 + // CouponServiceAdmin discount data (see ensureCouponExistInDiscountData()),
95 + // NOT fct_applied_coupons rows. AdminOrderProcessor::insertAppliedCoupons()
96 + // reads the code keys plus `id` and `discount` and builds its insert rows
97 + // from the Coupon model, so those two are the whole load-bearing contract;
98 + // everything else in the map is display metadata.
99 + //
100 + // The previous rules described fct_applied_coupons columns (coupon_id, code,
101 + // discounted_amount, stackable) that no caller has ever sent. They were inert
102 + // while the validator skipped absent wildcard children, and became a hard
103 + // 422 on every coupon order once it started materializing them.
104 + //
105 + // The per-row closure is the backstop, not decoration: whether the wildcard
106 + // rules below can fire at all depends on the validator materializing absent
107 + // children, so on its own `applied_coupon.*.id => required` is silently
108 + // unenforced on older framework builds. insertAppliedCoupons() subscripts
109 + // ['id'] unguarded, so an entry without one writes a null coupon_id.
110 + 'applied_coupon' => ['nullable', 'array', function ($attribute, $value) {
111 + if (!is_array($value)) {
112 + return null; // the `array` rule already reports this
113 + }
114 +
115 + foreach ($value as $code => $row) {
116 + $couponId = is_array($row) ? Arr::get($row, 'id') : null;
117 +
118 + if (!is_numeric($couponId) || (int) $couponId < 1) {
119 + return sprintf(
120 + /* translators: %1$s: the coupon code the admin applied to the order. */
121 + __('The applied coupon "%1$s" is missing its coupon id.', 'fluent-cart'),
122 + sanitize_text_field((string) $code)
123 + );
124 + }
125 + }
126 +
127 + return null;
128 + }],
129 + "applied_coupon.*.id" => 'required|numeric|min:1',
130 + // Bounded for the same reason as shipping_total above: sanitize() routes this
131 + // through Helper::roundCent(), which throws outside float's exact-integer
132 + // range, and a negative coupon discount has no meaning.
133 + "applied_coupon.*.discount" => 'required|numeric|min:0|max:9000000000000000',
134 + "applied_coupon.*.title" => 'nullable|sanitizeText|maxLength:192',
135 + "applied_coupon.*.type" => 'nullable|sanitizeText|maxLength:100',
136 + "applied_coupon.*.amount" => 'nullable|numeric',
137 + "applied_coupon.*.actual_amount" => 'nullable|numeric',
138 + "applied_coupon.*.unit_amount" => 'nullable|numeric',
139 + "applied_coupon.*.actual_quantity" => 'nullable|numeric',
140 + 'trigger' => 'nullable|string',
110 141 ];
111 142 }
112 143
113 144
@@ -158,9 +189,9 @@
158 189 'tax_total' => 'floatval',
159 190 'tax_behavior' => 'intval',
160 191 'total_amount' => 'floatval',
161 192 'rate' => 'sanitize_text_field',
162 - 'note' => 'sanitize_text_field',
193 + 'note' => 'sanitize_textarea_field',
163 194 'uuid' => 'sanitize_text_field',
164 195 'ip_address' => 'sanitize_text_field',
165 196 'billing_address_id' => 'intval',
166 197 'shipping_address_id' => 'intval',
@@ -216,27 +247,23 @@
216 247 "tax_lines.*.is_compound"=> function ($value) {
217 248 return (bool) $value;
218 249 },
219 250
220 - "applied_coupon.*.id" => 'intval',
221 - "applied_coupon.*.order_id" => 'intval',
222 - "applied_coupon.*.coupon_id" => 'intval',
223 - "applied_coupon.*.title" => 'sanitize_text_field',
224 - "applied_coupon.*.discount" => 'intval',
225 - "applied_coupon.*.code" => 'sanitize_text_field',
226 - "applied_coupon.*.status" => 'sanitize_text_field',
227 - "applied_coupon.*.type" => 'sanitize_text_field',
228 - "applied_coupon.*.amount" => 'intval',
229 - "applied_coupon.*.discounted_amount" => 'intval',
230 - "applied_coupon.*.stackable" => 'intval',
231 - "applied_coupon.*.priority" => 'intval',
232 - "applied_coupon.*.max_uses" => 'intval',
233 - "applied_coupon.*.use_count" => 'intval',
234 - "applied_coupon.*.max_per_customer" => 'intval',
235 - "applied_coupon.*.min_purchase_amount" => 'intval',
236 - "applied_coupon.*.max_discount_amount" => 'intval',
237 - "applied_coupon.*.notes" => 'sanitize_text_field',
238 - 'trigger' => 'sanitize_text_field',
251 + // Mirrors rules(): the coupons/apply discount-data shape, keyed by coupon code.
252 + "applied_coupon.*.id" => 'intval',
253 + // Already cents (CouponServiceAdmin rounds the distributed discount to two
254 + // decimals in cents) — normalize the float artifact without scaling. A bare
255 + // intval() here truncates, so a 9.99 discount would persist a cent short.
256 + "applied_coupon.*.discount" => function ($value) {
257 + return Helper::roundCent($value);
258 + },
259 + "applied_coupon.*.title" => 'sanitize_text_field',
260 + "applied_coupon.*.type" => 'sanitize_text_field',
261 + "applied_coupon.*.amount" => 'intval',
262 + "applied_coupon.*.actual_amount" => 'floatval',
263 + "applied_coupon.*.unit_amount" => 'intval',
264 + "applied_coupon.*.actual_quantity" => 'intval',
265 + 'trigger' => 'sanitize_text_field',
239 266 ];
240 267
241 268 }
242 269 }