PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.4
Yatra – Travel Booking & Tour Operator Software v3.0.4
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.4, at app/Validators/TripValidator.php

522 lines 20.2 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 if (isset($data['original_price'])) {
337 $sanitized['original_price'] = (float)$data['original_price'];
338 }
339
340 if (isset($data['discounted_price'])) {
341 $sanitized['discounted_price'] = (float)$data['discounted_price'];
342 }
343
344 if (isset($data['duration_days'])) {
345 $sanitized['duration_days'] = (int)$data['duration_days'];
346 }
347
348 if (isset($data['duration_nights'])) {
349 $sanitized['duration_nights'] = (int)$data['duration_nights'];
350 }
351
352 if (isset($data['duration_hours'])) {
353 $sanitized['duration_hours'] = (int)$data['duration_hours'];
354 }
355
356 if (isset($data['available_from'])) {
357 $sanitized['available_from'] = sanitize_text_field($data['available_from']);
358 }
359
360 if (isset($data['available_to'])) {
361 $sanitized['available_to'] = sanitize_text_field($data['available_to']);
362 }
363
364 if (isset($data['age_min'])) {
365 $sanitized['age_min'] = (int)$data['age_min'];
366 }
367
368 if (isset($data['age_max'])) {
369 $sanitized['age_max'] = (int)$data['age_max'];
370 }
371
372 if (isset($data['version'])) {
373 $sanitized['version'] = (int)$data['version'];
374 }
375
376 if (isset($data['updated_by'])) {
377 $sanitized['updated_by'] = (int)$data['updated_by'];
378 }
379
380 if (isset($data['updated_at'])) {
381 $sanitized['updated_at'] = sanitize_text_field($data['updated_at']);
382 }
383
384 if (isset($data['difficulty_level'])) {
385 $sanitized['difficulty_level'] = (int)$data['difficulty_level'];
386 }
387
388 if (isset($data['min_group_size'])) {
389 $sanitized['min_group_size'] = (int)$data['min_group_size'];
390 }
391
392 if (isset($data['max_group_size'])) {
393 $sanitized['max_group_size'] = (int)$data['max_group_size'];
394 }
395
396 // Booking fields
397 if (isset($data['min_travelers'])) {
398 $sanitized['min_travelers'] = (int)$data['min_travelers'];
399 }
400
401 if (isset($data['max_travelers'])) {
402 $sanitized['max_travelers'] = (int)$data['max_travelers'];
403 }
404
405 if (isset($data['booking_window_days'])) {
406 $sanitized['booking_window_days'] = (int)$data['booking_window_days'];
407 }
408
409 if (isset($data['booking_deadline_hours'])) {
410 $sanitized['booking_deadline_hours'] = (int)$data['booking_deadline_hours'];
411 }
412
413 // Time slot fields
414 if (isset($data['has_default_time_slots'])) {
415 $sanitized['has_default_time_slots'] = (bool)$data['has_default_time_slots'];
416 }
417
418 if (isset($data['default_time_slots'])) {
419 // Keep as string (already JSON encoded from controller)
420 $sanitized['default_time_slots'] = $data['default_time_slots'];
421 }
422
423 if (isset($data['departure_time'])) {
424 $sanitized['departure_time'] = sanitize_text_field($data['departure_time']);
425 }
426
427 // Enum fields
428 if (isset($data['status'])) {
429 $sanitized['status'] = in_array($data['status'], ['draft', 'publish', 'private', 'trash'])
430 ? $data['status']
431 : 'draft';
432 }
433
434 if (isset($data['trip_type'])) {
435 $sanitized['trip_type'] = in_array($data['trip_type'], ['single_day', 'multi_day', 'flexible'])
436 ? $data['trip_type']
437 : 'multi_day';
438 }
439
440 if (isset($data['pricing_type'])) {
441 $sanitized['pricing_type'] = in_array($data['pricing_type'], ['regular', 'traveler_based', 'dynamic', 'custom'])
442 ? $data['pricing_type']
443 : 'regular';
444 }
445
446 if (isset($data['featured_priority'])) {
447 $sanitized['featured_priority'] = in_array($data['featured_priority'], ['none', 'featured', 'new', 'limited', 'bestseller'])
448 ? $data['featured_priority']
449 : 'none';
450 }
451
452 if (isset($data['currency'])) {
453 $sanitized['currency'] = sanitize_text_field($data['currency']);
454 }
455
456 // Always include featured_image if it exists in the data, even if null
457 if (array_key_exists('featured_image', $data)) {
458 // Allow null or empty string to remove featured image
459 $sanitized['featured_image'] = empty($data['featured_image']) ? null : sanitize_text_field($data['featured_image']);
460 }
461
462 // JSON fields
463 if (isset($data['testimonials'])) {
464 $sanitized['testimonials'] = is_array($data['testimonials']) ? maybe_serialize($data['testimonials']) : $data['testimonials'];
465 }
466
467 if (isset($data['countries'])) {
468 $sanitized['countries'] = is_array($data['countries']) ? maybe_serialize($data['countries']) : $data['countries'];
469 }
470
471 if (isset($data['regions'])) {
472 $sanitized['regions'] = is_array($data['regions']) ? maybe_serialize($data['regions']) : $data['regions'];
473 }
474
475 if (isset($data['landmarks'])) {
476 $sanitized['landmarks'] = is_array($data['landmarks']) ? maybe_serialize($data['landmarks']) : $data['landmarks'];
477 }
478
479 if (isset($data['tags'])) {
480 $sanitized['tags'] = is_array($data['tags']) ? maybe_serialize($data['tags']) : $data['tags'];
481 }
482
483 if (isset($data['included_items'])) {
484 $sanitized['included_items'] = is_array($data['included_items']) ? maybe_serialize($data['included_items']) : $data['included_items'];
485 }
486
487 if (isset($data['excluded_items'])) {
488 $sanitized['excluded_items'] = is_array($data['excluded_items']) ? maybe_serialize($data['excluded_items']) : $data['excluded_items'];
489 }
490
491 if (isset($data['frontend_tabs'])) {
492 $sanitized['frontend_tabs'] = is_array($data['frontend_tabs']) ? maybe_serialize($data['frontend_tabs']) : $data['frontend_tabs'];
493 }
494
495 if (isset($data['testimonial_review_ids'])) {
496 $sanitized['testimonial_review_ids'] = is_array($data['testimonial_review_ids']) ? json_encode($data['testimonial_review_ids']) : $data['testimonial_review_ids'];
497 }
498
499 if (isset($data['features'])) {
500 $sanitized['features'] = is_array($data['features']) ? json_encode($data['features']) : $data['features'];
501 }
502
503 if (isset($data['includes'])) {
504 $sanitized['includes'] = is_array($data['includes']) ? json_encode($data['includes']) : $data['includes'];
505 }
506
507 if (isset($data['excludes'])) {
508 $sanitized['excludes'] = is_array($data['excludes']) ? json_encode($data['excludes']) : $data['excludes'];
509 }
510
511 // custom_fields is a JSON column — pass the array through so TripService can
512 // merge it with the existing stored data before serialising.
513 if (isset($data['custom_fields'])) {
514 $sanitized['custom_fields'] = is_array($data['custom_fields'])
515 ? $data['custom_fields']
516 : (array) $data['custom_fields'];
517 }
518
519 return apply_filters('yatra_trip_sanitize_data', $sanitized, $data);
520 }
521 }
522