Booking Form * Dynamic form customization requires the Dynamic Form Field module (Pro). * * @package Yatra * * Expected variables: * - $trip_id * - $trip_slug (optional) * - $total_travelers * - $deposit_required * - $deposit_percentage * - $partial_payment * - $partial_payment_percentage * - $enabled_gateways * * Form Configuration Structure: * - contact_form: Lead traveler contact information (prefix: contact_) * - emergency_contact_form: Emergency contact details (prefix: emergency_) * - traveler_form: Individual traveler details (array format: travelers[1][field_id]) */ if (!defined('ABSPATH')) { exit; } // Check if Dynamic Form Field module is enabled (Pro feature) $is_dynamic_form_enabled = apply_filters('yatra_dynamic_form_field_enabled', false); // Get form configuration - returns default config if module disabled, custom config if enabled $form_config = yatra_get_booking_form_config(); // Country list — pulls from the canonical FormatHelper source so every // dropdown (admin, public booking, Pro modules) shows the same full // ISO-3166-1 list. Operators that want a "popular countries" subset // or custom ordering apply the `yatra_countries_list` filter. $countries = \Yatra\Helpers\FormatHelper::getCountries(); /** * Render a single form field based on configuration * * @param array $field Field configuration * @param string $prefix Field name prefix (e.g., 'contact_', 'emergency_') * @param array $countries Country list for country fields * @param string $custom_name Optional custom field name (for array-style names like travelers[1][field_id]) * @param string $custom_id Optional custom field ID */ if (!function_exists('yatra_render_form_field')) { function yatra_render_form_field($field, $prefix = '', $countries = [], $custom_name = null, $custom_id = null) { if (empty($field['enabled'])) { return; } // Display-only text block: render admin-authored content (safe HTML) at this // position between fields. It is NOT an input — no label, name, value or // required handling — so it submits nothing and is never validated. if (($field['type'] ?? '') === 'text_block') { $tb_width = 'yatra-field-full'; if (($field['width'] ?? '') === 'half') { $tb_width = 'yatra-field-half'; } elseif (($field['width'] ?? '') === 'third') { $tb_width = 'yatra-field-third'; } ?>
*' : ''; $prefill_value = ''; if ($custom_name === null && $prefix === 'contact_') { global $booking; if (isset($booking) && is_object($booking)) { $prop = 'contact_' . ($field['id'] ?? ''); if (!empty($prop) && isset($booking->{$prop}) && $booking->{$prop} !== null && $booking->{$prop} !== '') { $prefill_value = (string) $booking->{$prop}; } } } $width_class = ''; if (!empty($field['width'])) { switch ($field['width']) { case 'half': $width_class = 'yatra-field-half'; break; case 'third': $width_class = 'yatra-field-third'; break; default: $width_class = 'yatra-field-full'; } } $field_type = $field['type'] ?? 'text'; ?>
. $is_dob = (stripos($field_id, 'birth') !== false || stripos($field_id, 'dob') !== false || stripos($field_name, 'birth') !== false || stripos($field_name, 'dob') !== false); $dob_attr = $is_dob ? ' data-yatra-dob="1" max="' . esc_attr(date('Y-m-d')) . '"' : ''; // Native date inputs ignore the placeholder, but once booking.js // upgrades the field to a flatpickr text input the placeholder is // what keeps it visually consistent with the other text fields // (otherwise it renders blank). Use the field's own placeholder // when set, else a format hint matching flatpickr's Y-m-d output. $date_placeholder = !empty($field['placeholder']) ? yatra_translate_form_string($field['placeholder']) : 'YYYY-MM-DD'; ?> value="" >
>
value="" >

$section_fields) : ?>

