# yatra/3.0.16/app/Validators/TripValidator.php

Yatra – Travel Booking &amp; Tour Operator Software, version 3.0.16. 552 lines.

- Page: https://pluginprobe.com/plugins/yatra/3.0.16/code/app/Validators/TripValidator.php
- Raw: https://pluginprobe.com/plugins/yatra/3.0.16/raw/app/Validators/TripValidator.php
- Modified: 2026-09-15T11:48:00+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/yatra/3.0.16/code/app/Validators/TripValidator.php#L10-L20`.

```php
<?php

declare(strict_types=1);

namespace Yatra\Validators;

use Yatra\Exceptions\ValidationException;

/**
 * Trip Validator
 * 
 * Comprehensive validation for trip data
 */
class TripValidator
{
    /**
     * Validate trip creation data
     */
    public static function validateCreate(array $data): void
    {
        $errors = [];

        // Required fields
        if (empty($data['title'])) {
            $errors['title'][] = __('Title is required', 'yatra');
        }

        if (empty($data['status'])) {
            $errors['status'][] = __('Status is required', 'yatra');
        } elseif (!in_array($data['status'], ['draft', 'publish', 'private', 'trash'])) {
            $errors['status'][] = __('Invalid status value', 'yatra');
        }

        // Validate pricing
        if (isset($data['original_price'])) {
            if (!is_numeric($data['original_price']) || (float)$data['original_price'] < 0) {
                $errors['original_price'][] = __('Original price must be a valid positive number', 'yatra');
            }
        }

        if (isset($data['sale_price'])) {
            if (!is_numeric($data['sale_price']) || (float)$data['sale_price'] < 0) {
                $errors['sale_price'][] = __('Sale price must be a valid positive number', 'yatra');
            }
        }

        // Validate duration
        if (isset($data['duration_days'])) {
            if (!is_numeric($data['duration_days']) || (int)$data['duration_days'] < 0) {
                $errors['duration_days'][] = __('Duration days must be a valid positive number', 'yatra');
            }
        }

        if (isset($data['duration_nights'])) {
            if (!is_numeric($data['duration_nights']) || (int)$data['duration_nights'] < 0) {
                $errors['duration_nights'][] = __('Duration nights must be a valid positive number', 'yatra');
            }
        }

        // Validate difficulty level
        if (isset($data['difficulty_level'])) {
            if (!is_numeric($data['difficulty_level']) || (int)$data['difficulty_level'] <= 0) {
                $errors['difficulty_level'][] = __('Difficulty level must be a valid positive integer', 'yatra');
            }
        }

        // Validate group size
        if (isset($data['min_group_size'])) {
            if (!is_numeric($data['min_group_size']) || (int)$data['min_group_size'] < 1) {
                $errors['min_group_size'][] = __('Minimum group size must be at least 1', 'yatra');
            }
        }

        if (isset($data['max_group_size'])) {
            if (!is_numeric($data['max_group_size']) || (int)$data['max_group_size'] < 1) {
                $errors['max_group_size'][] = __('Maximum group size must be at least 1', 'yatra');
            }
        }

        // Validate min/max group size relationship
        if (isset($data['min_group_size']) && isset($data['max_group_size'])) {
            if ((int)$data['min_group_size'] > (int)$data['max_group_size']) {
                $errors['max_group_size'][] = __('Maximum group size must be greater than or equal to minimum group size', 'yatra');
            }
        }

        // Validate slug format
        if (isset($data['slug']) && !empty($data['slug'])) {
            if (!preg_match('/^[\pL\pN-]+$/u', $data['slug'])) {
                $errors['slug'][] = __('Slug can only contain letters, numbers, and hyphens', 'yatra');
            }
        }

        if (!empty($errors)) {
            throw new ValidationException('Trip validation failed', $errors);
        }
    }

    /**
     * Validate trip update data
     */
    public static function validateUpdate(array $data, int $tripId): void
    {
        $errors = [];

        // ID validation
        if ($tripId <= 0) {
            $errors['id'][] = __('Invalid trip ID', 'yatra');
        }

        // Optional field validation (same rules as create, but fields are optional)
        if (isset($data['status']) && !in_array($data['status'], ['draft', 'publish', 'private', 'trash'])) {
            $errors['status'][] = __('Invalid status value', 'yatra');
        }

        if (isset($data['original_price']) && (!is_numeric($data['original_price']) || (float)$data['original_price'] < 0)) {
            $errors['original_price'][] = __('Original price must be a valid positive number', 'yatra');
        }

        if (isset($data['sale_price']) && (!is_numeric($data['sale_price']) || (float)$data['sale_price'] < 0)) {
            $errors['sale_price'][] = __('Sale price must be a valid positive number', 'yatra');
        }

        // Deposit fields (FlexiblePayments Pro feature — schema lives in free, used by Pro)
        if (isset($data['deposit_amount']) && $data['deposit_amount'] !== '' && (!is_numeric($data['deposit_amount']) || (float)$data['deposit_amount'] < 0)) {
            $errors['deposit_amount'][] = __('Deposit amount must be a valid positive number', 'yatra');
        }

        if (isset($data['deposit_percentage']) && $data['deposit_percentage'] !== '') {
            if (!is_numeric($data['deposit_percentage'])) {
                $errors['deposit_percentage'][] = __('Deposit percentage must be a valid number', 'yatra');
            } else {
                $pct = (float)$data['deposit_percentage'];
                if ($pct < 0 || $pct > 100) {
                    $errors['deposit_percentage'][] = __('Deposit percentage must be between 0 and 100', 'yatra');
                }
            }
        }

        if (isset($data['duration_days']) && (!is_numeric($data['duration_days']) || (int)$data['duration_days'] < 0)) {
            $errors['duration_days'][] = __('Duration days must be a valid positive number', 'yatra');
        }

        if (isset($data['duration_nights']) && (!is_numeric($data['duration_nights']) || (int)$data['duration_nights'] < 0)) {
            $errors['duration_nights'][] = __('Duration nights must be a valid positive number', 'yatra');
        }

        if (isset($data['difficulty_level']) && (!is_numeric($data['difficulty_level']) || (int)$data['difficulty_level'] <= 0)) {
            $errors['difficulty_level'][] = __('Difficulty level must be a valid positive integer', 'yatra');
        }

        if (isset($data['min_group_size']) && (!is_numeric($data['min_group_size']) || (int)$data['min_group_size'] < 1)) {
            $errors['min_group_size'][] = __('Minimum group size must be at least 1', 'yatra');
        }

        if (isset($data['max_group_size']) && (!is_numeric($data['max_group_size']) || (int)$data['max_group_size'] < 1)) {
            $errors['max_group_size'][] = __('Maximum group size must be at least 1', 'yatra');
        }

        if (isset($data['min_group_size']) && isset($data['max_group_size'])) {
            if ((int)$data['min_group_size'] > (int)$data['max_group_size']) {
                $errors['max_group_size'][] = __('Maximum group size must be greater than or equal to minimum group size', 'yatra');
            }
        }

        if (isset($data['slug']) && !empty($data['slug']) && !preg_match('/^[\pL\pN-]+$/u', $data['slug'])) {
            $errors['slug'][] = __('Slug can only contain letters, numbers, and hyphens', 'yatra');
        }

        if (!empty($errors)) {
            throw new ValidationException('Trip validation failed', $errors);
        }
    }

    /**
     * Sanitize trip data
     */
    public static function sanitize(array $data): array
    {
        $sanitized = [];

        // Text fields
        if (isset($data['title'])) {
            $sanitized['title'] = sanitize_text_field($data['title']);
        }

        if (isset($data['slug'])) {
            $sanitized['slug'] = \Yatra\Helpers\SlugHelper::generate($data['slug']);
        }

        if (isset($data['description'])) {
            $sanitized['description'] = wp_kses_post($data['description']);
        }

        if (isset($data['short_description'])) {
            $sanitized['short_description'] = wp_kses_post($data['short_description']);
        }

        if (isset($data['trip_details'])) {
            $sanitized['trip_details'] = wp_kses_post($data['trip_details']);
        }

        if (isset($data['what_makes_special'])) {
            $sanitized['what_makes_special'] = wp_kses_post($data['what_makes_special']);
        }

        if (isset($data['trip_story'])) {
            $sanitized['trip_story'] = wp_kses_post($data['trip_story']);
        }

        if (isset($data['video_url'])) {
            $sanitized['video_url'] = esc_url_raw($data['video_url']);
        }

        if (isset($data['virtual_tour_url'])) {
            $sanitized['virtual_tour_url'] = esc_url_raw($data['virtual_tour_url']);
        }

        if (isset($data['starting_location'])) {
            $sanitized['starting_location'] = sanitize_text_field($data['starting_location']);
        }

        if (isset($data['ending_location'])) {
            $sanitized['ending_location'] = sanitize_text_field($data['ending_location']);
        }

        if (isset($data['starting_latitude'])) {
            $sanitized['starting_latitude'] = is_numeric($data['starting_latitude']) ? (float) $data['starting_latitude'] : null;
        }

        if (isset($data['starting_longitude'])) {
            $sanitized['starting_longitude'] = is_numeric($data['starting_longitude']) ? (float) $data['starting_longitude'] : null;
        }

        if (isset($data['ending_latitude'])) {
            $sanitized['ending_latitude'] = is_numeric($data['ending_latitude']) ? (float) $data['ending_latitude'] : null;
        }

        if (isset($data['ending_longitude'])) {
            $sanitized['ending_longitude'] = is_numeric($data['ending_longitude']) ? (float) $data['ending_longitude'] : null;
        }

        if (isset($data['seasonal_availability'])) {
            $sanitized['seasonal_availability'] = sanitize_text_field($data['seasonal_availability']);
        }

        if (isset($data['best_season'])) {
            $sanitized['best_season'] = sanitize_text_field($data['best_season']);
        }

        if (isset($data['peak_season'])) {
            $sanitized['peak_season'] = sanitize_text_field($data['peak_season']);
        }

        if (isset($data['off_season'])) {
            $sanitized['off_season'] = sanitize_text_field($data['off_season']);
        }

        if (isset($data['accommodation_type'])) {
            $sanitized['accommodation_type'] = sanitize_text_field($data['accommodation_type']);
        }

        if (isset($data['meal_plan'])) {
            $sanitized['meal_plan'] = sanitize_text_field($data['meal_plan']);
        }

        if (isset($data['accommodation_details'])) {
            $sanitized['accommodation_details'] = wp_kses_post($data['accommodation_details']);
        }

        if (isset($data['pickup_location'])) {
            $sanitized['pickup_location'] = sanitize_text_field($data['pickup_location']);
        }

        if (isset($data['dropoff_location'])) {
            $sanitized['dropoff_location'] = sanitize_text_field($data['dropoff_location']);
        }

        if (isset($data['transportation_details'])) {
            $sanitized['transportation_details'] = wp_kses_post($data['transportation_details']);
        }

        if (isset($data['transportation_included'])) {
            $sanitized['transportation_included'] = (bool)$data['transportation_included'];
        }

        if (isset($data['payment_terms'])) {
            $sanitized['payment_terms'] = wp_kses_post($data['payment_terms']);
        }

        // Deposit fields — DB columns are decimal(10,2) / decimal(5,2).
        // Cast empty string to null so the column resets cleanly when the admin
        // clears the field (cast to float would coerce '' → 0.0 which is a
        // semantically-different "fixed $0 deposit").
        if (array_key_exists('deposit_amount', $data)) {
            $sanitized['deposit_amount'] = ($data['deposit_amount'] === '' || $data['deposit_amount'] === null)
                ? null
                : (float)$data['deposit_amount'];
        }

        if (array_key_exists('deposit_percentage', $data)) {
            $sanitized['deposit_percentage'] = ($data['deposit_percentage'] === '' || $data['deposit_percentage'] === null)
                ? null
                : (float)$data['deposit_percentage'];
        }

        if (isset($data['cancellation_policy'])) {
            $sanitized['cancellation_policy'] = wp_kses_post($data['cancellation_policy']);
        }

        if (isset($data['physical_requirements'])) {
            $sanitized['physical_requirements'] = wp_kses_post($data['physical_requirements']);
        }

        if (isset($data['visa_requirements'])) {
            $sanitized['visa_requirements'] = wp_kses_post($data['visa_requirements']);
        }

        if (isset($data['vaccination_requirements'])) {
            $sanitized['vaccination_requirements'] = wp_kses_post($data['vaccination_requirements']);
        }

        if (isset($data['meta_title'])) {
            $sanitized['meta_title'] = sanitize_text_field($data['meta_title']);
        }

        if (isset($data['meta_description'])) {
            $sanitized['meta_description'] = sanitize_textarea_field($data['meta_description']);
        }

        if (isset($data['meta_keywords'])) {
            $sanitized['meta_keywords'] = sanitize_text_field($data['meta_keywords']);
        }

        // Numeric fields.
        // Use array_key_exists (not isset) so an explicitly-sent null/empty
        // price is written as SQL NULL — letting admins CLEAR a price. With
        // isset(), a null was dropped and the old value lingered, so prices
        // could never be emptied from the UI.
        if (array_key_exists('original_price', $data)) {
            $sanitized['original_price'] = ($data['original_price'] === null || $data['original_price'] === '')
                ? null
                : (float)$data['original_price'];
        }

        if (array_key_exists('discounted_price', $data)) {
            $sanitized['discounted_price'] = ($data['discounted_price'] === null || $data['discounted_price'] === '')
                ? null
                : (float)$data['discounted_price'];
        }

        if (isset($data['duration_days'])) {
            $sanitized['duration_days'] = (int)$data['duration_days'];
        }

        if (isset($data['duration_nights'])) {
            $sanitized['duration_nights'] = (int)$data['duration_nights'];
        }

        if (isset($data['duration_hours'])) {
            $sanitized['duration_hours'] = (int)$data['duration_hours'];
        }

        if (isset($data['available_from'])) {
            $sanitized['available_from'] = sanitize_text_field($data['available_from']);
        }

        if (isset($data['available_to'])) {
            $sanitized['available_to'] = sanitize_text_field($data['available_to']);
        }

        if (isset($data['age_min'])) {
            $sanitized['age_min'] = (int)$data['age_min'];
        }

        if (isset($data['age_max'])) {
            $sanitized['age_max'] = (int)$data['age_max'];
        }

        if (isset($data['version'])) {
            $sanitized['version'] = (int)$data['version'];
        }

        if (isset($data['updated_by'])) {
            $sanitized['updated_by'] = (int)$data['updated_by'];
        }

        if (isset($data['updated_at'])) {
            $sanitized['updated_at'] = sanitize_text_field($data['updated_at']);
        }

        if (isset($data['difficulty_level'])) {
            $sanitized['difficulty_level'] = (int)$data['difficulty_level'];
        }

        if (isset($data['min_group_size'])) {
            $sanitized['min_group_size'] = (int)$data['min_group_size'];
        }

        if (isset($data['max_group_size'])) {
            $sanitized['max_group_size'] = (int)$data['max_group_size'];
        }

        // Booking fields
        if (isset($data['min_travelers'])) {
            $sanitized['min_travelers'] = (int)$data['min_travelers'];
        }

        if (isset($data['max_travelers'])) {
            $sanitized['max_travelers'] = (int)$data['max_travelers'];
        }

        if (isset($data['booking_window_days'])) {
            $sanitized['booking_window_days'] = (int)$data['booking_window_days'];
        }

        if (isset($data['booking_deadline_hours'])) {
            $sanitized['booking_deadline_hours'] = (int)$data['booking_deadline_hours'];
        }

        // Time slot fields
        if (isset($data['has_default_time_slots'])) {
            $sanitized['has_default_time_slots'] = (bool)$data['has_default_time_slots'];
        }

        if (isset($data['default_time_slots'])) {
            // Keep as string (already JSON encoded from controller)
            $sanitized['default_time_slots'] = $data['default_time_slots'];
        }

        if (isset($data['departure_time'])) {
            $sanitized['departure_time'] = sanitize_text_field($data['departure_time']);
        }

        // Enum fields
        if (isset($data['status'])) {
            $sanitized['status'] = in_array($data['status'], ['draft', 'publish', 'private', 'trash']) 
                ? $data['status'] 
                : 'draft';
        }

        if (isset($data['trip_type'])) {
            $sanitized['trip_type'] = in_array($data['trip_type'], ['single_day', 'multi_day', 'flexible']) 
                ? $data['trip_type'] 
                : 'multi_day';
        }

        if (isset($data['pricing_type'])) {
            $sanitized['pricing_type'] = in_array($data['pricing_type'], ['regular', 'traveler_based', 'dynamic', 'custom']) 
                ? $data['pricing_type'] 
                : 'regular';
        }

        if (isset($data['featured_priority'])) {
            $sanitized['featured_priority'] = in_array($data['featured_priority'], ['none', 'featured', 'new', 'limited', 'bestseller']) 
                ? $data['featured_priority'] 
                : 'none';
        }

        if (isset($data['currency'])) {
            $sanitized['currency'] = sanitize_text_field($data['currency']);
        }

        // Always include featured_image if it exists in the data, even if null
        if (array_key_exists('featured_image', $data)) {
            // Allow null or empty string to remove featured image
            $sanitized['featured_image'] = empty($data['featured_image']) ? null : sanitize_text_field($data['featured_image']);
        }

        // JSON fields
        if (isset($data['testimonials'])) {
            $sanitized['testimonials'] = is_array($data['testimonials']) ? maybe_serialize($data['testimonials']) : $data['testimonials'];
        }

        if (isset($data['countries'])) {
            $sanitized['countries'] = is_array($data['countries']) ? maybe_serialize($data['countries']) : $data['countries'];
        }

        if (isset($data['regions'])) {
            $sanitized['regions'] = is_array($data['regions']) ? maybe_serialize($data['regions']) : $data['regions'];
        }

        if (isset($data['landmarks'])) {
            $sanitized['landmarks'] = is_array($data['landmarks']) ? maybe_serialize($data['landmarks']) : $data['landmarks'];
        }

        if (isset($data['tags'])) {
            $sanitized['tags'] = is_array($data['tags']) ? maybe_serialize($data['tags']) : $data['tags'];
        }

        if (isset($data['included_items'])) {
            $sanitized['included_items'] = is_array($data['included_items']) ? maybe_serialize($data['included_items']) : $data['included_items'];
        }

        if (isset($data['excluded_items'])) {
            $sanitized['excluded_items'] = is_array($data['excluded_items']) ? maybe_serialize($data['excluded_items']) : $data['excluded_items'];
        }

        if (isset($data['frontend_tabs'])) {
            $sanitized['frontend_tabs'] = is_array($data['frontend_tabs']) ? maybe_serialize($data['frontend_tabs']) : $data['frontend_tabs'];
        }

        if (isset($data['testimonial_review_ids'])) {
            $sanitized['testimonial_review_ids'] = is_array($data['testimonial_review_ids']) ? json_encode($data['testimonial_review_ids']) : $data['testimonial_review_ids'];
        }

        if (isset($data['features'])) {
            $sanitized['features'] = is_array($data['features']) ? json_encode($data['features']) : $data['features'];
        }

        if (isset($data['includes'])) {
            $sanitized['includes'] = is_array($data['includes']) ? json_encode($data['includes']) : $data['includes'];
        }

        if (isset($data['excludes'])) {
            $sanitized['excludes'] = is_array($data['excludes']) ? json_encode($data['excludes']) : $data['excludes'];
        }

        // custom_fields is a JSON column — pass the array through so TripService can
        // merge it with the existing stored data before serialising.
        if (isset($data['custom_fields'])) {
            $sanitized['custom_fields'] = is_array($data['custom_fields'])
                ? $data['custom_fields']
                : (array) $data['custom_fields'];
        }

        // Hour-based duration only applies to single-day tours. Clamp to a sane
        // day-length range, and never let a multi-day / flexible trip carry
        // hours — otherwise the front end would show "8 hours" for a multi-day
        // trip and the Google Calendar module would build a short timed event
        // instead of the correct multi-day span.
        //
        // `trip_type` settles it when the payload carries it (the trip form
        // always sends both). A partial update that omits `trip_type` is caught
        // by the duration_days fallback below, so hours can never be stored
        // against a multi-day span.
        if (array_key_exists('duration_hours', $sanitized)) {
            $sanitized['duration_hours'] = max(0, min(24, (int) $sanitized['duration_hours']));
            $isMultiDayType = array_key_exists('trip_type', $sanitized)
                && $sanitized['trip_type'] !== 'single_day';
            $isMultiDaySpan = array_key_exists('duration_days', $sanitized)
                && (int) $sanitized['duration_days'] > 1;

            if ($isMultiDayType || $isMultiDaySpan) {
                $sanitized['duration_hours'] = 0;
            }
        }

        return apply_filters('yatra_trip_sanitize_data', $sanitized, $data);
    }
}

```
