PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.15
Yatra – Travel Booking & Tour Operator Software v3.0.15
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 / templates / partials / booking-form-fields.php

booking-form-fields.php in Yatra – Travel Booking & Tour Operator Software 3.0.15, at templates/partials/booking-form-fields.php

1,005 lines 47.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Booking Form Fields Partial
4 *
5 * Renders form fields based on configuration from Settings -> Booking Form
6 * Dynamic form customization requires the Dynamic Form Field module (Pro).
7 *
8 * @package Yatra
9 *
10 * Expected variables:
11 * - $trip_id
12 * - $trip_slug (optional)
13 * - $total_travelers
14 * - $deposit_required
15 * - $deposit_percentage
16 * - $partial_payment
17 * - $partial_payment_percentage
18 * - $enabled_gateways
19 *
20 * Form Configuration Structure:
21 * - contact_form: Lead traveler contact information (prefix: contact_)
22 * - emergency_contact_form: Emergency contact details (prefix: emergency_)
23 * - traveler_form: Individual traveler details (array format: travelers[1][field_id])
24 */
25
26 if (!defined('ABSPATH')) {
27 exit;
28 }
29
30 // Check if Dynamic Form Field module is enabled (Pro feature)
31 $is_dynamic_form_enabled = apply_filters('yatra_dynamic_form_field_enabled', false);
32
33 // Get form configuration - returns default config if module disabled, custom config if enabled
34 // Scoped to the trip being booked, so a field or section limited to certain
35 // trips (Pro) is neither rendered nor required here.
36 $form_config = yatra_get_booking_form_config(!empty($trip_id) ? (int) $trip_id : null);
37
38
39 // Country list — pulls from the canonical FormatHelper source so every
40 // dropdown (admin, public booking, Pro modules) shows the same full
41 // ISO-3166-1 list. Operators that want a "popular countries" subset
42 // or custom ordering apply the `yatra_countries_list` filter.
43 $countries = \Yatra\Helpers\FormatHelper::getCountries();
44
45 /**
46 * Render a single form field based on configuration
47 *
48 * @param array $field Field configuration
49 * @param string $prefix Field name prefix (e.g., 'contact_', 'emergency_')
50 * @param array $countries Country list for country fields
51 * @param string $custom_name Optional custom field name (for array-style names like travelers[1][field_id])
52 * @param string $custom_id Optional custom field ID
53 */
54 if (!function_exists('yatra_render_form_field')) {
55 function yatra_render_form_field($field, $prefix = '', $countries = [], $custom_name = null, $custom_id = null) {
56 if (empty($field['enabled'])) {
57 return;
58 }
59
60 // Display-only text block: render admin-authored content (safe HTML) at this
61 // position between fields. It is NOT an input — no label, name, value or
62 // required handling — so it submits nothing and is never validated.
63 if (($field['type'] ?? '') === 'text_block') {
64 $tb_width = 'yatra-field-full';
65 if (($field['width'] ?? '') === 'half') {
66 $tb_width = 'yatra-field-half';
67 } elseif (($field['width'] ?? '') === 'third') {
68 $tb_width = 'yatra-field-third';
69 }
70 ?>
71 <div class="yatra-form-group yatra-form-text-block <?php echo esc_attr($tb_width); ?>" data-applies-to="<?php echo esc_attr($field['applies_to'] ?? 'all'); ?>">
72 <?php echo wpautop(wp_kses_post(yatra_translate_form_string($field['content'] ?? ''))); ?>
73 </div>
74 <?php
75 return;
76 }
77
78 $field_id = $custom_id ? esc_attr($custom_id) : esc_attr($prefix . $field['id']);
79 $field_name = $custom_name ? esc_attr($custom_name) : esc_attr($prefix . $field['id']);
80 $required = !empty($field['required']);
81 $required_attr = $required ? 'required' : '';
82 $required_star = $required ? '<span class="required">*</span>' : '';
83
84 $prefill_value = '';
85 if ($custom_name === null && $prefix === 'contact_') {
86 global $booking;
87 if (isset($booking) && is_object($booking)) {
88 $prop = 'contact_' . ($field['id'] ?? '');
89 if (!empty($prop) && isset($booking->{$prop}) && $booking->{$prop} !== null && $booking->{$prop} !== '') {
90 $prefill_value = (string) $booking->{$prop};
91 }
92 }
93 }
94
95 $width_class = '';
96 if (!empty($field['width'])) {
97 switch ($field['width']) {
98 case 'half':
99 $width_class = 'yatra-field-half';
100 break;
101 case 'third':
102 $width_class = 'yatra-field-third';
103 break;
104 default:
105 $width_class = 'yatra-field-full';
106 }
107 }
108 $field_type = $field['type'] ?? 'text';
109 ?>
110 <div class="yatra-form-group <?php echo esc_attr($width_class); ?> <?php echo $field_type === 'checkbox' ? 'yatra-form-group--checkbox' : ''; ?>" data-applies-to="<?php echo esc_attr($field['applies_to'] ?? 'all'); ?>">
111 <?php if ($field_type !== 'checkbox') : ?>
112 <label for="<?php echo $field_id; ?>">
113 <?php echo esc_html(yatra_translate_form_string($field['label'])); ?> <?php echo $required_star; ?>
114 </label>
115 <?php endif; ?>
116 <?php
117 switch ($field['type']) {
118 case 'checkbox':
119 $is_checked = ($prefill_value === '1' || $prefill_value === 'on' || $prefill_value === 'true');
120 ?>
121 <label for="<?php echo $field_id; ?>" class="yatra-inline-checkbox-label">
122 <input
123 type="checkbox"
124 id="<?php echo $field_id; ?>"
125 name="<?php echo $field_name; ?>"
126 value="1"
127 <?php checked($is_checked, true, false); ?>
128 <?php echo $required_attr; ?>
129 />
130 <span><?php echo esc_html(yatra_translate_form_string($field['label'])); ?><?php echo $required_star; ?></span>
131 </label>
132 <?php
133 break;
134 case 'select':
135 ?>
136 <select id="<?php echo $field_id; ?>" name="<?php echo $field_name; ?>" <?php echo $required_attr; ?>>
137 <option value=""><?php echo esc_html(yatra_translate_form_string($field['placeholder'] ?? '') ?: __('Select...', 'yatra')); ?></option>
138 <?php if (!empty($field['options'])) : ?>
139 <?php foreach ($field['options'] as $option) : ?>
140 <option value="<?php echo esc_attr($option['value']); ?>" <?php echo selected($prefill_value, (string) ($option['value'] ?? ''), false); ?>>
141 <?php echo esc_html(yatra_translate_form_string($option['label'] ?? '')); ?>
142 </option>
143 <?php endforeach; ?>
144 <?php endif; ?>
145 </select>
146 <?php
147 break;
148
149 case 'country':
150 // Flag base is handed to the field (same contract as the phone
151 // widget) so country-select.js can build flag URLs without
152 // knowing the plugin URL. The select still renders and submits
153 // normally — the widget is a progressive enhancement over it.
154 $country_flag_base = \defined('YATRA_PLUGIN_URL') ? YATRA_PLUGIN_URL . 'assets/img/flags/' : '';
155 ?>
156 <select id="<?php echo $field_id; ?>" name="<?php echo $field_name; ?>" <?php echo $required_attr; ?>
157 data-yatra-country-select
158 data-flag-base="<?php echo esc_url($country_flag_base); ?>">
159 <option value=""><?php echo esc_html(yatra_translate_form_string($field['placeholder'] ?? '') ?: __('Select Country', 'yatra')); ?></option>
160 <?php foreach ($countries as $code => $name) : ?>
161 <option value="<?php echo esc_attr($code); ?>" <?php echo selected($prefill_value, (string) $code, false); ?>>
162 <?php echo esc_html($name); ?>
163 </option>
164 <?php endforeach; ?>
165 </select>
166 <?php
167 break;
168
169 case 'textarea':
170 ?>
171 <textarea
172 id="<?php echo $field_id; ?>"
173 name="<?php echo $field_name; ?>"
174 placeholder="<?php echo esc_attr(yatra_translate_form_string($field['placeholder'] ?? '')); ?>"
175 <?php echo $required_attr; ?>
176 rows="3"
177 ><?php echo esc_textarea($prefill_value); ?></textarea>
178 <?php
179 break;
180
181 case 'date':
182 $min_attr = '';
183 // Date-of-birth-style fields are bounded to today and flagged so
184 // booking.js can upgrade them to a picker with fast year jumping
185 // (a native date popup steps one month at a time). The class is
186 // the JS hook; if JS/flatpickr is unavailable the input stays a
187 // working native <input type="date">.
188 $is_dob = (stripos($field_id, 'birth') !== false || stripos($field_id, 'dob') !== false
189 || stripos($field_name, 'birth') !== false || stripos($field_name, 'dob') !== false);
190 $dob_attr = $is_dob ? ' data-yatra-dob="1" max="' . esc_attr(date('Y-m-d')) . '"' : '';
191 // Native date inputs ignore the placeholder, but once booking.js
192 // upgrades the field to a flatpickr text input the placeholder is
193 // what keeps it visually consistent with the other text fields
194 // (otherwise it renders blank). Use the field's own placeholder
195 // when set, else a format hint matching flatpickr's Y-m-d output.
196 $date_placeholder = !empty($field['placeholder'])
197 ? yatra_translate_form_string($field['placeholder'])
198 : 'YYYY-MM-DD';
199 ?>
200 <input
201 type="date"
202 class="yatra-js-date"
203 id="<?php echo $field_id; ?>"
204 name="<?php echo $field_name; ?>"
205 placeholder="<?php echo esc_attr($date_placeholder); ?>"
206 <?php echo $min_attr; ?>
207 <?php echo $dob_attr; ?>
208 <?php echo $required_attr; ?>
209 value="<?php echo esc_attr($prefill_value); ?>"
210 >
211 <?php
212 break;
213
214 default: // text, email, tel, number
215 // Phone (tel) fields get the international country-code widget
216 // UNLESS it has been explicitly disabled in the (Pro) form
217 // builder. A missing key means ON, so every existing form and
218 // customized field keeps the widget without needing a re-save.
219 $is_phone_widget = (($field['type'] ?? '') === 'tel')
220 && (!array_key_exists('show_country_code', $field) || !empty($field['show_country_code']));
221
222 if ($is_phone_widget) {
223 // Split the stored value for display: an international value
224 // ("+9779806015400") is parsed into country + national part;
225 // a legacy bare number is left in the number box under the
226 // default country (never rewritten on display).
227 $pv = (string) $prefill_value;
228 $detected = \Yatra\Helpers\FormatHelper::detectPhoneCountry($pv);
229 if ($detected !== null) {
230 $p_iso = $detected['iso'];
231 $p_dial = $detected['dial'];
232 $p_national = $detected['national'];
233 } else {
234 $p_iso = \Yatra\Helpers\FormatHelper::getDefaultPhoneCountry();
235 $p_dial = \Yatra\Helpers\FormatHelper::getDialingCode($p_iso);
236 $p_national = $pv;
237 }
238
239 // Companion hidden field holding the chosen ISO. Array-style
240 // names (travelers[0][phone]) become travelers[0][phone_country];
241 // the server combines dial code + national number on submit.
242 if (preg_match('/^(.*)\[([^\]]+)\]$/', $field_name, $mm)) {
243 $companion_name = $mm[1] . '[' . $mm[2] . '_country]';
244 } else {
245 $companion_name = $field_name . '_country';
246 }
247
248 $flag_base = \defined('YATRA_PLUGIN_URL') ? YATRA_PLUGIN_URL . 'assets/img/flags/' : '';
249 ?>
250 <div class="yatra-phone-field" data-yatra-phone
251 data-default-iso="<?php echo esc_attr($p_iso); ?>"
252 data-flag-base="<?php echo esc_url($flag_base); ?>">
253 <button type="button" class="yatra-phone-country" aria-haspopup="listbox" aria-expanded="false"
254 aria-label="<?php esc_attr_e('Select country dialing code', 'yatra'); ?>">
255 <img class="yatra-phone-flag" src="<?php echo esc_url($flag_base . strtolower($p_iso) . '.svg'); ?>"
256 alt="" width="22" height="16" loading="lazy">
257 <span class="yatra-phone-dial">+<?php echo esc_html($p_dial); ?></span>
258 <span class="yatra-phone-caret" aria-hidden="true"></span>
259 </button>
260 <input
261 type="tel"
262 class="yatra-phone-number"
263 id="<?php echo $field_id; ?>"
264 name="<?php echo $field_name; ?>"
265 value="<?php echo esc_attr($p_national); ?>"
266 placeholder="<?php echo esc_attr(yatra_translate_form_string($field['placeholder'] ?? '')); ?>"
267 inputmode="tel"
268 autocomplete="tel-national"
269 <?php echo $required_attr; ?>
270 >
271 <input type="hidden" class="yatra-phone-iso"
272 name="<?php echo esc_attr($companion_name); ?>"
273 value="<?php echo esc_attr($p_iso); ?>">
274 </div>
275 <?php
276 } else {
277 ?>
278 <input
279 type="<?php echo esc_attr($field['type']); ?>"
280 id="<?php echo $field_id; ?>"
281 name="<?php echo $field_name; ?>"
282 placeholder="<?php echo esc_attr(yatra_translate_form_string($field['placeholder'] ?? '')); ?>"
283 <?php echo $required_attr; ?>
284 value="<?php echo esc_attr($prefill_value); ?>"
285 >
286 <?php
287 }
288 break;
289 }
290 ?>
291 </div>
292 <?php
293 }
294 } // End function_exists check for yatra_render_form_field
295
296 /**
297 * Render a form section with all its fields
298 */
299 if (!function_exists('yatra_render_form_section')) {
300 function yatra_render_form_section($section_config, $prefix = '', $countries = []) {
301 if (isset($section_config['enabled']) && !$section_config['enabled']) {
302 return;
303 }
304
305 $fields = $section_config['fields'] ?? [];
306
307 // Sort fields by order
308 usort($fields, function($a, $b) {
309 return ($a['order'] ?? 0) - ($b['order'] ?? 0);
310 });
311
312 // Group fields by section (for subsections like dietary)
313 $grouped_fields = [];
314 $current_section = null;
315
316 foreach ($fields as $field) {
317 if (!empty($field['enabled'])) {
318 $section = $field['section'] ?? 'main';
319 if (!isset($grouped_fields[$section])) {
320 $grouped_fields[$section] = [];
321 }
322 $grouped_fields[$section][] = $field;
323 }
324 }
325 ?>
326 <div class="yatra-booking-section">
327 <h2 class="yatra-section-title"><?php echo esc_html(yatra_translate_form_string($section_config['title'] ?? '')); ?></h2>
328 <?php if (!empty($section_config['description'])) : ?>
329 <p class="yatra-section-description"><?php echo esc_html(yatra_translate_form_string($section_config['description'])); ?></p>
330 <?php endif; ?>
331
332 <?php foreach ($grouped_fields as $section_key => $section_fields) : ?>
333 <?php if ($section_key !== 'main') : ?>
334 <div class="yatra-traveler-subsection">
335 <h4 class="yatra-subsection-title">
336 <?php
337 switch ($section_key) {
338 case 'passport':
339 esc_html_e('Additional details', 'yatra');
340 break;
341 case 'dietary_medical':
342 esc_html_e('Dietary & Medical Requirements', 'yatra');
343 break;
344 default:
345 echo esc_html(ucwords(str_replace('_', ' ', $section_key)));
346 }
347 ?>
348 </h4>
349 <?php endif; ?>
350
351 <div class="yatra-form-row">
352 <?php foreach ($section_fields as $field) : ?>
353 <?php yatra_render_form_field($field, $prefix, $countries); ?>
354 <?php endforeach; ?>
355 </div>
356
357 <?php if ($section_key !== 'main') : ?>
358 </div>
359 <?php endif; ?>
360 <?php endforeach; ?>
361 </div>
362 <?php
363 }
364 } // End function_exists check for yatra_render_form_section
365 ?>
366
367 <!-- Hidden Fields -->
368 <input type="hidden" name="trip_id" value="<?php echo esc_attr($trip_id); ?>">
369 <?php if (!empty($trip_slug)) : ?>
370 <input type="hidden" name="trip_slug" value="<?php echo esc_attr($trip_slug); ?>">
371 <?php endif; ?>
372
373 <?php if ($is_remaining_payment) : ?>
374 <input type="hidden" name="is_remaining_payment" value="1">
375 <?php if (!empty($existing_booking_id)) : ?>
376 <input type="hidden" name="existing_booking_id" value="<?php echo esc_attr($existing_booking_id); ?>">
377 <?php endif; ?>
378 <?php if (!empty($booking_reference)) : ?>
379 <input type="hidden" name="booking_reference" value="<?php echo esc_attr($booking_reference); ?>">
380 <?php endif; ?>
381 <?php if ($remaining_amount !== null) : ?>
382 <input type="hidden" name="remaining_amount" value="<?php echo esc_attr($remaining_amount); ?>">
383 <?php endif; ?>
384 <?php if (!empty($booking->amount_paid)) : ?>
385 <input type="hidden" name="amount_paid" value="<?php echo esc_attr($booking->amount_paid); ?>">
386 <?php endif; ?>
387 <?php if (!empty($booking->total_amount)) : ?>
388 <input type="hidden" name="total_amount" value="<?php echo esc_attr($booking->total_amount); ?>">
389 <?php endif; ?>
390 <?php endif; ?>
391
392 <?php if (!$is_remaining_payment) : ?>
393 <!-- Contact Form Section -->
394 <?php
395 $contact_config = $form_config['contact_form'] ?? [];
396 if (!empty($contact_config)) {
397 yatra_render_form_section($contact_config, 'contact_', $countries);
398 }
399 ?>
400
401 <!-- Emergency Contact Section -->
402 <?php
403 $emergency_config = $form_config['emergency_contact_form'] ?? [];
404 if (!empty($emergency_config) && (!isset($emergency_config['enabled']) || $emergency_config['enabled'])) {
405 yatra_render_form_section($emergency_config, 'emergency_', $countries);
406 }
407 ?>
408
409 <!-- Traveler Information Section -->
410 <?php
411 $traveler_config = $form_config['traveler_form'] ?? [];
412 ?>
413 <?php endif; ?>
414
415 <?php
416 $traveler_config = $form_config['traveler_form'] ?? [];
417 $traveler_count = isset($total_travelers) ? max(1, (int)$total_travelers) : 1;
418
419 // Check if we have traveler-based pricing info
420 $pricing_type = isset($pricing_type) ? $pricing_type : 'regular';
421 $price_types = isset($price_types) ? $price_types : [];
422 $traveler_counts = isset($traveler_counts) ? $traveler_counts : [];
423
424 /**
425 * Coerce JSON / serialized / object values to a list for foreach-safe iteration.
426 *
427 * @param mixed $value
428 * @return array<int|string, mixed>
429 */
430 $yatra_normalize_booking_arrayish = static function ($value): array {
431 if ($value === null || $value === false || $value === '') {
432 return [];
433 }
434 if (is_array($value)) {
435 return $value;
436 }
437 if ($value instanceof \stdClass) {
438 return (array) $value;
439 }
440 if (is_string($value)) {
441 $trim = trim($value);
442 if ($trim === '') {
443 return [];
444 }
445 $decoded = json_decode($value, true);
446 if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) {
447 return $decoded;
448 }
449 if (function_exists('maybe_unserialize')) {
450 $un = maybe_unserialize($value);
451 if (is_array($un)) {
452 return $un;
453 }
454 }
455 }
456
457 return [];
458 };
459
460 $price_types = $yatra_normalize_booking_arrayish($price_types);
461 $traveler_counts = $yatra_normalize_booking_arrayish($traveler_counts);
462
463 // Build traveler-to-category mapping for traveler-based pricing
464 $traveler_category_map = [];
465 if ($pricing_type === 'traveler_based' && !empty($price_types) && !empty($traveler_counts)) {
466 $traveler_index = 1;
467 foreach ($price_types as $index => $pt) {
468 $pt = (object) $pt;
469 $category_id = $pt->category_id ?? $index;
470 $category_label = $pt->category_label ?? __('Traveler', 'yatra');
471 $count = isset($traveler_counts[$category_id]) ? (int) $traveler_counts[$category_id] : 0;
472
473 for ($c = 1; $c <= $count; $c++) {
474 $traveler_category_map[$traveler_index] = [
475 'category_id' => $category_id,
476 'category_label' => $category_label,
477 'category_index' => $c,
478 ];
479 $traveler_index++;
480 }
481 }
482 }
483
484 $price_candidates = [
485 isset($trip->price) ? (float) $trip->price : 0,
486 isset($booking->session_price) ? (float) $booking->session_price : 0,
487 isset($trip->sale_price) ? (float) $trip->sale_price : 0,
488 isset($trip->original_price) ? (float) $trip->original_price : 0,
489 ];
490
491 $effective_trip_price = 0;
492 foreach ($price_candidates as $candidate) {
493 if ($candidate > 0) {
494 $effective_trip_price = $candidate;
495 break;
496 }
497 }
498
499 $traveler_price_rows = [];
500 $calculated_total = 0;
501
502 if ($pricing_type === 'traveler_based' && !empty($price_types)) {
503 foreach ($price_types as $index => $pt) {
504 $pt = (object) $pt;
505 $category_id = $pt->category_id ?? $index;
506 $category_label = $pt->category_label ?? __('Traveler', 'yatra');
507 $category_price = isset($pt->effective_price) ? (float) $pt->effective_price : ($pt->sale_price ?? $pt->discounted_price ?? $pt->original_price ?? 0);
508 $count = isset($traveler_counts[$category_id]) ? (int) $traveler_counts[$category_id] : ($index === 0 ? 1 : 0);
509 // Single source of truth for the line amount (per-person × count, flat
510 // per-group, or per-block group pricing). Defaults to the prior flat/
511 // multiply behaviour when group_overflow is absent.
512 $pt_pricing_mode = $pt->pricing_mode ?? 'per_person';
513 $subtotal = \Yatra\Services\TripPricingService::categoryLineSubtotal($pt, $count, $category_price);
514
515 $age_info = '';
516 if (isset($pt->age_min) || isset($pt->age_max)) {
517 if (isset($pt->age_min) && isset($pt->age_max)) {
518 /* translators: 1: minimum age, 2: maximum age. */
519 $age_info = sprintf(__('(Age %1$d-%2$d)', 'yatra'), $pt->age_min, $pt->age_max);
520 } elseif (isset($pt->age_min)) {
521 /* translators: %d: minimum age. */
522 $age_info = sprintf(__('(Age %d+)', 'yatra'), $pt->age_min);
523 } else {
524 /* translators: %d: maximum age. */
525 $age_info = sprintf(__('(Up to age %d)', 'yatra'), $pt->age_max);
526 }
527 }
528
529 $traveler_price_rows[] = [
530 'category_id' => $category_id,
531 'category_label' => $category_label,
532 'category_price' => $category_price,
533 'count' => $count,
534 'subtotal' => $subtotal,
535 'pricing_mode' => $pt_pricing_mode,
536 'age_info' => $age_info,
537 ];
538
539 $calculated_total += $subtotal;
540
541 if ($effective_trip_price <= 0 && $category_price > 0) {
542 $effective_trip_price = $category_price;
543 }
544 }
545 }
546
547 if ($effective_trip_price <= 0) {
548 $effective_trip_price = 0;
549 }
550
551 $initial_total_amount = $calculated_total > 0
552 ? $calculated_total
553 : ($effective_trip_price * max(1, (int) $total_travelers));
554
555 if ($initial_total_amount <= 0 && $effective_trip_price > 0) {
556 $initial_total_amount = $effective_trip_price * max(1, (int) $total_travelers);
557 }
558
559 $initial_due_amount = $initial_total_amount;
560
561 ?>
562
563 <?php
564 // Honour the Traveler section's enable flag (Pro Dynamic Form module), mirroring
565 // the Contact/Emergency sections. The default config has no `enabled` key, so an
566 // un-customised/Free site treats the section as enabled and renders as before.
567 $traveler_section_enabled = !isset($traveler_config['enabled']) || (bool) $traveler_config['enabled'];
568 ?>
569 <?php if (!empty($traveler_config) && $traveler_section_enabled && !$is_remaining_payment) : ?>
570 <div class="yatra-booking-section">
571 <h2 class="yatra-section-title"><?php echo esc_html(yatra_translate_form_string($traveler_config['title'] ?? '') ?: __('Traveler Information', 'yatra')); ?></h2>
572 <?php if (!empty($traveler_config['description'])) : ?>
573 <p class="yatra-section-description"><?php echo esc_html(yatra_translate_form_string($traveler_config['description'])); ?></p>
574 <?php endif; ?>
575
576 <div id="yatra-travelers-container">
577 <?php
578 $traveler_fields = $traveler_config['fields'] ?? [];
579 usort($traveler_fields, function($a, $b) {
580 return ($a['order'] ?? 0) - ($b['order'] ?? 0);
581 });
582
583 // Group fields by section
584 $grouped_traveler_fields = [];
585 foreach ($traveler_fields as $field) {
586 if (!empty($field['enabled'])) {
587 $section = $field['section'] ?? 'main';
588 if (!isset($grouped_traveler_fields[$section])) {
589 $grouped_traveler_fields[$section] = [];
590 }
591 $grouped_traveler_fields[$section][] = $field;
592 }
593 }
594
595 for ($i = 1; $i <= $traveler_count; $i++) :
596 // Determine traveler label based on category if traveler-based pricing
597 if (!empty($traveler_category_map[$i])) {
598 $category_info = $traveler_category_map[$i];
599 $category_label = $category_info['category_label'];
600 $category_index = $category_info['category_index'];
601 /* translators: 1: traveler category label (e.g. Adult, Child), 2: sequential index within that category. */
602 $traveler_label = sprintf(__('%1$s %2$d', 'yatra'), $category_label, $category_index);
603 if ($i === 1) {
604 $traveler_label .= ' (' . __('Lead Traveler', 'yatra') . ')';
605 }
606 } else {
607 /* translators: %d: traveler sequence number. */
608 $traveler_label = ($i === 1) ? __('Traveler 1 (Lead Traveler)', 'yatra') : sprintf(__('Traveler %d', 'yatra'), $i);
609 }
610
611 // Fields targeted at the lead traveler (applies_to === 'lead') render
612 // only for Traveler 1; everything else ('all' / unset) renders for
613 // every traveler. Empty subsections are dropped so additional
614 // travelers don't get an orphan heading.
615 $traveler_grouped_fields = [];
616 foreach ($grouped_traveler_fields as $sk => $sf) {
617 $applicable = array_values(array_filter($sf, function ($f) use ($i) {
618 return ($f['applies_to'] ?? 'all') !== 'lead' || $i === 1;
619 }));
620 if (!empty($applicable)) {
621 $traveler_grouped_fields[$sk] = $applicable;
622 }
623 }
624 ?>
625 <div class="yatra-traveler-form" data-traveler-index="<?php echo esc_attr($i); ?>" <?php if (!empty($traveler_category_map[$i])): ?>data-category-id="<?php echo esc_attr($traveler_category_map[$i]['category_id']); ?>" data-category-label="<?php echo esc_attr($traveler_category_map[$i]['category_label']); ?>"<?php endif; ?>>
626 <div class="yatra-traveler-header">
627 <h3 class="yatra-traveler-title"><?php echo esc_html($traveler_label); ?></h3>
628 <?php if ($i > 1) : ?>
629 <span class="yatra-traveler-note"><?php esc_html_e('Additional traveler', 'yatra'); ?></span>
630 <?php endif; ?>
631 </div>
632
633 <?php foreach ($traveler_grouped_fields as $section_key => $section_fields) : ?>
634 <?php if ($section_key !== 'main') : ?>
635 <div class="yatra-traveler-subsection">
636 <h4 class="yatra-subsection-title">
637 <?php
638 switch ($section_key) {
639 case 'passport':
640 esc_html_e('Additional details', 'yatra');
641 break;
642 case 'dietary_medical':
643 esc_html_e('Dietary & Medical Requirements', 'yatra');
644 break;
645 default:
646 echo esc_html(ucwords(str_replace('_', ' ', $section_key)));
647 }
648 ?>
649 </h4>
650 <?php endif; ?>
651
652 <div class="yatra-form-row">
653 <?php foreach ($section_fields as $field) :
654 $field_id = "traveler-{$i}-" . esc_attr($field['id']);
655 $field_name = "travelers[{$i}][" . esc_attr($field['id']) . "]";
656 yatra_render_form_field($field, '', $countries, $field_name, $field_id);
657 endforeach; ?>
658 </div>
659
660 <?php if ($section_key !== 'main') : ?>
661 </div>
662 <?php endif; ?>
663 <?php endforeach; ?>
664 </div>
665 <?php endfor; ?>
666 </div>
667 </div>
668 <?php endif; ?>
669
670 <!-- Payment Method Section -->
671 <?php
672 /**
673 * Filter to check if flexible payments module is enabled (Pro feature)
674 * When enabled, deposit and partial payment options will be available
675 */
676 $flexible_payments_enabled = apply_filters('yatra_flexible_payments_enabled', false);
677
678 /**
679 * Filter to get payment method options.
680 *
681 * Pro module (FlexiblePayments) can add deposit/partial payment options here.
682 * We pass `trip_id` so the Pro module can look up per-trip overrides
683 * (trip.deposit_amount / trip.deposit_percentage) and expose the deposit
684 * option even when the site-wide "deposit_required" flag is off — i.e. a
685 * single trip can opt in to a custom deposit without enabling deposits for
686 * the whole catalogue.
687 */
688 $payment_method_options = apply_filters('yatra_payment_method_options', [], [
689 'trip_id' => isset($trip_id) ? (int) $trip_id : 0,
690 'deposit_required' => $deposit_required,
691 'deposit_percentage' => $deposit_percentage,
692 'partial_payment' => $partial_payment,
693 'partial_payment_percentage' => $partial_payment_percentage,
694 // Tour start (when a date is already chosen) → Pro hides deposit/partial for
695 // tour-anchored payments when the tour is inside the balance-due window.
696 'travel_date' => isset($booking->travel_date) ? (string) $booking->travel_date : '',
697 ]);
698
699 $has_flexible_options = $flexible_payments_enabled && !empty($payment_method_options);
700 ?>
701 <?php if (!$is_remaining_payment && $has_flexible_options) : ?>
702 <div class="yatra-booking-section">
703 <h2 class="yatra-section-title"><?php esc_html_e('Payment Method', 'yatra'); ?></h2>
704
705 <div class="yatra-payment-methods">
706 <label class="yatra-payment-option">
707 <input type="radio" name="payment_method" value="full" checked>
708 <span class="yatra-payment-label">
709 <strong><?php esc_html_e('Pay in Full', 'yatra'); ?></strong>
710 <span><?php esc_html_e('Pay the total amount now', 'yatra'); ?></span>
711 </span>
712 </label>
713
714 <?php foreach ($payment_method_options as $option_key => $option) : ?>
715 <label class="yatra-payment-option">
716 <input type="radio" name="payment_method" value="<?php echo esc_attr($option['value']); ?>">
717 <span class="yatra-payment-label">
718 <strong><?php echo esc_html($option['label']); ?></strong>
719 <span><?php echo esc_html($option['description']); ?></span>
720 </span>
721 </label>
722 <?php endforeach; ?>
723 </div>
724 </div>
725 <?php endif; ?>
726
727 <!-- Payment Gateway Section -->
728 <?php
729 // Always use the live registry list (same as BookingPageHandler). Do not intersect with $enabled_gateways
730 // from older templates/sessions — that caused only stale gateways (e.g. Pay Later) to show.
731 $display_gateways = [];
732 if (class_exists('Yatra\PaymentGateways\PaymentGatewayRegistry')) {
733 $registry = \Yatra\PaymentGateways\PaymentGatewayRegistry::getInstance();
734 foreach ($registry->getForCheckout() as $gateway) {
735 if (!empty($gateway['id'])) {
736 $display_gateways[$gateway['id']] = $gateway;
737 }
738 }
739 }
740 ?>
741 <?php if (!empty($display_gateways)) : ?>
742 <div class="yatra-booking-section">
743 <h2 class="yatra-section-title"><?php esc_html_e('Select Payment Gateway', 'yatra'); ?></h2>
744
745 <div class="yatra-gateway-options">
746 <?php
747 $first = true;
748 foreach ($display_gateways as $gateway_id => $gateway) :
749 $icon = !empty($gateway['icon']) ? $gateway['icon'] : plugins_url('assets/images/payment-placeholder.png', dirname(__DIR__));
750 ?>
751 <div class="yatra-gateway-option-wrapper">
752 <label class="yatra-gateway-option">
753 <input type="radio" name="payment_gateway" value="<?php echo esc_attr($gateway_id); ?>" <?php checked($first); ?>>
754 <span class="yatra-gateway-icon-wrap">
755 <img src="<?php echo esc_url($icon ?? ''); ?>" alt="<?php echo esc_attr($gateway['title'] ?? ''); ?>" class="yatra-gateway-icon">
756 </span>
757 <span class="yatra-gateway-content">
758 <strong class="yatra-gateway-title"><?php echo esc_html($gateway['title']); ?></strong>
759 <span class="yatra-gateway-desc"><?php echo esc_html($gateway['description']); ?></span>
760 <?php if (empty($gateway['is_configured'])) : ?>
761 <span class="yatra-gateway-desc yatra-gateway-setup-required"><?php esc_html_e('Setup incomplete — finish configuration under Yatra → Settings → Payment, or choose another method.', 'yatra'); ?></span>
762 <?php endif; ?>
763 </span>
764 </label>
765 <div class="yatra-gateway-extra" id="yatra-gateway-extra-<?php echo esc_attr($gateway_id); ?>" data-gateway="<?php echo esc_attr($gateway_id); ?>"></div>
766 </div>
767 <?php
768 $first = false;
769 endforeach;
770 ?>
771 </div>
772 </div>
773 <?php endif; ?>
774
775 <?php if (!$is_remaining_payment) : ?>
776 <!-- Special Requests -->
777 <div class="yatra-booking-section">
778 <h2 class="yatra-section-title"><?php esc_html_e('Special Requests', 'yatra'); ?></h2>
779 <p class="yatra-section-description"><?php esc_html_e('Any special requests or notes for your trip (optional)', 'yatra'); ?></p>
780
781 <div class="yatra-form-group yatra-field-full">
782 <textarea
783 id="special-requests"
784 name="special_requests"
785 rows="4"
786 placeholder="<?php esc_attr_e('E.g., dietary requirements, accessibility needs, celebration requests...', 'yatra'); ?>"
787 ></textarea>
788 </div>
789 </div>
790 <?php endif; ?>
791
792 <!-- Account Creation Section (only for guests) -->
793 <?php
794 $allow_guest_checkout = \Yatra\Services\SettingsService::isEnabled('allow_guest_checkout');
795 $require_login = \Yatra\Services\SettingsService::isEnabled('require_login');
796 // Account CREATION is gated by the same setting the backend enforces
797 // (AuthController rejects registration when this is off). When registration is
798 // disabled we never offer account-creation UI.
799 $registration_enabled = \Yatra\Services\SettingsService::isEnabled('customer_registration');
800
801 // Show the account section only when it has something to render: login is
802 // required, guest checkout is off (must authenticate), or registration is on
803 // (optional/required account creation). When registration is OFF and guests are
804 // allowed, there is nothing to show — proceed as a pure guest.
805 if (!is_user_logged_in() && !$is_remaining_payment
806 && ($require_login || !$allow_guest_checkout || $registration_enabled)) :
807 ?>
808 <div class="yatra-booking-section yatra-account-section">
809 <h2 class="yatra-section-title"><?php esc_html_e('Account', 'yatra'); ?></h2>
810
811 <?php if ($require_login || (!$allow_guest_checkout && !$registration_enabled)) : ?>
812 <!-- Login Required Message (login required, OR guests off with registration disabled) -->
813 <div class="yatra-login-required-notice" style="background: #fef3c7; border: 1px solid #f59e0b; padding: 16px; border-radius: 8px; margin-bottom: 16px;">
814 <div style="display: flex; align-items: flex-start; gap: 12px;">
815 <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#d97706" stroke-width="2" style="flex-shrink: 0;">
816 <circle cx="12" cy="12" r="10"></circle>
817 <line x1="12" y1="8" x2="12" y2="12"></line>
818 <line x1="12" y1="16" x2="12.01" y2="16"></line>
819 </svg>
820 <div>
821 <p style="font-weight: 600; color: #92400e; margin: 0 0 8px 0;"><?php esc_html_e('Login Required', 'yatra'); ?></p>
822 <p style="color: #a16207; margin: 0 0 12px 0; font-size: 14px;">
823 <?php echo $registration_enabled
824 ? esc_html__('You must be logged in to complete this booking. Please log in or create an account.', 'yatra')
825 : esc_html__('You must be logged in to complete this booking. Please log in to continue.', 'yatra'); ?>
826 </p>
827 <a href="<?php echo esc_url(wp_login_url(get_permalink())); ?>" class="yatra-login-btn" style="display: inline-flex; align-items: center; gap: 8px; padding: 10px 20px; background: #3b82f6; color: #fff; text-decoration: none; border-radius: 6px; font-weight: 500; font-size: 14px;">
828 <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
829 <path d="M15 3h4a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-4"></path>
830 <polyline points="10 17 15 12 10 7"></polyline>
831 <line x1="15" y1="12" x2="3" y2="12"></line>
832 </svg>
833 <?php esc_html_e('Log In', 'yatra'); ?>
834 </a>
835 <?php if ($registration_enabled) : ?>
836 <span style="margin: 0 12px; color: #a16207;"><?php esc_html_e('or', 'yatra'); ?></span>
837 <a href="<?php echo esc_url(wp_registration_url()); ?>" style="color: #3b82f6; text-decoration: none; font-weight: 500;">
838 <?php esc_html_e('Create an Account', 'yatra'); ?>
839 </a>
840 <?php endif; ?>
841 </div>
842 </div>
843 </div>
844 <input type="hidden" name="login_required" value="1">
845
846 <?php elseif (!$allow_guest_checkout && $registration_enabled) : ?>
847 <!-- Account Required - Must Create Account (registration enabled) -->
848 <p class="yatra-section-description" style="margin-bottom: 16px;">
849 <?php esc_html_e('Please create an account to complete your booking. This allows you to manage your bookings and receive important updates.', 'yatra'); ?>
850 </p>
851
852 <div class="yatra-form-row">
853 <div class="yatra-form-group yatra-field-half">
854 <label for="account_password">
855 <?php esc_html_e('Password', 'yatra'); ?> <span class="required">*</span>
856 </label>
857 <input type="password" id="account_password" name="account_password" required minlength="8"
858 placeholder="<?php esc_attr_e('Minimum 8 characters', 'yatra'); ?>">
859 </div>
860 <div class="yatra-form-group yatra-field-half">
861 <label for="account_password_confirm">
862 <?php esc_html_e('Confirm Password', 'yatra'); ?> <span class="required">*</span>
863 </label>
864 <input type="password" id="account_password_confirm" name="account_password_confirm" required minlength="8"
865 placeholder="<?php esc_attr_e('Re-enter password', 'yatra'); ?>">
866 </div>
867 </div>
868 <p class="yatra-field-hint" style="font-size: 13px; color: #6b7280; margin-top: 8px;">
869 <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="display: inline-block; vertical-align: middle; margin-right: 4px;">
870 <circle cx="12" cy="12" r="10"></circle>
871 <path d="M12 16v-4"></path>
872 <path d="M12 8h.01"></path>
873 </svg>
874 <?php esc_html_e('Your email address will be used as your username.', 'yatra'); ?>
875 </p>
876 <input type="hidden" name="create_account" value="1">
877
878 <?php elseif ($registration_enabled) : ?>
879 <!-- Guest Checkout Allowed - Optional Account Creation (registration enabled) -->
880 <p class="yatra-section-description" style="margin-bottom: 16px;">
881 <?php esc_html_e('Create an account to easily manage your bookings and receive travel updates.', 'yatra'); ?>
882 </p>
883
884 <label class="yatra-checkbox-label" style="margin-bottom: 16px;">
885 <input type="checkbox" name="create_account" id="create-account" value="1">
886 <span><?php esc_html_e('Create an account for easier booking management', 'yatra'); ?></span>
887 </label>
888
889 <div class="yatra-account-fields" id="yatra-account-fields" style="display: none;">
890 <div class="yatra-form-row">
891 <div class="yatra-form-group yatra-field-half">
892 <label for="account_password">
893 <?php esc_html_e('Password', 'yatra'); ?> <span class="required">*</span>
894 </label>
895 <input type="password" id="account_password" name="account_password" minlength="8"
896 autocomplete="new-password"
897 placeholder="<?php esc_attr_e('Minimum 8 characters', 'yatra'); ?>">
898 </div>
899 <div class="yatra-form-group yatra-field-half">
900 <label for="account_password_confirm">
901 <?php esc_html_e('Confirm Password', 'yatra'); ?> <span class="required">*</span>
902 </label>
903 <input type="password" id="account_password_confirm" name="account_password_confirm" minlength="8"
904 autocomplete="new-password"
905 placeholder="<?php esc_attr_e('Re-enter password', 'yatra'); ?>">
906 </div>
907 </div>
908 <p class="yatra-field-hint" style="font-size: 13px; color: #6b7280; margin-top: 8px;">
909 <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="display: inline-block; vertical-align: middle; margin-right: 4px;">
910 <circle cx="12" cy="12" r="10"></circle>
911 <path d="M12 16v-4"></path>
912 <path d="M12 8h.01"></path>
913 </svg>
914 <?php esc_html_e('Your email address will be used as your username.', 'yatra'); ?>
915 </p>
916 </div>
917 <?php endif; ?>
918 </div>
919 <?php elseif (is_user_logged_in()) : ?>
920 <!-- User is logged in -->
921 <div class="yatra-booking-section yatra-logged-in-notice">
922 <div style="display: flex; align-items: center; gap: 12px; padding: 12px 16px; background: #ecfdf5; border: 1px solid #10b981; border-radius: 8px;">
923 <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#059669" stroke-width="2">
924 <path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path>
925 <polyline points="22 4 12 14.01 9 11.01"></polyline>
926 </svg>
927 <div>
928 <span style="font-weight: 500; color: #065f46;">
929 <?php
930 $current_user = wp_get_current_user();
931 printf(
932 /* translators: %s: User display name */
933 esc_html__('Logged in as %s', 'yatra'),
934 '<strong>' . esc_html($current_user->display_name) . '</strong>'
935 );
936 ?>
937 </span>
938 <a href="<?php echo esc_url(wp_logout_url(get_permalink())); ?>" style="margin-left: 12px; color: #059669; font-size: 13px;">
939 <?php esc_html_e('Log out', 'yatra'); ?>
940 </a>
941 </div>
942 </div>
943 </div>
944 <?php endif; ?>
945
946 <!-- Terms & Conditions -->
947 <div class="yatra-booking-section">
948 <div class="yatra-terms-container">
949 <?php
950 $termsPageId = 0;
951 $privacyPageId = 0;
952 if (class_exists('\\Yatra\\Services\\SettingsService')) {
953 $termsPageId = \Yatra\Services\SettingsService::getInt('terms_page_id', 0);
954 $privacyPageId = \Yatra\Services\SettingsService::getInt('privacy_policy_page_id', 0);
955 } else {
956 $termsPageId = (int) get_option('yatra_terms_page_id', 0);
957 $privacyPageId = (int) get_option('yatra_privacy_policy_page_id', 0);
958 }
959
960 $termsUrl = $termsPageId > 0 ? get_permalink($termsPageId) : '';
961 $privacyUrl = $privacyPageId > 0 ? get_permalink($privacyPageId) : '';
962 if ($privacyUrl === '' && function_exists('get_privacy_policy_url')) {
963 $privacyUrl = (string) get_privacy_policy_url();
964 }
965 ?>
966
967 <label class="yatra-checkbox-label">
968 <input type="checkbox" name="accept_terms" id="accept-terms" required>
969 <span>
970 <?php
971 printf(
972 /* translators: %s: Terms and Conditions link */
973 esc_html__('I have read and agree to the %s', 'yatra'),
974 $termsUrl
975 ? '<a href="' . esc_url($termsUrl) . '" target="_blank" rel="noopener noreferrer">' . esc_html__('Terms and Conditions', 'yatra') . '</a>'
976 : '<span>' . esc_html__('Terms and Conditions', 'yatra') . '</span>'
977 );
978 ?>
979 <span class="required">*</span>
980 </span>
981 </label>
982
983 <label class="yatra-checkbox-label">
984 <input type="checkbox" name="accept_privacy" id="accept-privacy" required>
985 <span>
986 <?php
987 printf(
988 /* translators: %s: Privacy Policy link */
989 esc_html__('I agree to the %s and consent to my data being processed', 'yatra'),
990 $privacyUrl
991 ? '<a href="' . esc_url($privacyUrl) . '" target="_blank" rel="noopener noreferrer">' . esc_html__('Privacy Policy', 'yatra') . '</a>'
992 : '<span>' . esc_html__('Privacy Policy', 'yatra') . '</span>'
993 );
994 ?>
995 <span class="required">*</span>
996 </span>
997 </label>
998
999 <label class="yatra-checkbox-label">
1000 <input type="checkbox" name="subscribe_newsletter" id="subscribe-newsletter">
1001 <span><?php esc_html_e('Subscribe to our newsletter for travel tips and exclusive offers', 'yatra'); ?></span>
1002 </label>
1003 </div>
1004 </div>
1005