PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.2.8
Yatra – Travel Booking & Tour Operator Software v3.0.2.8
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 2.0.10 All 83 releases
yatra / app / Validators / BookingValidator.php

BookingValidator.php in Yatra – Travel Booking & Tour Operator Software 3.0.2.8, at app/Validators/BookingValidator.php

386 lines 14.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 declare(strict_types=1);
4
5 namespace Yatra\Validators;
6
7 use Yatra\Exceptions\ValidationException;
8
9 /**
10 * Booking Validator
11 *
12 * Comprehensive validation for booking data
13 */
14 class BookingValidator
15 {
16 /**
17 * Validate booking creation data
18 */
19 public static function validateCreate(array $data): void
20 {
21 $errors = [];
22
23 // Required fields
24 if (empty($data['trip_id'])) {
25 $errors['trip_id'][] = __('Trip ID is required', 'yatra');
26 } elseif (!is_numeric($data['trip_id']) || (int)$data['trip_id'] <= 0) {
27 $errors['trip_id'][] = __('Trip ID must be a valid positive integer', 'yatra');
28 }
29
30 // Customer can be guest, so customer_id is optional (if provided, validate)
31 if (isset($data['customer_id']) && $data['customer_id'] !== '') {
32 if (!is_numeric($data['customer_id']) || (int)$data['customer_id'] <= 0) {
33 $errors['customer_id'][] = __('Customer ID must be a valid positive integer', 'yatra');
34 }
35 }
36
37 // Accept either departure_date or travel_date
38 $departureDate = $data['departure_date'] ?? $data['travel_date'] ?? null;
39 if (empty($departureDate)) {
40 $errors['departure_date'][] = __('Departure date is required', 'yatra');
41 } elseif (!self::isValidDate($departureDate)) {
42 $errors['departure_date'][] = __('Departure date must be a valid date', 'yatra');
43 } elseif (strtotime($departureDate) < strtotime('today')) {
44 $errors['departure_date'][] = __('Departure date cannot be in the past', 'yatra');
45 }
46
47 // Validate status
48 if (isset($data['status'])) {
49 $validStatuses = ['pending', 'confirmed', 'cancelled', 'completed', 'refunded', 'waitlist'];
50 if (!in_array($data['status'], $validStatuses, true)) {
51 $errors['status'][] = __('Invalid booking status', 'yatra');
52 }
53 }
54
55 // Validate pricing
56 if (isset($data['total_amount'])) {
57 if (!is_numeric($data['total_amount']) || (float)$data['total_amount'] < 0) {
58 $errors['total_amount'][] = __('Total amount must be a valid positive number', 'yatra');
59 }
60 }
61
62 if (isset($data['paid_amount'])) {
63 if (!is_numeric($data['paid_amount']) || (float)$data['paid_amount'] < 0) {
64 $errors['paid_amount'][] = __('Paid amount must be a valid positive number', 'yatra');
65 }
66 }
67
68 // Validate traveler count
69 $travelerCount = $data['total_travelers'] ?? $data['travelers_count'] ?? null;
70 if ($travelerCount !== null) {
71 if (!is_numeric($travelerCount) || (int)$travelerCount < 1) {
72 $errors['total_travelers'][] = __('Total travelers must be at least 1', 'yatra');
73 }
74 }
75
76 // Payment: booking "amount type" (full / deposit / partial) vs gateway (processor).
77 // Checkout sends both; Pro Flexible Payments uses payment_method=deposit|partial|full.
78 $bookingAmountMethods = ['full', 'partial', 'deposit'];
79 $gatewayIds = apply_filters('yatra_valid_booking_payment_gateway_ids', [
80 'cash',
81 'bank_transfer',
82 'credit_card',
83 'paypal',
84 'stripe',
85 'razorpay',
86 'pay_later',
87 'paystack',
88 'mollie',
89 'square',
90 'authorize_net',
91 'esewa',
92 'khalti',
93 ]);
94
95 if (isset($data['payment_method']) && $data['payment_method'] !== '') {
96 if (!in_array($data['payment_method'], $bookingAmountMethods, true)) {
97 // Legacy: some clients put the gateway id in payment_method only
98 if (!in_array($data['payment_method'], $gatewayIds, true)) {
99 $errors['payment_method'][] = __('Invalid payment method', 'yatra');
100 }
101 }
102 }
103
104 if (isset($data['payment_gateway']) && $data['payment_gateway'] !== '') {
105 if (!in_array($data['payment_gateway'], $gatewayIds, true)) {
106 $errors['payment_gateway'][] = __('Invalid payment gateway', 'yatra');
107 }
108 }
109
110 // Validate email format
111 if (isset($data['customer_email']) && !empty($data['customer_email'])) {
112 if (!is_email($data['customer_email'])) {
113 $errors['customer_email'][] = __('Invalid email format', 'yatra');
114 }
115 }
116
117 if (!empty($errors)) {
118 throw new ValidationException('Booking validation failed', $errors);
119 }
120 }
121
122 /**
123 * Validate booking update data
124 */
125 public static function validateUpdate(array $data, int $bookingId): void
126 {
127 $errors = [];
128
129 // ID validation
130 if ($bookingId <= 0) {
131 $errors['id'][] = __('Invalid booking ID', 'yatra');
132 }
133
134 // Optional field validation
135 if (isset($data['trip_id']) && (!is_numeric($data['trip_id']) || (int)$data['trip_id'] <= 0)) {
136 $errors['trip_id'][] = __('Trip ID must be a valid positive integer', 'yatra');
137 }
138
139 if (isset($data['customer_id']) && (!is_numeric($data['customer_id']) || (int)$data['customer_id'] <= 0)) {
140 $errors['customer_id'][] = __('Customer ID must be a valid positive integer', 'yatra');
141 }
142
143 if (isset($data['departure_date'])) {
144 if (!self::isValidDate($data['departure_date'])) {
145 $errors['departure_date'][] = __('Departure date must be a valid date', 'yatra');
146 }
147 }
148
149 if (isset($data['status'])) {
150 $validStatuses = ['pending', 'confirmed', 'cancelled', 'completed', 'refunded', 'waitlist'];
151 if (!in_array($data['status'], $validStatuses, true)) {
152 $errors['status'][] = __('Invalid booking status', 'yatra');
153 }
154 }
155
156 if (isset($data['total_amount']) && (!is_numeric($data['total_amount']) || (float)$data['total_amount'] < 0)) {
157 $errors['total_amount'][] = __('Total amount must be a valid positive number', 'yatra');
158 }
159
160 if (isset($data['paid_amount']) && (!is_numeric($data['paid_amount']) || (float)$data['paid_amount'] < 0)) {
161 $errors['paid_amount'][] = __('Paid amount must be a valid positive number', 'yatra');
162 }
163
164 if (isset($data['total_travelers']) && (!is_numeric($data['total_travelers']) || (int)$data['total_travelers'] < 1)) {
165 $errors['total_travelers'][] = __('Total travelers must be at least 1', 'yatra');
166 }
167
168 if (isset($data['payment_method'])) {
169 $validMethods = ['cash', 'bank_transfer', 'credit_card', 'paypal', 'stripe', 'razorpay'];
170 if (!in_array($data['payment_method'], $validMethods)) {
171 $errors['payment_method'][] = __('Invalid payment method', 'yatra');
172 }
173 }
174
175 if (isset($data['customer_email']) && !empty($data['customer_email']) && !is_email($data['customer_email'])) {
176 $errors['customer_email'][] = __('Invalid email format', 'yatra');
177 }
178
179 if (!empty($errors)) {
180 throw new ValidationException('Booking validation failed', $errors);
181 }
182 }
183
184 /**
185 * Sanitize booking data
186 */
187 public static function sanitize(array $data): array
188 {
189 $sanitized = [];
190
191 // Integer fields
192 if (isset($data['trip_id'])) {
193 $sanitized['trip_id'] = (int)$data['trip_id'];
194 }
195
196 if (isset($data['customer_id'])) {
197 $sanitized['customer_id'] = (int)$data['customer_id'];
198 }
199
200 if (isset($data['total_travelers'])) {
201 $sanitized['total_travelers'] = (int)$data['total_travelers'];
202 }
203 if (isset($data['travelers_count'])) {
204 $sanitized['travelers_count'] = (int)$data['travelers_count'];
205 }
206
207 // Float fields
208 if (isset($data['total_amount'])) {
209 $sanitized['total_amount'] = (float)$data['total_amount'];
210 }
211
212 if (isset($data['paid_amount'])) {
213 $sanitized['paid_amount'] = (float)$data['paid_amount'];
214 }
215
216 // Date fields
217 if (isset($data['departure_date'])) {
218 $sanitized['departure_date'] = sanitize_text_field($data['departure_date']);
219 }
220 if (isset($data['travel_date'])) {
221 $sanitized['travel_date'] = sanitize_text_field($data['travel_date']);
222 }
223
224 if (isset($data['booking_date'])) {
225 $sanitized['booking_date'] = sanitize_text_field($data['booking_date']);
226 }
227
228 // Text fields
229 if (isset($data['customer_name'])) {
230 $sanitized['customer_name'] = sanitize_text_field($data['customer_name']);
231 }
232
233 if (isset($data['customer_email'])) {
234 $sanitized['customer_email'] = sanitize_email($data['customer_email']);
235 }
236
237 if (isset($data['customer_phone'])) {
238 $sanitized['customer_phone'] = sanitize_text_field($data['customer_phone']);
239 }
240
241 if (isset($data['notes'])) {
242 $sanitized['notes'] = wp_kses_post($data['notes']);
243 }
244
245 // Enum fields
246 if (isset($data['status'])) {
247 $validStatuses = ['pending', 'confirmed', 'cancelled', 'completed', 'refunded', 'waitlist'];
248 $sanitized['status'] = in_array($data['status'], $validStatuses, true) ? $data['status'] : 'pending';
249 }
250
251 if (isset($data['payment_method'])) {
252 // Align with allowed frontend values (full/partial or gateway handles)
253 $validMethods = [
254 'full',
255 'partial',
256 'cash',
257 'bank_transfer',
258 'credit_card',
259 'paypal',
260 'stripe',
261 'razorpay',
262 'pay_later',
263 'paystack',
264 'mollie',
265 'square',
266 'authorize_net',
267 'esewa',
268 'khalti',
269 ];
270 $sanitized['payment_method'] = in_array($data['payment_method'], $validMethods, true)
271 ? $data['payment_method']
272 : $data['payment_method']; // keep original so validation can report exact value
273 }
274 if (isset($data['payment_gateway'])) {
275 $sanitized['payment_gateway'] = sanitize_text_field($data['payment_gateway']);
276 }
277
278 if (isset($data['payment_status'])) {
279 $validStatuses = ['pending', 'paid', 'partial', 'refunded', 'failed'];
280 $sanitized['payment_status'] = in_array($data['payment_status'], $validStatuses) ? $data['payment_status'] : 'pending';
281 }
282
283 // Tax fields
284 if (isset($data['subtotal'])) {
285 $sanitized['subtotal'] = (float)$data['subtotal'];
286 }
287 if (isset($data['tax_amount'])) {
288 $sanitized['tax_amount'] = (float)$data['tax_amount'];
289 }
290 if (isset($data['tax_rate'])) {
291 $sanitized['tax_rate'] = (float)$data['tax_rate'];
292 }
293 if (isset($data['tax_inclusive'])) {
294 $sanitized['tax_inclusive'] = (bool)$data['tax_inclusive'];
295 }
296 if (isset($data['tax_details'])) {
297 $sanitized['tax_details'] = $data['tax_details']; // Already JSON encoded
298 }
299
300 // Other booking fields
301 if (isset($data['currency'])) {
302 $sanitized['currency'] = sanitize_text_field($data['currency']);
303 }
304 if (isset($data['amount_due'])) {
305 $sanitized['amount_due'] = (float)$data['amount_due'];
306 }
307 if (isset($data['amount_paid'])) {
308 $sanitized['amount_paid'] = (float)$data['amount_paid'];
309 }
310 if (isset($data['discount_amount'])) {
311 $sanitized['discount_amount'] = (float)$data['discount_amount'];
312 }
313 if (isset($data['discount_code'])) {
314 $sanitized['discount_code'] = sanitize_text_field($data['discount_code']);
315 }
316 if (isset($data['reference'])) {
317 $sanitized['reference'] = sanitize_text_field($data['reference']);
318 }
319 if (isset($data['contact_first_name'])) {
320 $sanitized['contact_first_name'] = sanitize_text_field($data['contact_first_name']);
321 }
322 if (isset($data['contact_last_name'])) {
323 $sanitized['contact_last_name'] = sanitize_text_field($data['contact_last_name']);
324 }
325 if (isset($data['contact_email'])) {
326 $sanitized['contact_email'] = sanitize_email($data['contact_email']);
327 }
328 if (isset($data['contact_phone'])) {
329 $sanitized['contact_phone'] = sanitize_text_field($data['contact_phone']);
330 }
331 if (isset($data['contact_country'])) {
332 $sanitized['contact_country'] = sanitize_text_field($data['contact_country']);
333 }
334 if (isset($data['contact_data'])) {
335 $sanitized['contact_data'] = $data['contact_data']; // Already JSON encoded
336 }
337 if (isset($data['emergency_contact'])) {
338 $sanitized['emergency_contact'] = $data['emergency_contact']; // Already JSON encoded
339 }
340 if (isset($data['availability_id'])) {
341 $sanitized['availability_id'] = !empty($data['availability_id']) ? (int)$data['availability_id'] : null;
342 }
343 if (isset($data['user_id'])) {
344 $sanitized['user_id'] = !empty($data['user_id']) ? (int)$data['user_id'] : null;
345 }
346 if (isset($data['special_requests'])) {
347 $sanitized['special_requests'] = sanitize_textarea_field($data['special_requests']);
348 }
349 if (isset($data['newsletter_optin'])) {
350 $sanitized['newsletter_optin'] = (int)(bool)$data['newsletter_optin'];
351 }
352 if (isset($data['ip_address'])) {
353 $sanitized['ip_address'] = sanitize_text_field($data['ip_address']);
354 }
355 if (isset($data['created_at'])) {
356 $sanitized['created_at'] = sanitize_text_field($data['created_at']);
357 }
358 if (isset($data['updated_at'])) {
359 $sanitized['updated_at'] = sanitize_text_field($data['updated_at']);
360 }
361
362 // Itinerary costs fields
363 if (isset($data['itinerary_costs'])) {
364 $sanitized['itinerary_costs'] = $data['itinerary_costs']; // Already JSON encoded
365 }
366 if (isset($data['itinerary_costs_total'])) {
367 $sanitized['itinerary_costs_total'] = (float)$data['itinerary_costs_total'];
368 }
369 if (isset($data['departure_time'])) {
370 $t = trim((string) $data['departure_time']);
371 $sanitized['departure_time'] = $t !== '' ? sanitize_text_field($t) : '';
372 }
373
374 return $sanitized;
375 }
376
377 /**
378 * Check if date is valid
379 */
380 private static function isValidDate(string $date): bool
381 {
382 $d = \DateTime::createFromFormat('Y-m-d', $date);
383 return $d && $d->format('Y-m-d') === $date;
384 }
385 }
386