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

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

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