amount_paid)) : ?> total_amount)) : ?> */ $yatra_normalize_booking_arrayish = static function ($value): array { if ($value === null || $value === false || $value === '') { return []; } if (is_array($value)) { return $value; } if ($value instanceof \stdClass) { return (array) $value; } if (is_string($value)) { $trim = trim($value); if ($trim === '') { return []; } $decoded = json_decode($value, true); if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) { return $decoded; } if (function_exists('maybe_unserialize')) { $un = maybe_unserialize($value); if (is_array($un)) { return $un; } } } return []; }; $price_types = $yatra_normalize_booking_arrayish($price_types); $traveler_counts = $yatra_normalize_booking_arrayish($traveler_counts); // Build traveler-to-category mapping for traveler-based pricing $traveler_category_map = []; if ($pricing_type === 'traveler_based' && !empty($price_types) && !empty($traveler_counts)) { $traveler_index = 1; foreach ($price_types as $index => $pt) { $pt = (object) $pt; $category_id = $pt->category_id ?? $index; $category_label = $pt->category_label ?? __('Traveler', 'yatra'); $count = isset($traveler_counts[$category_id]) ? (int) $traveler_counts[$category_id] : 0; for ($c = 1; $c <= $count; $c++) { $traveler_category_map[$traveler_index] = [ 'category_id' => $category_id, 'category_label' => $category_label, 'category_index' => $c, ]; $traveler_index++; } } } $price_candidates = [ isset($trip->price) ? (float) $trip->price : 0, isset($booking->session_price) ? (float) $booking->session_price : 0, isset($trip->sale_price) ? (float) $trip->sale_price : 0, isset($trip->original_price) ? (float) $trip->original_price : 0, ]; $effective_trip_price = 0; foreach ($price_candidates as $candidate) { if ($candidate > 0) { $effective_trip_price = $candidate; break; } } $traveler_price_rows = []; $calculated_total = 0; if ($pricing_type === 'traveler_based' && !empty($price_types)) { foreach ($price_types as $index => $pt) { $pt = (object) $pt; $category_id = $pt->category_id ?? $index; $category_label = $pt->category_label ?? __('Traveler', 'yatra'); $category_price = isset($pt->effective_price) ? (float) $pt->effective_price : ($pt->sale_price ?? $pt->discounted_price ?? $pt->original_price ?? 0); $count = isset($traveler_counts[$category_id]) ? (int) $traveler_counts[$category_id] : ($index === 0 ? 1 : 0); // Single source of truth for the line amount (per-person × count, flat // per-group, or per-block group pricing). Defaults to the prior flat/ // multiply behaviour when group_overflow is absent. $pt_pricing_mode = $pt->pricing_mode ?? 'per_person'; $subtotal = \Yatra\Services\TripPricingService::categoryLineSubtotal($pt, $count, $category_price); $age_info = ''; if (isset($pt->age_min) || isset($pt->age_max)) { if (isset($pt->age_min) && isset($pt->age_max)) { /* translators: 1: minimum age, 2: maximum age. */ $age_info = sprintf(__('(Age %1$d-%2$d)', 'yatra'), $pt->age_min, $pt->age_max); } elseif (isset($pt->age_min)) { /* translators: %d: minimum age. */ $age_info = sprintf(__('(Age %d+)', 'yatra'), $pt->age_min); } else { /* translators: %d: maximum age. */ $age_info = sprintf(__('(Up to age %d)', 'yatra'), $pt->age_max); } } $traveler_price_rows[] = [ 'category_id' => $category_id, 'category_label' => $category_label, 'category_price' => $category_price, 'count' => $count, 'subtotal' => $subtotal, 'pricing_mode' => $pt_pricing_mode, 'age_info' => $age_info, ]; $calculated_total += $subtotal; if ($effective_trip_price <= 0 && $category_price > 0) { $effective_trip_price = $category_price; } } } if ($effective_trip_price <= 0) { $effective_trip_price = 0; } $initial_total_amount = $calculated_total > 0 ? $calculated_total : ($effective_trip_price * max(1, (int) $total_travelers)); if ($initial_total_amount <= 0 && $effective_trip_price > 0) { $initial_total_amount = $effective_trip_price * max(1, (int) $total_travelers); } $initial_due_amount = $initial_total_amount; ?>

$sf) { $applicable = array_values(array_filter($sf, function ($f) use ($i) { return ($f['applies_to'] ?? 'all') !== 'lead' || $i === 1; })); if (!empty($applicable)) { $traveler_grouped_fields[$sk] = $applicable; } } ?>
data-category-id="" data-category-label="">

1) : ?>
$section_fields) : ?>

isset($trip_id) ? (int) $trip_id : 0, 'deposit_required' => $deposit_required, 'deposit_percentage' => $deposit_percentage, 'partial_payment' => $partial_payment, 'partial_payment_percentage' => $partial_payment_percentage, ]); $has_flexible_options = $flexible_payments_enabled && !empty($payment_method_options); ?>

$option) : ?>
getForCheckout() as $gateway) { if (!empty($gateway['id'])) { $display_gateways[$gateway['id']] = $gateway; } } } ?>

$gateway) : $icon = !empty($gateway['icon']) ? $gateway['icon'] : plugins_url('assets/images/payment-placeholder.png', dirname(__DIR__)); ?>

' . esc_html($current_user->display_name) . '' ); ?>
0 ? get_permalink($termsPageId) : ''; $privacyUrl = $privacyPageId > 0 ? get_permalink($privacyPageId) : ''; if ($privacyUrl === '' && function_exists('get_privacy_policy_url')) { $privacyUrl = (string) get_privacy_policy_url(); } ?>