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/TripValidator.php +72 -10 3.0.3 → 3.0.16 View file →
@@ -85,10 +85,10 @@
85 85 }
86 86
87 87 // Validate slug format
88 88 if (isset($data['slug']) && !empty($data['slug'])) {
89 - if (!preg_match('/^[a-z0-9-]+$/', $data['slug'])) {
90 - $errors['slug'][] = __('Slug can only contain lowercase letters, numbers, and hyphens', 'yatra');
89 + if (!preg_match('/^[\pL\pN-]+$/u', $data['slug'])) {
90 + $errors['slug'][] = __('Slug can only contain letters, numbers, and hyphens', 'yatra');
91 91 }
92 92 }
93 93
94 94 if (!empty($errors)) {
@@ -120,8 +120,24 @@
120 120 if (isset($data['sale_price']) && (!is_numeric($data['sale_price']) || (float)$data['sale_price'] < 0)) {
121 121 $errors['sale_price'][] = __('Sale price must be a valid positive number', 'yatra');
122 122 }
123 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 +
124 140 if (isset($data['duration_days']) && (!is_numeric($data['duration_days']) || (int)$data['duration_days'] < 0)) {
125 141 $errors['duration_days'][] = __('Duration days must be a valid positive number', 'yatra');
126 142 }
127 143
@@ -146,10 +162,10 @@
146 162 $errors['max_group_size'][] = __('Maximum group size must be greater than or equal to minimum group size', 'yatra');
147 163 }
148 164 }
149 165
150 - if (isset($data['slug']) && !empty($data['slug']) && !preg_match('/^[a-z0-9-]+$/', $data['slug'])) {
151 - $errors['slug'][] = __('Slug can only contain lowercase letters, numbers, and hyphens', 'yatra');
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');
152 168 }
153 169
154 170 if (!empty($errors)) {
155 171 throw new ValidationException('Trip validation failed', $errors);
@@ -168,9 +184,9 @@
168 184 $sanitized['title'] = sanitize_text_field($data['title']);
169 185 }
170 186
171 187 if (isset($data['slug'])) {
172 - $sanitized['slug'] = sanitize_title($data['slug']);
188 + $sanitized['slug'] = \Yatra\Helpers\SlugHelper::generate($data['slug']);
173 189 }
174 190
175 191 if (isset($data['description'])) {
176 192 $sanitized['description'] = wp_kses_post($data['description']);
@@ -271,8 +287,24 @@
271 287 if (isset($data['payment_terms'])) {
272 288 $sanitized['payment_terms'] = wp_kses_post($data['payment_terms']);
273 289 }
274 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 +
275 307 if (isset($data['cancellation_policy'])) {
276 308 $sanitized['cancellation_policy'] = wp_kses_post($data['cancellation_policy']);
277 309 }
278 310
@@ -299,15 +331,23 @@
299 331 if (isset($data['meta_keywords'])) {
300 332 $sanitized['meta_keywords'] = sanitize_text_field($data['meta_keywords']);
301 333 }
302 334
303 - // Numeric fields
304 - if (isset($data['original_price'])) {
305 - $sanitized['original_price'] = (float)$data['original_price'];
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'];
306 344 }
307 345
308 - if (isset($data['discounted_price'])) {
309 - $sanitized['discounted_price'] = (float)$data['discounted_price'];
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'];
310 350 }
311 351
312 352 if (isset($data['duration_days'])) {
313 353 $sanitized['duration_days'] = (int)$data['duration_days'];
@@ -481,8 +521,30 @@
481 521 if (isset($data['custom_fields'])) {
482 522 $sanitized['custom_fields'] = is_array($data['custom_fields'])
483 523 ? $data['custom_fields']
484 524 : (array) $data['custom_fields'];
525 + }
526 +
527 + // Hour-based duration only applies to single-day tours. Clamp to a sane
528 + // day-length range, and never let a multi-day / flexible trip carry
529 + // hours — otherwise the front end would show "8 hours" for a multi-day
530 + // trip and the Google Calendar module would build a short timed event
531 + // instead of the correct multi-day span.
532 + //
533 + // `trip_type` settles it when the payload carries it (the trip form
534 + // always sends both). A partial update that omits `trip_type` is caught
535 + // by the duration_days fallback below, so hours can never be stored
536 + // against a multi-day span.
537 + if (array_key_exists('duration_hours', $sanitized)) {
538 + $sanitized['duration_hours'] = max(0, min(24, (int) $sanitized['duration_hours']));
539 + $isMultiDayType = array_key_exists('trip_type', $sanitized)
540 + && $sanitized['trip_type'] !== 'single_day';
541 + $isMultiDaySpan = array_key_exists('duration_days', $sanitized)
542 + && (int) $sanitized['duration_days'] > 1;
543 +
544 + if ($isMultiDayType || $isMultiDaySpan) {
545 + $sanitized['duration_hours'] = 0;
546 + }
485 547 }
486 548
487 549 return apply_filters('yatra_trip_sanitize_data', $sanitized, $data);
488 550 }