PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.16
Yatra – Travel Booking & Tour Operator Software v3.0.16
3.0.16 3.0.15 3.0.14 3.0.14.1 3.0.14.2 3.0.12 3.0.13 3.0.11 3.0.10 3.0.9 3.0.8 3.0.7 3.0.6 3.0.5 3.0.5.1 3.0.4 3.0.3 3.0.2.9 3.0.2.7 3.0.2.8 3.0.2.6 trunk 1.0.0 2.0.0 2.0.1 All 84 releases
← All changes | app/Validators/BookingValidator.php +90 -15 3.0.7 → 3.0.16 View file →
@@ -36,8 +36,70 @@
36 36 'waitlist',
37 37 ];
38 38
39 39 /**
40 + * Accepted payment statuses — mirrors the `payment_status` ENUM on the
41 + * bookings table.
42 + */
43 + private const VALID_PAYMENT_STATUSES = [
44 + 'pending',
45 + 'partial',
46 + 'paid',
47 + 'refunded',
48 + 'failed',
49 + ];
50 +
51 + /**
52 + * Normalize a locale-formatted numeric string to a PHP-parseable form.
53 + *
54 + * Russian / European locales format money as "1 200,50" (space thousands +
55 + * comma decimal), and some browsers/inputs submit that raw string. PHP's
56 + * is_numeric() rejects it and (float) silently truncates it ("200,50" → 200),
57 + * which surfaced to users as a generic "Booking validation failed" with no
58 + * indication of the real cause. Normalize before validating/casting so we
59 + * accept "1 200,50", "1.200,50" (EU), "1,200.50" (US) and "200,50" alike.
60 + *
61 + * @param mixed $value
62 + * @return mixed Normalized string for numeric input, original value otherwise.
63 + */
64 + private static function normalizeNumeric($value)
65 + {
66 + if (is_int($value) || is_float($value)) {
67 + return $value;
68 + }
69 + if (!is_string($value)) {
70 + return $value;
71 + }
72 +
73 + $v = trim($value);
74 + if ($v === '') {
75 + return $v;
76 + }
77 +
78 + // Strip currency symbols and all whitespace used as thousands separators
79 + // (regular space, NBSP U+00A0, narrow NBSP U+202F, thin space U+2009).
80 + $v = preg_replace('/[\s\x{00A0}\x{202F}\x{2009}]/u', '', $v);
81 +
82 + $lastComma = strrpos($v, ',');
83 + $lastDot = strrpos($v, '.');
84 +
85 + if ($lastComma !== false && $lastDot !== false) {
86 + // Both present → the right-most one is the decimal separator.
87 + if ($lastComma > $lastDot) {
88 + $v = str_replace('.', '', $v); // dots are thousands
89 + $v = str_replace(',', '.', $v); // comma is decimal
90 + } else {
91 + $v = str_replace(',', '', $v); // commas are thousands
92 + }
93 + } elseif ($lastComma !== false) {
94 + // Only a comma → treat it as the decimal separator.
95 + $v = str_replace(',', '.', $v);
96 + }
97 +
98 + return $v;
99 + }
100 +
101 + /**
40 102 * Validate booking creation data
41 103 */
42 104 public static function validateCreate(array $data): void
43 105 {
@@ -73,17 +135,19 @@
73 135 $errors['status'][] = __('Invalid booking status', 'yatra');
74 136 }
75 137 }
76 138
77 - // Validate pricing
139 + // Validate pricing (locale-tolerant: accept "1 200,50" / "1.200,50" etc.)
78 140 if (isset($data['total_amount'])) {
79 - if (!is_numeric($data['total_amount']) || (float)$data['total_amount'] < 0) {
141 + $totalAmount = self::normalizeNumeric($data['total_amount']);
142 + if (!is_numeric($totalAmount) || (float)$totalAmount < 0) {
80 143 $errors['total_amount'][] = __('Total amount must be a valid positive number', 'yatra');
81 144 }
82 145 }
83 146
84 147 if (isset($data['paid_amount'])) {
85 - if (!is_numeric($data['paid_amount']) || (float)$data['paid_amount'] < 0) {
148 + $paidAmount = self::normalizeNumeric($data['paid_amount']);
149 + if (!is_numeric($paidAmount) || (float)$paidAmount < 0) {
86 150 $errors['paid_amount'][] = __('Paid amount must be a valid positive number', 'yatra');
87 151 }
88 152 }
89 153
@@ -173,13 +237,23 @@
173 237 $errors['status'][] = __('Invalid booking status', 'yatra');
174 238 }
175 239 }
176 240
177 - if (isset($data['total_amount']) && (!is_numeric($data['total_amount']) || (float)$data['total_amount'] < 0)) {
241 + // Reject rather than fall through to sanitize(), which coerces an
242 + // unknown value to 'pending'. On an update that silently reset a
243 + // fully-paid booking to unpaid while amount_paid kept the money that had
244 + // actually been received — and still reported success.
245 + if (isset($data['payment_status'])) {
246 + if (!in_array($data['payment_status'], self::VALID_PAYMENT_STATUSES, true)) {
247 + $errors['payment_status'][] = __('Invalid payment status', 'yatra');
248 + }
249 + }
250 +
251 + if (isset($data['total_amount']) && (!is_numeric(self::normalizeNumeric($data['total_amount'])) || (float)self::normalizeNumeric($data['total_amount']) < 0)) {
178 252 $errors['total_amount'][] = __('Total amount must be a valid positive number', 'yatra');
179 253 }
180 254
181 - if (isset($data['paid_amount']) && (!is_numeric($data['paid_amount']) || (float)$data['paid_amount'] < 0)) {
255 + if (isset($data['paid_amount']) && (!is_numeric(self::normalizeNumeric($data['paid_amount'])) || (float)self::normalizeNumeric($data['paid_amount']) < 0)) {
182 256 $errors['paid_amount'][] = __('Paid amount must be a valid positive number', 'yatra');
183 257 }
184 258
185 259 if (isset($data['total_travelers']) && (!is_numeric($data['total_travelers']) || (int)$data['total_travelers'] < 1)) {
@@ -224,15 +298,16 @@
224 298 if (isset($data['travelers_count'])) {
225 299 $sanitized['travelers_count'] = (int)$data['travelers_count'];
226 300 }
227 301
228 - // Float fields
302 + // Float fields (normalize locale formatting so "200,50" stores as 200.50,
303 + // not silently truncated to 200 by a bare (float) cast).
229 304 if (isset($data['total_amount'])) {
230 - $sanitized['total_amount'] = (float)$data['total_amount'];
305 + $sanitized['total_amount'] = (float)self::normalizeNumeric($data['total_amount']);
231 306 }
232 307
233 308 if (isset($data['paid_amount'])) {
234 - $sanitized['paid_amount'] = (float)$data['paid_amount'];
309 + $sanitized['paid_amount'] = (float)self::normalizeNumeric($data['paid_amount']);
235 310 }
236 311
237 312 // Date fields
238 313 if (isset($data['departure_date'])) {
@@ -301,15 +376,15 @@
301 376 }
302 377
303 378 // Tax fields
304 379 if (isset($data['subtotal'])) {
305 - $sanitized['subtotal'] = (float)$data['subtotal'];
380 + $sanitized['subtotal'] = (float)self::normalizeNumeric($data['subtotal']);
306 381 }
307 382 if (isset($data['tax_amount'])) {
308 - $sanitized['tax_amount'] = (float)$data['tax_amount'];
383 + $sanitized['tax_amount'] = (float)self::normalizeNumeric($data['tax_amount']);
309 384 }
310 385 if (isset($data['tax_rate'])) {
311 - $sanitized['tax_rate'] = (float)$data['tax_rate'];
386 + $sanitized['tax_rate'] = (float)self::normalizeNumeric($data['tax_rate']);
312 387 }
313 388 if (isset($data['tax_inclusive'])) {
314 389 $sanitized['tax_inclusive'] = (bool)$data['tax_inclusive'];
315 390 }
@@ -321,15 +396,15 @@
321 396 if (isset($data['currency'])) {
322 397 $sanitized['currency'] = sanitize_text_field($data['currency']);
323 398 }
324 399 if (isset($data['amount_due'])) {
325 - $sanitized['amount_due'] = (float)$data['amount_due'];
400 + $sanitized['amount_due'] = (float)self::normalizeNumeric($data['amount_due']);
326 401 }
327 402 if (isset($data['amount_paid'])) {
328 - $sanitized['amount_paid'] = (float)$data['amount_paid'];
403 + $sanitized['amount_paid'] = (float)self::normalizeNumeric($data['amount_paid']);
329 404 }
330 405 if (isset($data['discount_amount'])) {
331 - $sanitized['discount_amount'] = (float)$data['discount_amount'];
406 + $sanitized['discount_amount'] = (float)self::normalizeNumeric($data['discount_amount']);
332 407 }
333 408 if (isset($data['discount_code'])) {
334 409 $sanitized['discount_code'] = sanitize_text_field($data['discount_code']);
335 410 }
@@ -409,9 +484,9 @@
409 484 if (isset($data['itinerary_costs'])) {
410 485 $sanitized['itinerary_costs'] = $data['itinerary_costs']; // Already JSON encoded
411 486 }
412 487 if (isset($data['itinerary_costs_total'])) {
413 - $sanitized['itinerary_costs_total'] = (float)$data['itinerary_costs_total'];
488 + $sanitized['itinerary_costs_total'] = (float)self::normalizeNumeric($data['itinerary_costs_total']);
414 489 }
415 490 if (isset($data['departure_time'])) {
416 491 $t = trim((string) $data['departure_time']);
417 492 $sanitized['departure_time'] = $t !== '' ? sanitize_text_field($t) : '';