PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.8
Yatra – Travel Booking & Tour Operator Software v3.0.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 / TripValidator.php

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

530 lines 20.7 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 * Trip Validator
11 *
12 * Comprehensive validation for trip data
13 */
14 class TripValidator
15 {
16 /**
17 * Validate trip creation data
18 */
19 public static function validateCreate(array $data): void
20 {
21 $errors = [];
22
23 // Required fields
24 if (empty($data['title'])) {
25 $errors['title'][] = __('Title is required', 'yatra');
26 }
27
28 if (empty($data['status'])) {
29 $errors['status'][] = __('Status is required', 'yatra');
30 } elseif (!in_array($data['status'], ['draft', 'publish', 'private', 'trash'])) {
31 $errors['status'][] = __('Invalid status value', 'yatra');
32 }
33
34 // Validate pricing
35 if (isset($data['original_price'])) {
36 if (!is_numeric($data['original_price']) || (float)$data['original_price'] < 0) {
37 $errors['original_price'][] = __('Original price must be a valid positive number', 'yatra');
38 }
39 }
40
41 if (isset($data['sale_price'])) {
42 if (!is_numeric($data['sale_price']) || (float)$data['sale_price'] < 0) {
43 $errors['sale_price'][] = __('Sale price must be a valid positive number', 'yatra');
44 }
45 }
46
47 // Validate duration
48 if (isset($data['duration_days'])) {
49 if (!is_numeric($data['duration_days']) || (int)$data['duration_days'] < 0) {
50 $errors['duration_days'][] = __('Duration days must be a valid positive number', 'yatra');
51 }
52 }
53
54 if (isset($data['duration_nights'])) {
55 if (!is_numeric($data['duration_nights']) || (int)$data['duration_nights'] < 0) {
56 $errors['duration_nights'][] = __('Duration nights must be a valid positive number', 'yatra');
57 }
58 }
59
60 // Validate difficulty level
61 if (isset($data['difficulty_level'])) {
62 if (!is_numeric($data['difficulty_level']) || (int)$data['difficulty_level'] <= 0) {
63 $errors['difficulty_level'][] = __('Difficulty level must be a valid positive integer', 'yatra');
64 }
65 }
66
67 // Validate group size
68 if (isset($data['min_group_size'])) {
69 if (!is_numeric($data['min_group_size']) || (int)$data['min_group_size'] < 1) {
70 $errors['min_group_size'][] = __('Minimum group size must be at least 1', 'yatra');
71 }
72 }
73
74 if (isset($data['max_group_size'])) {
75 if (!is_numeric($data['max_group_size']) || (int)$data['max_group_size'] < 1) {
76 $errors['max_group_size'][] = __('Maximum group size must be at least 1', 'yatra');
77 }
78 }
79
80 // Validate min/max group size relationship
81 if (isset($data['min_group_size']) && isset($data['max_group_size'])) {
82 if ((int)$data['min_group_size'] > (int)$data['max_group_size']) {
83 $errors['max_group_size'][] = __('Maximum group size must be greater than or equal to minimum group size', 'yatra');
84 }
85 }
86
87 // Validate slug format
88 if (isset($data['slug']) && !empty($data['slug'])) {
89 if (!preg_match('/^[\pL\pN-]+$/u', $data['slug'])) {
90 $errors['slug'][] = __('Slug can only contain letters, numbers, and hyphens', 'yatra');
91 }
92 }
93
94 if (!empty($errors)) {
95 throw new ValidationException('Trip validation failed', $errors);
96 }
97 }
98
99 /**
100 * Validate trip update data
101 */
102 public static function validateUpdate(array $data, int $tripId): void
103 {
104 $errors = [];
105
106 // ID validation
107 if ($tripId <= 0) {
108 $errors['id'][] = __('Invalid trip ID', 'yatra');
109 }
110
111 // Optional field validation (same rules as create, but fields are optional)
112 if (isset($data['status']) && !in_array($data['status'], ['draft', 'publish', 'private', 'trash'])) {
113 $errors['status'][] = __('Invalid status value', 'yatra');
114 }
115
116 if (isset($data['original_price']) && (!is_numeric($data['original_price']) || (float)$data['original_price'] < 0)) {
117 $errors['original_price'][] = __('Original price must be a valid positive number', 'yatra');
118 }
119
120 if (isset($data['sale_price']) && (!is_numeric($data['sale_price']) || (float)$data['sale_price'] < 0)) {
121 $errors['sale_price'][] = __('Sale price must be a valid positive number', 'yatra');
122 }
123
124 // Deposit fields (FlexiblePayments Pro feature — schema lives in free, used by Pro)
125 if (isset($data['deposit_amount']) && $data['deposit_amount'] !== '' && (!is_numeric($data['deposit_amount']) || (float)$data['deposit_amount'] < 0)) {
126 $errors['deposit_amount'][] = __('Deposit amount must be a valid positive number', 'yatra');
127 }
128
129 if (isset($data['deposit_percentage']) && $data['deposit_percentage'] !== '') {
130 if (!is_numeric($data['deposit_percentage'])) {
131 $errors['deposit_percentage'][] = __('Deposit percentage must be a valid number', 'yatra');
132 } else {
133 $pct = (float)$data['deposit_percentage'];
134 if ($pct < 0 || $pct > 100) {
135 $errors['deposit_percentage'][] = __('Deposit percentage must be between 0 and 100', 'yatra');
136 }
137 }
138 }
139
140 if (isset($data['duration_days']) && (!is_numeric($data['duration_days']) || (int)$data['duration_days'] < 0)) {
141 $errors['duration_days'][] = __('Duration days must be a valid positive number', 'yatra');
142 }
143
144 if (isset($data['duration_nights']) && (!is_numeric($data['duration_nights']) || (int)$data['duration_nights'] < 0)) {
145 $errors['duration_nights'][] = __('Duration nights must be a valid positive number', 'yatra');
146 }
147
148 if (isset($data['difficulty_level']) && (!is_numeric($data['difficulty_level']) || (int)$data['difficulty_level'] <= 0)) {
149 $errors['difficulty_level'][] = __('Difficulty level must be a valid positive integer', 'yatra');
150 }
151
152 if (isset($data['min_group_size']) && (!is_numeric($data['min_group_size']) || (int)$data['min_group_size'] < 1)) {
153 $errors['min_group_size'][] = __('Minimum group size must be at least 1', 'yatra');
154 }
155
156 if (isset($data['max_group_size']) && (!is_numeric($data['max_group_size']) || (int)$data['max_group_size'] < 1)) {
157 $errors['max_group_size'][] = __('Maximum group size must be at least 1', 'yatra');
158 }
159
160 if (isset($data['min_group_size']) && isset($data['max_group_size'])) {
161 if ((int)$data['min_group_size'] > (int)$data['max_group_size']) {
162 $errors['max_group_size'][] = __('Maximum group size must be greater than or equal to minimum group size', 'yatra');
163 }
164 }
165
166 if (isset($data['slug']) && !empty($data['slug']) && !preg_match('/^[\pL\pN-]+$/u', $data['slug'])) {
167 $errors['slug'][] = __('Slug can only contain letters, numbers, and hyphens', 'yatra');
168 }
169
170 if (!empty($errors)) {
171 throw new ValidationException('Trip validation failed', $errors);
172 }
173 }
174
175 /**
176 * Sanitize trip data
177 */
178 public static function sanitize(array $data): array
179 {
180 $sanitized = [];
181
182 // Text fields
183 if (isset($data['title'])) {
184 $sanitized['title'] = sanitize_text_field($data['title']);
185 }
186
187 if (isset($data['slug'])) {
188 $sanitized['slug'] = \Yatra\Helpers\SlugHelper::generate($data['slug']);
189 }
190
191 if (isset($data['description'])) {
192 $sanitized['description'] = wp_kses_post($data['description']);
193 }
194
195 if (isset($data['short_description'])) {
196 $sanitized['short_description'] = wp_kses_post($data['short_description']);
197 }
198
199 if (isset($data['trip_details'])) {
200 $sanitized['trip_details'] = wp_kses_post($data['trip_details']);
201 }
202
203 if (isset($data['what_makes_special'])) {
204 $sanitized['what_makes_special'] = wp_kses_post($data['what_makes_special']);
205 }
206
207 if (isset($data['trip_story'])) {
208 $sanitized['trip_story'] = wp_kses_post($data['trip_story']);
209 }
210
211 if (isset($data['video_url'])) {
212 $sanitized['video_url'] = esc_url_raw($data['video_url']);
213 }
214
215 if (isset($data['virtual_tour_url'])) {
216 $sanitized['virtual_tour_url'] = esc_url_raw($data['virtual_tour_url']);
217 }
218
219 if (isset($data['starting_location'])) {
220 $sanitized['starting_location'] = sanitize_text_field($data['starting_location']);
221 }
222
223 if (isset($data['ending_location'])) {
224 $sanitized['ending_location'] = sanitize_text_field($data['ending_location']);
225 }
226
227 if (isset($data['starting_latitude'])) {
228 $sanitized['starting_latitude'] = is_numeric($data['starting_latitude']) ? (float) $data['starting_latitude'] : null;
229 }
230
231 if (isset($data['starting_longitude'])) {
232 $sanitized['starting_longitude'] = is_numeric($data['starting_longitude']) ? (float) $data['starting_longitude'] : null;
233 }
234
235 if (isset($data['ending_latitude'])) {
236 $sanitized['ending_latitude'] = is_numeric($data['ending_latitude']) ? (float) $data['ending_latitude'] : null;
237 }
238
239 if (isset($data['ending_longitude'])) {
240 $sanitized['ending_longitude'] = is_numeric($data['ending_longitude']) ? (float) $data['ending_longitude'] : null;
241 }
242
243 if (isset($data['seasonal_availability'])) {
244 $sanitized['seasonal_availability'] = sanitize_text_field($data['seasonal_availability']);
245 }
246
247 if (isset($data['best_season'])) {
248 $sanitized['best_season'] = sanitize_text_field($data['best_season']);
249 }
250
251 if (isset($data['peak_season'])) {
252 $sanitized['peak_season'] = sanitize_text_field($data['peak_season']);
253 }
254
255 if (isset($data['off_season'])) {
256 $sanitized['off_season'] = sanitize_text_field($data['off_season']);
257 }
258
259 if (isset($data['accommodation_type'])) {
260 $sanitized['accommodation_type'] = sanitize_text_field($data['accommodation_type']);
261 }
262
263 if (isset($data['meal_plan'])) {
264 $sanitized['meal_plan'] = sanitize_text_field($data['meal_plan']);
265 }
266
267 if (isset($data['accommodation_details'])) {
268 $sanitized['accommodation_details'] = wp_kses_post($data['accommodation_details']);
269 }
270
271 if (isset($data['pickup_location'])) {
272 $sanitized['pickup_location'] = sanitize_text_field($data['pickup_location']);
273 }
274
275 if (isset($data['dropoff_location'])) {
276 $sanitized['dropoff_location'] = sanitize_text_field($data['dropoff_location']);
277 }
278
279 if (isset($data['transportation_details'])) {
280 $sanitized['transportation_details'] = wp_kses_post($data['transportation_details']);
281 }
282
283 if (isset($data['transportation_included'])) {
284 $sanitized['transportation_included'] = (bool)$data['transportation_included'];
285 }
286
287 if (isset($data['payment_terms'])) {
288 $sanitized['payment_terms'] = wp_kses_post($data['payment_terms']);
289 }
290
291 // Deposit fields — DB columns are decimal(10,2) / decimal(5,2).
292 // Cast empty string to null so the column resets cleanly when the admin
293 // clears the field (cast to float would coerce '' → 0.0 which is a
294 // semantically-different "fixed $0 deposit").
295 if (array_key_exists('deposit_amount', $data)) {
296 $sanitized['deposit_amount'] = ($data['deposit_amount'] === '' || $data['deposit_amount'] === null)
297 ? null
298 : (float)$data['deposit_amount'];
299 }
300
301 if (array_key_exists('deposit_percentage', $data)) {
302 $sanitized['deposit_percentage'] = ($data['deposit_percentage'] === '' || $data['deposit_percentage'] === null)
303 ? null
304 : (float)$data['deposit_percentage'];
305 }
306
307 if (isset($data['cancellation_policy'])) {
308 $sanitized['cancellation_policy'] = wp_kses_post($data['cancellation_policy']);
309 }
310
311 if (isset($data['physical_requirements'])) {
312 $sanitized['physical_requirements'] = wp_kses_post($data['physical_requirements']);
313 }
314
315 if (isset($data['visa_requirements'])) {
316 $sanitized['visa_requirements'] = wp_kses_post($data['visa_requirements']);
317 }
318
319 if (isset($data['vaccination_requirements'])) {
320 $sanitized['vaccination_requirements'] = wp_kses_post($data['vaccination_requirements']);
321 }
322
323 if (isset($data['meta_title'])) {
324 $sanitized['meta_title'] = sanitize_text_field($data['meta_title']);
325 }
326
327 if (isset($data['meta_description'])) {
328 $sanitized['meta_description'] = sanitize_textarea_field($data['meta_description']);
329 }
330
331 if (isset($data['meta_keywords'])) {
332 $sanitized['meta_keywords'] = sanitize_text_field($data['meta_keywords']);
333 }
334
335 // Numeric fields.
336 // Use array_key_exists (not isset) so an explicitly-sent null/empty
337 // price is written as SQL NULL — letting admins CLEAR a price. With
338 // isset(), a null was dropped and the old value lingered, so prices
339 // could never be emptied from the UI.
340 if (array_key_exists('original_price', $data)) {
341 $sanitized['original_price'] = ($data['original_price'] === null || $data['original_price'] === '')
342 ? null
343 : (float)$data['original_price'];
344 }
345
346 if (array_key_exists('discounted_price', $data)) {
347 $sanitized['discounted_price'] = ($data['discounted_price'] === null || $data['discounted_price'] === '')
348 ? null
349 : (float)$data['discounted_price'];
350 }
351
352 if (isset($data['duration_days'])) {
353 $sanitized['duration_days'] = (int)$data['duration_days'];
354 }
355
356 if (isset($data['duration_nights'])) {
357 $sanitized['duration_nights'] = (int)$data['duration_nights'];
358 }
359
360 if (isset($data['duration_hours'])) {
361 $sanitized['duration_hours'] = (int)$data['duration_hours'];
362 }
363
364 if (isset($data['available_from'])) {
365 $sanitized['available_from'] = sanitize_text_field($data['available_from']);
366 }
367
368 if (isset($data['available_to'])) {
369 $sanitized['available_to'] = sanitize_text_field($data['available_to']);
370 }
371
372 if (isset($data['age_min'])) {
373 $sanitized['age_min'] = (int)$data['age_min'];
374 }
375
376 if (isset($data['age_max'])) {
377 $sanitized['age_max'] = (int)$data['age_max'];
378 }
379
380 if (isset($data['version'])) {
381 $sanitized['version'] = (int)$data['version'];
382 }
383
384 if (isset($data['updated_by'])) {
385 $sanitized['updated_by'] = (int)$data['updated_by'];
386 }
387
388 if (isset($data['updated_at'])) {
389 $sanitized['updated_at'] = sanitize_text_field($data['updated_at']);
390 }
391
392 if (isset($data['difficulty_level'])) {
393 $sanitized['difficulty_level'] = (int)$data['difficulty_level'];
394 }
395
396 if (isset($data['min_group_size'])) {
397 $sanitized['min_group_size'] = (int)$data['min_group_size'];
398 }
399
400 if (isset($data['max_group_size'])) {
401 $sanitized['max_group_size'] = (int)$data['max_group_size'];
402 }
403
404 // Booking fields
405 if (isset($data['min_travelers'])) {
406 $sanitized['min_travelers'] = (int)$data['min_travelers'];
407 }
408
409 if (isset($data['max_travelers'])) {
410 $sanitized['max_travelers'] = (int)$data['max_travelers'];
411 }
412
413 if (isset($data['booking_window_days'])) {
414 $sanitized['booking_window_days'] = (int)$data['booking_window_days'];
415 }
416
417 if (isset($data['booking_deadline_hours'])) {
418 $sanitized['booking_deadline_hours'] = (int)$data['booking_deadline_hours'];
419 }
420
421 // Time slot fields
422 if (isset($data['has_default_time_slots'])) {
423 $sanitized['has_default_time_slots'] = (bool)$data['has_default_time_slots'];
424 }
425
426 if (isset($data['default_time_slots'])) {
427 // Keep as string (already JSON encoded from controller)
428 $sanitized['default_time_slots'] = $data['default_time_slots'];
429 }
430
431 if (isset($data['departure_time'])) {
432 $sanitized['departure_time'] = sanitize_text_field($data['departure_time']);
433 }
434
435 // Enum fields
436 if (isset($data['status'])) {
437 $sanitized['status'] = in_array($data['status'], ['draft', 'publish', 'private', 'trash'])
438 ? $data['status']
439 : 'draft';
440 }
441
442 if (isset($data['trip_type'])) {
443 $sanitized['trip_type'] = in_array($data['trip_type'], ['single_day', 'multi_day', 'flexible'])
444 ? $data['trip_type']
445 : 'multi_day';
446 }
447
448 if (isset($data['pricing_type'])) {
449 $sanitized['pricing_type'] = in_array($data['pricing_type'], ['regular', 'traveler_based', 'dynamic', 'custom'])
450 ? $data['pricing_type']
451 : 'regular';
452 }
453
454 if (isset($data['featured_priority'])) {
455 $sanitized['featured_priority'] = in_array($data['featured_priority'], ['none', 'featured', 'new', 'limited', 'bestseller'])
456 ? $data['featured_priority']
457 : 'none';
458 }
459
460 if (isset($data['currency'])) {
461 $sanitized['currency'] = sanitize_text_field($data['currency']);
462 }
463
464 // Always include featured_image if it exists in the data, even if null
465 if (array_key_exists('featured_image', $data)) {
466 // Allow null or empty string to remove featured image
467 $sanitized['featured_image'] = empty($data['featured_image']) ? null : sanitize_text_field($data['featured_image']);
468 }
469
470 // JSON fields
471 if (isset($data['testimonials'])) {
472 $sanitized['testimonials'] = is_array($data['testimonials']) ? maybe_serialize($data['testimonials']) : $data['testimonials'];
473 }
474
475 if (isset($data['countries'])) {
476 $sanitized['countries'] = is_array($data['countries']) ? maybe_serialize($data['countries']) : $data['countries'];
477 }
478
479 if (isset($data['regions'])) {
480 $sanitized['regions'] = is_array($data['regions']) ? maybe_serialize($data['regions']) : $data['regions'];
481 }
482
483 if (isset($data['landmarks'])) {
484 $sanitized['landmarks'] = is_array($data['landmarks']) ? maybe_serialize($data['landmarks']) : $data['landmarks'];
485 }
486
487 if (isset($data['tags'])) {
488 $sanitized['tags'] = is_array($data['tags']) ? maybe_serialize($data['tags']) : $data['tags'];
489 }
490
491 if (isset($data['included_items'])) {
492 $sanitized['included_items'] = is_array($data['included_items']) ? maybe_serialize($data['included_items']) : $data['included_items'];
493 }
494
495 if (isset($data['excluded_items'])) {
496 $sanitized['excluded_items'] = is_array($data['excluded_items']) ? maybe_serialize($data['excluded_items']) : $data['excluded_items'];
497 }
498
499 if (isset($data['frontend_tabs'])) {
500 $sanitized['frontend_tabs'] = is_array($data['frontend_tabs']) ? maybe_serialize($data['frontend_tabs']) : $data['frontend_tabs'];
501 }
502
503 if (isset($data['testimonial_review_ids'])) {
504 $sanitized['testimonial_review_ids'] = is_array($data['testimonial_review_ids']) ? json_encode($data['testimonial_review_ids']) : $data['testimonial_review_ids'];
505 }
506
507 if (isset($data['features'])) {
508 $sanitized['features'] = is_array($data['features']) ? json_encode($data['features']) : $data['features'];
509 }
510
511 if (isset($data['includes'])) {
512 $sanitized['includes'] = is_array($data['includes']) ? json_encode($data['includes']) : $data['includes'];
513 }
514
515 if (isset($data['excludes'])) {
516 $sanitized['excludes'] = is_array($data['excludes']) ? json_encode($data['excludes']) : $data['excludes'];
517 }
518
519 // custom_fields is a JSON column — pass the array through so TripService can
520 // merge it with the existing stored data before serialising.
521 if (isset($data['custom_fields'])) {
522 $sanitized['custom_fields'] = is_array($data['custom_fields'])
523 ? $data['custom_fields']
524 : (array) $data['custom_fields'];
525 }
526
527 return apply_filters('yatra_trip_sanitize_data', $sanitized, $data);
528 }
529 }
530