PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.5
Yatra – Travel Booking & Tour Operator Software v3.0.5
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-content.php

booking-content.php in Yatra – Travel Booking & Tour Operator Software 3.0.5, at templates/partials/booking-content.php

827 lines 54.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Booking Content Partial
4 *
5 * Main booking form and sidebar content - used by both:
6 * - booking.php (with header/footer)
7 * - shortcode (without header/footer)
8 *
9 * @package Yatra
10 *
11 * Expected variables from parent:
12 * - $booking (global object)
13 * - $trip
14 * - $travel_date
15 * - $total_travelers
16 * - $deposit_required
17 * - $deposit_percentage
18 * - $partial_payment
19 * - $partial_payment_percentage
20 * - $enabled_gateways
21 * - $is_remaining_payment
22 * - $remaining_amount
23 * - $existing_booking_id
24 * - $booking_reference
25 */
26
27 if (!defined('ABSPATH')) {
28 exit;
29 }
30
31 // Prepare variables for form fields partial
32 $trip_id = $trip->id;
33 $trip_slug = $trip->slug ?? '';
34 $is_remaining_payment = !empty($booking->is_remaining_payment);
35 $remaining_amount = isset($booking->remaining_amount) ? (float) $booking->remaining_amount : null;
36 $existing_booking_id = $booking->existing_booking_id ?? 0;
37 $booking_reference = $booking->booking_reference ?? '';
38 $amount_paid = isset($booking->amount_paid) ? (float) $booking->amount_paid : null;
39 $total_amount = isset($booking->total_amount) ? (float) $booking->total_amount : null;
40
41 // Check if login is required
42 $require_login = \Yatra\Services\SettingsService::get('require_login', false);
43 $allow_guest_checkout = \Yatra\Services\SettingsService::get('allow_guest_checkout', true);
44 $needs_authentication = !is_user_logged_in() && ($require_login || !$allow_guest_checkout);
45
46 // Get group discount from booking object (calculated in AppServiceProvider)
47 $group_discount = $booking->group_discount ?? null;
48 $group_discount_amount = $group_discount['amount'] ?? 0;
49 $group_discount_code = $group_discount['code'] ?? null;
50 $group_discount_label = $group_discount['label'] ?? __('Group Discount', 'yatra');
51
52 // Get pre-calculated pricing from booking object (calculated in BookingPageHandler)
53 $pricing_calculation = $booking->pricing_calculation ?? [
54 'base_amount' => 0,
55 'gross_total' => 0,
56 'final_total' => 0,
57 'amount_due' => 0,
58 'tax_calculation' => ['tax_breakdown' => [], 'total_tax_amount' => 0],
59 ];
60
61 $tax_calculation = $pricing_calculation['tax_calculation'] ?? ['tax_breakdown' => [], 'total_tax_amount' => 0];
62
63 // Use pre-calculated amounts
64 $summary_total_amount = $is_remaining_payment && !empty($total_amount)
65 ? (float) $total_amount
66 : ($pricing_calculation['final_total'] ?? 0);
67 $summary_due_amount = $is_remaining_payment && $remaining_amount !== null
68 ? (float) $remaining_amount
69 : ($pricing_calculation['amount_due'] ?? $pricing_calculation['final_total'] ?? 0);
70 ?>
71
72 <div class="yatra-booking-page">
73 <div class="yatra-booking-container">
74 <div class="yatra-booking-layout">
75 <!-- Left Side: Auth Form or Booking Form -->
76 <div class="yatra-booking-main">
77 <?php if ($needs_authentication) : ?>
78 <!-- Show Login/Registration Form -->
79 <div class="yatra-booking-header">
80 <h1><?php esc_html_e('Login Required', 'yatra'); ?></h1>
81 <p class="yatra-booking-subtitle"><?php esc_html_e('Please login or create an account to continue with your booking', 'yatra'); ?></p>
82 </div>
83
84 <?php
85 // Include the authentication form partial
86 include YATRA_PLUGIN_PATH . 'templates/partials/booking-auth.php';
87 ?>
88 <?php else : ?>
89 <!-- Show Booking Form -->
90 <div class="yatra-booking-header">
91 <h1>
92 <?php
93 if (!empty($booking->is_remaining_payment)) {
94 esc_html_e('Pay Remaining Balance', 'yatra');
95 } else {
96 esc_html_e('Complete Your Booking', 'yatra');
97 }
98 ?>
99 </h1>
100 <p class="yatra-booking-subtitle">
101 <?php
102 $reference_label = $booking_reference ? '#' . $booking_reference : '';
103
104 if ($is_remaining_payment) {
105 echo esc_html(
106 sprintf(
107 /* translators: %s booking reference */
108 __('You are paying the remaining balance for Booking %s.', 'yatra'),
109 $reference_label ?: __('(unknown reference)', 'yatra')
110 )
111 );
112 } else {
113 esc_html_e('Please fill in your details to complete the booking', 'yatra');
114 }
115 ?>
116 </p>
117 <?php if ($is_remaining_payment && !empty($remaining_amount)) : ?>
118 <div class="yatra-remaining-banner">
119 <strong><?php esc_html_e('Amount Due Now', 'yatra'); ?>:</strong>
120 <span><?php echo esc_html(yatra_format_price((float) $remaining_amount, null, false)); ?></span>
121 <?php if ($amount_paid !== null) : ?>
122 <small>
123 <?php
124 printf(
125 /* translators: %s formatted amount */
126 esc_html__('Amount already paid: %s', 'yatra'),
127 yatra_format_price((float) $amount_paid, null, false)
128 );
129 ?>
130 </small>
131 <?php endif; ?>
132 </div>
133 <?php endif; ?>
134 </div>
135
136 <!-- Booking Form -->
137 <form
138 class="yatra-booking-form"
139 id="yatra-booking-form"
140 data-payment-due="<?php echo esc_attr($summary_due_amount); ?>"
141 data-is-remaining-payment="<?php echo esc_attr($is_remaining_payment ? 'yes' : 'no'); ?>"
142 >
143 <?php
144 // Pass pricing type info to form fields partial
145 // Prefer booking pricing data, fallback to trip definition
146 $trip_pricing_mode = \Yatra\Services\TripPricingService::resolvePricingType($trip);
147 $pricing_type = !empty($booking->pricing_type) ? $booking->pricing_type : ($trip->pricing_type ?? 'regular');
148 $price_types = !empty($booking->price_types) ? $booking->price_types : ($trip->price_types ?? []);
149 if ($trip_pricing_mode === 'regular' && empty($booking->price_types)) {
150 $pricing_type = 'regular';
151 $price_types = [];
152 }
153 if (empty($pricing_type) && !empty($price_types)) {
154 $pricing_type = 'traveler_based';
155 }
156 // Do not override explicit regular trip pricing with orphan trip_price_types rows
157 if (empty($price_types) && !empty($trip->price_types) && $trip_pricing_mode === 'traveler_based') {
158 $price_types = $trip->price_types;
159 if (empty($pricing_type) || $pricing_type === 'regular') {
160 $pricing_type = 'traveler_based';
161 }
162 }
163 $traveler_counts = $booking->traveler_counts ?? [];
164
165 // Include shared booking form fields
166 include YATRA_PLUGIN_PATH . 'templates/partials/booking-form-fields.php';
167 ?>
168
169 <!-- Submit Button -->
170 <div class="yatra-booking-actions">
171 <button type="submit" class="yatra-booking-pay-btn" id="yatra-submit-booking">
172 <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="CurrentColor" stroke-width="2">
173 <rect x="1" y="4" width="22" height="16" rx="2" ry="2"></rect>
174 <line x1="1" y1="10" x2="23" y2="10"></line>
175 </svg>
176 <span id="pay-button-text"><?php esc_html_e('Complete Booking', 'yatra'); ?></span>
177 <span id="pay-amount"><?php echo esc_html(yatra_format_price((float) ($summary_due_amount ?? 0), null, false)); ?></span>
178 </button>
179 <a href="<?php echo esc_url(yatra_get_trip_permalink($trip)); ?>" class="yatra-booking-cancel-btn">
180 <?php esc_html_e('Cancel', 'yatra'); ?>
181 </a>
182 </div>
183 <div class="yatra-booking-form-errors" id="yatra-booking-form-errors" aria-live="polite"></div>
184
185 <input type="hidden" name="trip_price" value="<?php echo esc_attr($trip->price); ?>">
186 <input type="hidden" name="currency" value="<?php echo esc_attr(\Yatra\Services\SettingsService::getCurrency()); ?>">
187 <?php wp_nonce_field('yatra_booking_nonce', 'yatra_booking_nonce'); ?>
188 </form>
189 <?php endif; ?>
190 </div>
191
192 <!-- Right Side: Booking Summary -->
193 <div class="yatra-booking-sidebar">
194
195 <?php if ($is_remaining_payment) : ?>
196 <!-- Simplified Remaining Payment Sidebar -->
197 <div class="yatra-booking-summary yatra-remaining-summary" data-summary-total="<?php echo esc_attr($summary_total_amount); ?>" data-summary-due="<?php echo esc_attr($summary_due_amount); ?>" data-is-remaining="yes">
198 <h3><?php esc_html_e('Payment Summary', 'yatra'); ?></h3>
199
200 <!-- Booking Reference -->
201 <div class="yatra-remaining-reference">
202 <div class="yatra-reference-badge">
203 <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
204 <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
205 <polyline points="14 2 14 8 20 8"></polyline>
206 <line x1="16" y1="13" x2="8" y2="13"></line>
207 <line x1="16" y1="17" x2="8" y2="17"></line>
208 </svg>
209 <span><?php esc_html_e('Booking Reference', 'yatra'); ?></span>
210 </div>
211 <strong class="yatra-reference-value">#<?php echo esc_html($booking_reference); ?></strong>
212 </div>
213
214 <!-- Trip Info Card -->
215 <div class="yatra-remaining-trip-card">
216 <div class="yatra-remaining-trip-image">
217 <img src="<?php echo esc_url($trip->featured_image ?? ''); ?>" alt="<?php echo esc_attr($trip->title ?? ''); ?>">
218 </div>
219 <div class="yatra-remaining-trip-info">
220 <h4><?php echo esc_html($trip->title); ?></h4>
221 <div class="yatra-remaining-trip-meta">
222 <span>
223 <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
224 <rect x="3" y="4" width="18" height="18" rx="2" ry="2"></rect>
225 <line x1="16" y1="2" x2="16" y2="6"></line>
226 <line x1="8" y1="2" x2="8" y2="6"></line>
227 <line x1="3" y1="10" x2="21" y2="10"></line>
228 </svg>
229 <?php echo esc_html(date_i18n(get_option('date_format'), strtotime($travel_date))); ?>
230 </span>
231 <span>
232 <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
233 <path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path>
234 <circle cx="9" cy="7" r="4"></circle>
235 </svg>
236 <?php echo esc_html($total_travelers . ' ' . _n('traveler', 'travelers', $total_travelers, 'yatra')); ?>
237 </span>
238 </div>
239 </div>
240 </div>
241
242 <!-- Payment Breakdown -->
243 <div class="yatra-remaining-breakdown">
244 <div class="yatra-remaining-row">
245 <span><?php esc_html_e('Original Booking Total', 'yatra'); ?></span>
246 <span><?php echo esc_html(yatra_format_price((float) ($total_amount ?? 0), null, false)); ?></span>
247 </div>
248 <div class="yatra-remaining-row yatra-remaining-paid">
249 <span>
250 <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
251 <path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path>
252 <polyline points="22 4 12 14.01 9 11.01"></polyline>
253 </svg>
254 <?php esc_html_e('Amount Already Paid', 'yatra'); ?>
255 </span>
256 <span class="yatra-paid-amount"><?php echo esc_html(yatra_format_price((float) ($amount_paid ?? 0), null, false)); ?></span>
257 </div>
258 <div class="yatra-remaining-row yatra-remaining-due">
259 <span><strong><?php esc_html_e('Remaining Balance', 'yatra'); ?></strong></span>
260 <span class="yatra-due-amount"><strong><?php echo esc_html(yatra_format_price((float) ($remaining_amount ?? 0), null, false)); ?></strong></span>
261 </div>
262 </div>
263
264 <!-- Due Now Highlight -->
265 <div class="yatra-remaining-due-now">
266 <div class="yatra-due-now-label">
267 <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
268 <rect x="1" y="4" width="22" height="16" rx="2" ry="2"></rect>
269 <line x1="1" y1="10" x2="23" y2="10"></line>
270 </svg>
271 <?php esc_html_e('Amount Due Now', 'yatra'); ?>
272 </div>
273 <div class="yatra-due-now-amount" id="summary-due">
274 <?php echo esc_html(yatra_format_price((float) ($remaining_amount ?? 0), null, false)); ?>
275 </div>
276 </div>
277
278 <!-- Secure Payment Badge -->
279 <div class="yatra-remaining-secure">
280 <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
281 <rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect>
282 <path d="M7 11V7a5 5 0 0 1 10 0v4"></path>
283 </svg>
284 <span><?php esc_html_e('Secure SSL encrypted payment', 'yatra'); ?></span>
285 </div>
286 </div>
287 <?php else : ?>
288 <!-- Regular Booking Summary -->
289 <div class="yatra-booking-summary" data-summary-total="<?php echo esc_attr($summary_total_amount); ?>" data-summary-due="<?php echo esc_attr($summary_due_amount); ?>" data-is-remaining="no">
290 <h3><?php esc_html_e('Booking Summary', 'yatra'); ?></h3>
291
292 <!-- Travel Details -->
293 <div class="yatra-summary-travel-details">
294 <?php
295 $departure_time = $booking->departure_time ?? '';
296 $is_day_trip = $booking->is_day_trip ?? false;
297 $pricing_type = $booking->pricing_type ?? 'regular';
298 $price_types = $booking->price_types ?? [];
299 $traveler_counts = $booking->traveler_counts ?? [];
300 ?>
301
302 <div class="yatra-summary-form-group">
303 <label for="travel-date"><?php esc_html_e('Travel Date', 'yatra'); ?></label>
304 <div class="yatra-summary-readonly-field">
305 <?php if ($is_day_trip && !empty($departure_time)): ?>
306 <span class="yatra-date-display"><?php echo esc_html(\Yatra\Helpers\FormatHelper::formatDate((string) $travel_date)); ?></span>
307 <span class="yatra-time-display"><?php echo esc_html(\Yatra\Helpers\FormatHelper::formatTimeForDisplay($departure_time)); ?></span>
308 <?php else: ?>
309 <?php echo esc_html(\Yatra\Helpers\FormatHelper::formatDate((string) $travel_date)); ?>
310 <?php endif; ?>
311 </div>
312 <input type="hidden" id="travel-date" name="travel_date" form="yatra-booking-form" value="<?php echo esc_attr($travel_date); ?>">
313 <?php if (!empty($departure_time)): ?>
314 <input type="hidden" name="departure_time" form="yatra-booking-form" value="<?php echo esc_attr($departure_time); ?>">
315 <?php endif; ?>
316 </div>
317
318 <?php if ($pricing_type === 'traveler_based' && !empty($price_types)): ?>
319 <!-- Traveler-based pricing: use shared traveler selector partial -->
320 <div class="yatra-summary-form-group yatra-traveler-categories">
321 <label><?php esc_html_e('Travelers', 'yatra'); ?></label>
322 <?php
323 $traveler_rows = [];
324 foreach ($price_types as $index => $pt) {
325 $pt = (object) $pt;
326 $category_id = $pt->category_id ?? $index;
327 $category_label = $pt->category_label ?? __('Traveler', 'yatra');
328 $count = isset($traveler_counts[$category_id]) ? (int) $traveler_counts[$category_id] : ($index === 0 ? 1 : 0);
329 $category_price = isset($pt->effective_price) ? (float) $pt->effective_price : ($pt->sale_price ?? $pt->discounted_price ?? $pt->original_price ?? 0);
330 $pricing_mode = (!empty($pt->pricing_mode) && $pt->pricing_mode === 'per_group') ? 'per_group' : 'per_person';
331 $age_info = '';
332 if (isset($pt->age_min) || isset($pt->age_max)) {
333 if (isset($pt->age_min) && isset($pt->age_max)) {
334 /* translators: 1: minimum age, 2: maximum age. */
335 $age_info = sprintf(__('(Age %1$d-%2$d)', 'yatra'), $pt->age_min, $pt->age_max);
336 } elseif (isset($pt->age_min)) {
337 /* translators: %d: minimum age. */
338 $age_info = sprintf(__('(Age %d+)', 'yatra'), $pt->age_min);
339 } else {
340 /* translators: %d: maximum age. */
341 $age_info = sprintf(__('(Up to age %d)', 'yatra'), $pt->age_max);
342 }
343 }
344 $input_id = 'traveler_' . $category_id;
345 $traveler_rows[] = [
346 'label' => $category_label,
347 'subtitle' => $age_info,
348 'price_html' => $category_price > 0 ? '<span class="yatra-quantity-price">' . yatra_format_price($category_price) . '</span>' : '',
349 'row_attrs' => [
350 'data-category-id' => $category_id,
351 'data-price' => $category_price,
352 'data-pricing-mode' => $pricing_mode,
353 ],
354 'minus_disabled' => ($index !== 0 && $count <= 0),
355 'plus_disabled' => false,
356 'minus_attrs' => [
357 'data-target' => $input_id,
358 /* translators: %s: traveler category label (e.g. "Adult", "Child"). */
359 'aria-label' => sprintf(__('Decrease %s', 'yatra'), $category_label),
360 ],
361 'plus_attrs' => [
362 'data-target' => $input_id,
363 /* translators: %s: traveler category label (e.g. "Adult", "Child"). */
364 'aria-label' => sprintf(__('Increase %s', 'yatra'), $category_label),
365 ],
366 'input_attrs' => [
367 'id' => $input_id,
368 'name' => 'travelers[' . $category_id . ']',
369 'value' => $count,
370 'min' => 0,
371 'max' => $trip->max_travelers ?: 20,
372 'data-category-label' => $category_label,
373 'data-price' => $category_price,
374 'data-pricing-mode' => $pricing_mode,
375 ],
376 ];
377 }
378 $traveler_display_text = !empty($traveler_rows) ? ($traveler_rows[0]['label'] . ' x ' . max(1, (int) $traveler_rows[0]['input_attrs']['value'])) : __('Traveler x 1', 'yatra');
379 $root_id = '';
380 $root_class = 'yatra-booking-field-select yatra-participants-select';
381 $container_attrs = [];
382 $display_id = 'participants-display';
383 $display_class = 'yatra-participants-display';
384 $display_attrs = [];
385 $dropdown_id = 'quantity-selector';
386 $dropdown_class = 'yatra-booking-quantity-selector';
387 $dropdown_attrs = [];
388 $icon_html = yatra_svg_icon('users', 'yatra-icon-sm');
389 $rows = $traveler_rows;
390 include YATRA_PLUGIN_PATH . 'templates/partials/traveler-selector.php';
391 ?>
392 </div>
393 <?php else: ?>
394 <!-- Regular pricing: Read-only display -->
395 <div class="yatra-summary-form-group">
396 <label for="number-of-travelers"><?php esc_html_e('Travelers', 'yatra'); ?></label>
397 <div class="yatra-summary-readonly-field">
398 <?php echo esc_html($total_travelers . ' ' . _n('traveler', 'travelers', $total_travelers, 'yatra')); ?>
399 </div>
400 <input type="hidden" id="number-of-travelers" name="number_of_travelers" form="yatra-booking-form" value="<?php echo esc_attr($total_travelers); ?>">
401 </div>
402 <?php endif; ?>
403 </div>
404
405 <!-- Trip Info -->
406 <div class="yatra-summary-trip">
407 <?php $trip_permalink = function_exists('yatra_get_trip_permalink') ? yatra_get_trip_permalink($trip) : ''; ?>
408 <div class="yatra-summary-image">
409 <?php if (!empty($trip_permalink)) : ?>
410 <a href="<?php echo esc_url($trip_permalink ?? ''); ?>">
411 <?php endif; ?>
412 <img src="<?php echo esc_url($trip->featured_image ?? ''); ?>" alt="<?php echo esc_attr($trip->title ?? ''); ?>">
413 <?php if (!empty($trip_permalink)) : ?>
414 </a>
415 <?php endif; ?>
416 </div>
417 <div class="yatra-summary-details">
418 <?php if (!empty($trip_permalink)) : ?>
419 <h4><a href="<?php echo esc_url($trip_permalink ?? ''); ?>"><?php echo esc_html($trip->title ?? ''); ?></a></h4>
420 <?php else : ?>
421 <h4><?php echo esc_html($trip->title); ?></h4>
422 <?php endif; ?>
423 <div class="yatra-summary-meta">
424 <?php
425 $duration_days = (int) ($trip->duration_days ?? 0);
426 $duration_nights = isset($trip->duration_nights) ? (int) $trip->duration_nights : null;
427 ?>
428 <?php if ($duration_days > 0) : ?>
429 <?php if ($duration_days <= 1) : ?>
430 <span><?php esc_html_e('Day Trip', 'yatra'); ?></span>
431 <?php else : ?>
432 <span><?php echo esc_html(yatra_format_duration($duration_days, $duration_nights)); ?></span>
433 <?php endif; ?>
434 <?php endif; ?>
435 <?php
436 // Resolve difficulty_level to a human label.
437 // The trip row stores difficulty_level as the
438 // classification FK id (e.g. 32), and that's
439 // what we'd otherwise render — "32" on the
440 // sidebar. Prefer the joined `difficulty_name`
441 // when present; fall back to a one-row lookup
442 // in wp_yatra_classifications when only the
443 // id arrived; finally fall back to the raw
444 // string (legacy trips that stored
445 // 'easy'/'moderate' before the FK migration).
446 $difficulty_label = '';
447 $difficulty_raw = $trip->difficulty_level ?? null;
448 if (!empty($trip->difficulty_name)) {
449 $difficulty_label = (string) $trip->difficulty_name;
450 } elseif (is_numeric($difficulty_raw) && (int) $difficulty_raw > 0) {
451 global $wpdb;
452 $classifications_table = \Yatra\Database\Tables\ClassificationsTable::getTableName();
453 $difficulty_label = (string) $wpdb->get_var($wpdb->prepare(
454 "SELECT name FROM {$classifications_table} WHERE id = %d LIMIT 1",
455 (int) $difficulty_raw
456 ));
457 } elseif (is_string($difficulty_raw) && $difficulty_raw !== '') {
458 $difficulty_label = ucfirst($difficulty_raw);
459 }
460 ?>
461 <?php if ($difficulty_label !== '') : ?>
462 <span>•</span>
463 <span><?php echo esc_html($difficulty_label); ?></span>
464 <?php endif; ?>
465 </div>
466 </div>
467 </div>
468
469
470 <!-- Trip Attributes -->
471 <?php if (!empty($trip->attributes)): ?>
472 <div class="yatra-booking-attributes">
473 <h5><?php esc_html_e('Trip Features', 'yatra'); ?></h5>
474 <div class="yatra-booking-attributes-list">
475 <?php foreach ($trip->attributes as $attribute): ?>
476 <div class="yatra-booking-attribute-item">
477 <div class="yatra-booking-attribute-name">
478 <?php echo esc_html($attribute['name']); ?>
479 </div>
480 <div class="yatra-booking-attribute-value">
481 <?php
482 // Display value based on field type (simplified for booking sidebar)
483 switch ($attribute['field_type']) {
484 case 'checkbox':
485 $cb_val = $attribute['value'] ?? null;
486 $cb_options = json_decode($attribute['field_options'] ?? '[]', true);
487 if (!is_array($cb_options)) {
488 $cb_options = [];
489 }
490 if (is_array($cb_val)) {
491 $labels = [];
492 foreach ($cb_options as $opt) {
493 if (!isset($opt['value'], $opt['label'])) {
494 continue;
495 }
496 foreach ($cb_val as $v) {
497 if ((string) $v === (string) $opt['value']) {
498 $labels[] = $opt['label'];
499 break;
500 }
501 }
502 }
503 echo esc_html($labels !== [] ? implode(', ', $labels) : '—');
504 } else {
505 echo $cb_val ? esc_html__('Yes', 'yatra') : esc_html__('No', 'yatra');
506 }
507 break;
508 case 'select':
509 case 'radio':
510 $options = json_decode($attribute['field_options'] ?? '[]', true);
511 if (!is_array($options)) {
512 $options = [];
513 }
514 $selected_value = is_array($attribute['value']) ? ($attribute['value'][0] ?? null) : ($attribute['value'] ?? null);
515 $matched_label = null;
516 foreach ($options as $option) {
517 if (!is_array($option) || !isset($option['value'], $option['label'])) {
518 continue;
519 }
520 if ((string) $option['value'] === (string) $selected_value) {
521 $matched_label = (string) $option['label'];
522 break;
523 }
524 }
525 if ($matched_label !== null) {
526 echo esc_html($matched_label);
527 } elseif ($selected_value !== null && $selected_value !== '') {
528 echo esc_html((string) $selected_value);
529 } else {
530 echo esc_html('—');
531 }
532 break;
533 case 'date':
534 echo esc_html(date_i18n(get_option('date_format'), strtotime($attribute['value'])));
535 break;
536 case 'color':
537 echo '<span class="yatra-booking-color-swatch" style="background-color: ' . esc_attr($attribute['value']) . ';" title="' . esc_attr($attribute['value']) . '"></span>';
538 break;
539 case 'textarea':
540 // Truncate long text for sidebar
541 $text = wp_strip_all_tags($attribute['value']);
542 echo esc_html(substr($text, 0, 25) . (strlen($text) > 25 ? '...' : ''));
543 break;
544 case 'number':
545 echo esc_html(number_format($attribute['value'], 0));
546 break;
547 default:
548 // Truncate long text for sidebar
549 echo esc_html(substr($attribute['value'], 0, 20) . (strlen($attribute['value']) > 20 ? '...' : ''));
550 break;
551 }
552 ?>
553 </div>
554 </div>
555 <?php endforeach; ?>
556 </div>
557 </div>
558 <?php endif; ?>
559
560 <!-- Coupon Code Section
561 Server-side checks whether a coupon is already
562 applied to this booking session so the correct UI
563 (toggle-to-enter vs applied-with-remove) is rendered
564 on the FIRST byte. Without this, the page always
565 shipped with the applied-coupon block `display:none`
566 and relied on the JS `loadCouponFromSession()` GET
567 to flip the visibility — which fails on page refresh
568 in REST contexts where PHPSESSID doesn't propagate,
569 leaving the user without a remove button. -->
570 <?php
571 $applied_coupon = null;
572 if (!empty($booking) && is_object($booking)) {
573 $session_for_coupon = yatra_get_booking_session();
574 if (is_array($session_for_coupon) && !empty($session_for_coupon['coupon']['code'])) {
575 $applied_coupon = $session_for_coupon['coupon'];
576 }
577 }
578 $coupon_is_applied = !empty($applied_coupon);
579 $coupon_discount_amount_for_display = $coupon_is_applied
580 ? (float) ($applied_coupon['discount_amount'] ?? $applied_coupon['calculated_amount'] ?? 0)
581 : 0.0;
582 ?>
583 <div class="yatra-coupon-section">
584 <div class="yatra-coupon-toggle" <?php echo $coupon_is_applied ? 'style="display: none;"' : ''; ?>>
585 <button type="button" id="yatra-coupon-toggle-btn" class="yatra-coupon-toggle-btn">
586 <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
587 <path d="M20 12v6a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2v-6"></path>
588 <path d="M12 2v10"></path>
589 <path d="m4.93 10.93 1.41 1.41"></path>
590 <path d="m17.66 10.93-1.41 1.41"></path>
591 <circle cx="12" cy="2" r="2"></circle>
592 </svg>
593 <?php esc_html_e('Have a coupon code?', 'yatra'); ?>
594 <svg class="yatra-coupon-chevron" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
595 <polyline points="6 9 12 15 18 9"></polyline>
596 </svg>
597 </button>
598 </div>
599 <div class="yatra-coupon-form" id="yatra-coupon-form" style="display: none;">
600 <div class="yatra-coupon-input-group">
601 <input type="text" id="yatra-coupon-code" name="coupon_code" placeholder="<?php esc_attr_e('Enter coupon code', 'yatra'); ?>" class="yatra-coupon-input" autocomplete="off">
602 <button type="button" id="yatra-apply-coupon" class="yatra-apply-coupon-btn">
603 <?php esc_html_e('Apply', 'yatra'); ?>
604 </button>
605 </div>
606 <div id="yatra-coupon-message" class="yatra-coupon-message" style="display: none;"></div>
607 </div>
608 <!-- Applied Coupon Display — rendered visible on first paint when the
609 session already has a coupon, so the remove button is reachable
610 even before any JS runs. -->
611 <div class="yatra-applied-coupon" id="yatra-applied-coupon" <?php echo $coupon_is_applied ? '' : 'style="display: none;"'; ?>>
612 <div class="yatra-applied-coupon-info">
613 <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
614 <path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path>
615 <polyline points="22 4 12 14.01 9 11.01"></polyline>
616 </svg>
617 <span class="yatra-coupon-code-display"><?php echo $coupon_is_applied ? esc_html($applied_coupon['code']) : ''; ?></span>
618 <span class="yatra-coupon-discount"><?php echo $coupon_is_applied && $coupon_discount_amount_for_display > 0 ? '-' . esc_html(yatra_format_price($coupon_discount_amount_for_display)) : ''; ?></span>
619 </div>
620 <button type="button" id="yatra-remove-coupon" class="yatra-remove-coupon-btn" title="<?php esc_attr_e('Remove coupon', 'yatra'); ?>">
621 <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
622 <line x1="18" y1="6" x2="6" y2="18"></line>
623 <line x1="6" y1="6" x2="18" y2="18"></line>
624 </svg>
625 </button>
626 </div>
627 </div>
628
629 <!-- Additional Services Section (Premium Feature)
630 Renders as its own card BEFORE the Pricing Summary
631 (after the coupon code section). Checkbox toggles fire
632 the change handler in booking.js → updateServicesInSession()
633 → /booking/session POST → schedulePricingSummaryRefresh()
634 → /booking/summary GET → re-render the pricing-summary
635 partial below, which then shows the selected services
636 as compact line items in the price breakdown. -->
637 <?php
638 /**
639 * Filter: Get additional services for this trip
640 * Allows premium modules to add extra services to the booking
641 */
642 $additional_services = apply_filters('yatra_booking_additional_services', [], $trip_id, $total_travelers, $traveler_counts ?? [], $travel_date);
643
644 // Get selected services from session (set from popup)
645 $session = yatra_get_booking_session();
646 $selected_service_ids = isset($session['additional_services']) && is_array($session['additional_services'])
647 ? array_map('intval', $session['additional_services'])
648 : [];
649
650 if (!empty($additional_services)) :
651 ?>
652 <div class="yatra-additional-services-section">
653 <div class="yatra-services-header">
654 <h4><?php esc_html_e('Additional Services', 'yatra'); ?></h4>
655 <p class="yatra-services-subtitle"><?php esc_html_e('Enhance your trip with these optional add-ons', 'yatra'); ?></p>
656 </div>
657 <div class="yatra-services-list" id="yatra-additional-services">
658 <?php foreach ($additional_services as $service) :
659 $service_id = (int) $service['id'];
660 $service_name = $service['name'];
661 $service_description = (string) ($service['description'] ?? '');
662 $service_price = (float) $service['price'];
663 $service_price_per = $service['price_per'] ?? 'person';
664 $is_required = isset($service['is_required']) && ($service['is_required'] === true || $service['is_required'] === 1 || $service['is_required'] === '1');
665 $is_included = isset($service['is_included']) && ($service['is_included'] === true || $service['is_included'] === 1 || $service['is_included'] === '1');
666 $is_selected = in_array($service_id, $selected_service_ids, false);
667
668 $price_label = yatra_format_price($service_price);
669 if ($service_price_per === 'person') {
670 $price_label .= ' ' . __('per person', 'yatra');
671 } elseif ($service_price_per === 'day') {
672 $price_label .= ' ' . __('per day', 'yatra');
673 } else {
674 $price_label .= ' ' . __('per booking', 'yatra');
675 }
676 if ($is_included) {
677 $price_label = __('Included', 'yatra');
678 }
679
680 $is_checked = $is_required || $is_included || $is_selected;
681 $is_disabled = $is_required || $is_included;
682 ?>
683 <?php
684 // Description shown as a hover tooltip on the
685 // whole row (no separate ⓘ icon). `title` lives
686 // on the outer .yatra-service-item so hovering
687 // anywhere in the block reveals the tooltip —
688 // the small icon variant was easy to miss.
689 $plain_description = $service_description !== '' ? wp_strip_all_tags($service_description) : '';
690 ?>
691 <div class="yatra-service-item <?php echo $is_required ? 'yatra-service-required' : ''; ?> <?php echo $is_checked ? 'yatra-service-selected' : ''; ?>"
692 data-service-id="<?php echo esc_attr((string) $service_id); ?>"
693 data-service-price="<?php echo esc_attr((string) $service_price); ?>"
694 data-service-price-per="<?php echo esc_attr($service_price_per); ?>"
695 data-is-required="<?php echo $is_required ? '1' : '0'; ?>"
696 <?php if ($plain_description !== '') : ?>
697 data-tooltip="<?php echo esc_attr($plain_description); ?>"
698 aria-label="<?php echo esc_attr($plain_description); ?>"
699 <?php endif; ?>>
700 <label class="yatra-service-label">
701 <input type="checkbox"
702 name="additional_services[]"
703 value="<?php echo esc_attr((string) $service_id); ?>"
704 <?php echo $is_checked ? 'checked' : ''; ?>
705 <?php echo $is_disabled ? 'disabled' : ''; ?>
706 form="yatra-booking-form">
707 <div class="yatra-service-info">
708 <span class="yatra-service-name"><?php echo esc_html($service_name); ?></span>
709 </div>
710 <span class="yatra-service-price"><?php echo esc_html($price_label); ?></span>
711 </label>
712 </div>
713 <?php endforeach; ?>
714 </div>
715 </div>
716 <?php endif; ?>
717
718 <!-- Price Breakdown — services + dynamic-pricing summary + totals all live
719 in one card so the sidebar reads as a single "Pricing Summary" panel. -->
720 <div class="yatra-summary-pricing" id="yatra-summary-pricing" data-pricing-type="<?php echo esc_attr($pricing_type); ?>" data-is-remaining="<?php echo esc_attr($is_remaining_payment ? 'yes' : 'no'); ?>">
721 <?php
722 // Get checkout model from booking object
723 $checkout = $booking->checkout ?? null;
724
725 // Surface the dynamic-pricing breakdown as a template
726 // variable so the partial can render the DP line items.
727 // CalculationService stuffs the breakdown into
728 // `pricing_calculation.dynamic_pricing` via the
729 // `yatra_price_breakdown` filter; without exposing it
730 // here the partial's DP block stays dark on first load.
731 $dynamic_pricing = $pricing_calculation['dynamic_pricing'] ?? null;
732 $currency = $pricing_calculation['currency'] ?? null;
733
734 // The pricing-summary partial calls $render_additional_services()
735 // at the right spot — right after the traveler/per-person rows
736 // and before the Trip Subtotal — so the services row shares the
737 // same compact "label … price" layout as the traveler row.
738
739 // Include pricing summary template (uses $checkout model)
740 include YATRA_PLUGIN_PATH . 'templates/partials/pricing-summary.php';
741 ?>
742 </div>
743
744 <!-- Cancellation Policy -->
745 <?php if (!empty($trip->cancellation_policy)): ?>
746 <div class="yatra-summary-info yatra-cancellation-policy">
747 <h4><?php esc_html_e('Cancellation Policy', 'yatra'); ?></h4>
748 <div class="yatra-cancellation-policy-content">
749 <?php
750 // Process and display the cancellation policy as a paragraph
751 $policy_text = wp_strip_all_tags($trip->cancellation_policy);
752
753 // Truncate if too long for booking summary
754 $max_length = 200;
755 if (strlen($policy_text) > $max_length) {
756 $truncated_text = substr($policy_text, 0, $max_length);
757 // Find last complete sentence within truncation
758 $last_period = strrpos($truncated_text, '.');
759 $last_exclamation = strrpos($truncated_text, '!');
760 $last_question = strrpos($truncated_text, '?');
761
762 $last_sentence_end = max($last_period, $last_exclamation, $last_question);
763
764 if ($last_sentence_end > $max_length * 0.7) {
765 // If we have a complete sentence, end there
766 $display_text = substr($truncated_text, 0, $last_sentence_end + 1);
767 } else {
768 // Otherwise, truncate at word boundary
769 $display_text = substr($truncated_text, 0, strrpos($truncated_text, ' ')) . '...';
770 }
771 } else {
772 $display_text = $policy_text;
773 }
774
775 echo '<p>' . esc_html($display_text) . '</p>';
776
777 // Show "more" indicator if truncated
778 if (strlen($policy_text) > $max_length) {
779 echo '<p class="yatra-policy-more">';
780 echo esc_html__('Full policy available on trip details page', 'yatra');
781 echo '</p>';
782 }
783 ?>
784 </div>
785 </div>
786 <?php endif; ?>
787
788 <!-- Booking Assurance -->
789 <div class="yatra-summary-info">
790 <h4><?php esc_html_e('Booking Assurance', 'yatra'); ?></h4>
791 <ul>
792 <li>
793 <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="display: inline-block; vertical-align: middle; margin-right: 6px;">
794 <path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path>
795 <polyline points="22 4 12 14.01 9 11.01"></polyline>
796 </svg>
797 <?php esc_html_e('Instant confirmation', 'yatra'); ?>
798 </li>
799 <li>
800 <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="display: inline-block; vertical-align: middle; margin-right: 6px;">
801 <path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z"></path>
802 </svg>
803 <?php esc_html_e('24/7 customer support', 'yatra'); ?>
804 </li>
805 <li>
806 <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="display: inline-block; vertical-align: middle; margin-right: 6px;">
807 <rect x="1" y="4" width="22" height="16" rx="2" ry="2"></rect>
808 <line x1="1" y1="10" x2="23" y2="10"></line>
809 </svg>
810 <?php esc_html_e('Secure payment processing', 'yatra'); ?>
811 </li>
812 </ul>
813 </div>
814
815 <!-- Selected Gateway Info -->
816 <div class="yatra-gateway-info" id="yatra-gateway-info" style="display: none;">
817 <h4><?php esc_html_e('Payment Information', 'yatra'); ?></h4>
818 <div id="yatra-gateway-details"></div>
819 </div>
820 </div>
821 <?php endif; ?>
822 </div>
823 </div>
824 </div>
825 </div>
826
827