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-content.php

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

846 lines 55.7 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 'data-group-overflow' => $pt->group_overflow ?? 'block',
354 'data-max-pax' => $pt->max_pax ?? '',
355 ],
356 'minus_disabled' => ($index !== 0 && $count <= 0),
357 'plus_disabled' => false,
358 'minus_attrs' => [
359 'data-target' => $input_id,
360 /* translators: %s: traveler category label (e.g. "Adult", "Child"). */
361 'aria-label' => sprintf(__('Decrease %s', 'yatra'), $category_label),
362 ],
363 'plus_attrs' => [
364 'data-target' => $input_id,
365 /* translators: %s: traveler category label (e.g. "Adult", "Child"). */
366 'aria-label' => sprintf(__('Increase %s', 'yatra'), $category_label),
367 ],
368 'input_attrs' => [
369 'id' => $input_id,
370 'name' => 'travelers[' . $category_id . ']',
371 'value' => $count,
372 'min' => 0,
373 'max' => $trip->max_travelers ?: 20,
374 'data-category-label' => $category_label,
375 'data-price' => $category_price,
376 'data-pricing-mode' => $pricing_mode,
377 'data-group-overflow' => $pt->group_overflow ?? 'block',
378 'data-max-pax' => $pt->max_pax ?? '',
379 ],
380 ];
381 }
382 $traveler_display_text = !empty($traveler_rows) ? ($traveler_rows[0]['label'] . ' x ' . max(1, (int) $traveler_rows[0]['input_attrs']['value'])) : __('Traveler x 1', 'yatra');
383 $root_id = '';
384 $root_class = 'yatra-booking-field-select yatra-participants-select';
385 $container_attrs = [];
386 $display_id = 'participants-display';
387 $display_class = 'yatra-participants-display';
388 $display_attrs = [];
389 $dropdown_id = 'quantity-selector';
390 $dropdown_class = 'yatra-booking-quantity-selector';
391 $dropdown_attrs = [];
392 $icon_html = yatra_svg_icon('users', 'yatra-icon-sm');
393 $rows = $traveler_rows;
394 include YATRA_PLUGIN_PATH . 'templates/partials/traveler-selector.php';
395 ?>
396 </div>
397 <?php else: ?>
398 <!-- Regular pricing: Read-only display -->
399 <div class="yatra-summary-form-group">
400 <label for="number-of-travelers"><?php esc_html_e('Travelers', 'yatra'); ?></label>
401 <div class="yatra-summary-readonly-field">
402 <?php echo esc_html($total_travelers . ' ' . _n('traveler', 'travelers', $total_travelers, 'yatra')); ?>
403 </div>
404 <input type="hidden" id="number-of-travelers" name="number_of_travelers" form="yatra-booking-form" value="<?php echo esc_attr($total_travelers); ?>">
405 </div>
406 <?php endif; ?>
407 </div>
408
409 <!-- Trip Info -->
410 <div class="yatra-summary-trip">
411 <?php $trip_permalink = function_exists('yatra_get_trip_permalink') ? yatra_get_trip_permalink($trip) : ''; ?>
412 <div class="yatra-summary-image">
413 <?php if (!empty($trip_permalink)) : ?>
414 <a href="<?php echo esc_url($trip_permalink ?? ''); ?>">
415 <?php endif; ?>
416 <img src="<?php echo esc_url($trip->featured_image ?? ''); ?>" alt="<?php echo esc_attr($trip->title ?? ''); ?>">
417 <?php if (!empty($trip_permalink)) : ?>
418 </a>
419 <?php endif; ?>
420 </div>
421 <div class="yatra-summary-details">
422 <?php if (!empty($trip_permalink)) : ?>
423 <h4><a href="<?php echo esc_url($trip_permalink ?? ''); ?>"><?php echo esc_html($trip->title ?? ''); ?></a></h4>
424 <?php else : ?>
425 <h4><?php echo esc_html($trip->title); ?></h4>
426 <?php endif; ?>
427 <div class="yatra-summary-meta">
428 <?php
429 $duration_days = (int) ($trip->duration_days ?? 0);
430 $duration_nights = isset($trip->duration_nights) ? (int) $trip->duration_nights : null;
431 $duration_hours = (int) ($trip->duration_hours ?? 0);
432 ?>
433 <?php if ($duration_hours > 0) : ?>
434 <span><?php echo esc_html(yatra_format_duration(0, null, $duration_hours)); ?></span>
435 <?php elseif ($duration_days > 0) : ?>
436 <?php if ($duration_days <= 1) : ?>
437 <span><?php esc_html_e('Day Trip', 'yatra'); ?></span>
438 <?php else : ?>
439 <span><?php echo esc_html(yatra_format_duration($duration_days, $duration_nights)); ?></span>
440 <?php endif; ?>
441 <?php endif; ?>
442 <?php
443 // Resolve difficulty_level to a human label.
444 // The trip row stores difficulty_level as the
445 // classification FK id (e.g. 32), and that's
446 // what we'd otherwise render — "32" on the
447 // sidebar. Prefer the joined `difficulty_name`
448 // when present; fall back to a one-row lookup
449 // in wp_yatra_classifications when only the
450 // id arrived; finally fall back to the raw
451 // string (legacy trips that stored
452 // 'easy'/'moderate' before the FK migration).
453 $difficulty_label = '';
454 $difficulty_raw = $trip->difficulty_level ?? null;
455 if (!empty($trip->difficulty_name)) {
456 $difficulty_label = (string) $trip->difficulty_name;
457 } elseif (is_numeric($difficulty_raw) && (int) $difficulty_raw > 0) {
458 global $wpdb;
459 $classifications_table = \Yatra\Database\Tables\ClassificationsTable::getTableName();
460 $difficulty_label = (string) $wpdb->get_var($wpdb->prepare(
461 "SELECT name FROM {$classifications_table} WHERE id = %d LIMIT 1",
462 (int) $difficulty_raw
463 ));
464 } elseif (is_string($difficulty_raw) && $difficulty_raw !== '') {
465 $difficulty_label = ucfirst($difficulty_raw);
466 }
467 ?>
468 <?php if ($difficulty_label !== '') : ?>
469 <span></span>
470 <span><?php echo esc_html($difficulty_label); ?></span>
471 <?php endif; ?>
472 </div>
473 </div>
474 </div>
475
476
477 <!-- Trip Attributes -->
478 <?php if (!empty($trip->attributes)): ?>
479 <div class="yatra-booking-attributes">
480 <h5><?php esc_html_e('Trip Features', 'yatra'); ?></h5>
481 <div class="yatra-booking-attributes-list">
482 <?php foreach ($trip->attributes as $attribute): ?>
483 <div class="yatra-booking-attribute-item">
484 <div class="yatra-booking-attribute-name">
485 <?php echo esc_html($attribute['name']); ?>
486 </div>
487 <div class="yatra-booking-attribute-value">
488 <?php
489 // Display value based on field type (simplified for booking sidebar)
490 switch ($attribute['field_type']) {
491 case 'checkbox':
492 $cb_val = $attribute['value'] ?? null;
493 $cb_options = json_decode($attribute['field_options'] ?? '[]', true);
494 if (!is_array($cb_options)) {
495 $cb_options = [];
496 }
497 if (is_array($cb_val)) {
498 $labels = [];
499 foreach ($cb_options as $opt) {
500 if (!isset($opt['value'], $opt['label'])) {
501 continue;
502 }
503 foreach ($cb_val as $v) {
504 if ((string) $v === (string) $opt['value']) {
505 $labels[] = $opt['label'];
506 break;
507 }
508 }
509 }
510 echo esc_html($labels !== [] ? implode(', ', $labels) : '');
511 } else {
512 echo $cb_val ? esc_html__('Yes', 'yatra') : esc_html__('No', 'yatra');
513 }
514 break;
515 case 'select':
516 case 'radio':
517 $options = json_decode($attribute['field_options'] ?? '[]', true);
518 if (!is_array($options)) {
519 $options = [];
520 }
521 $selected_value = is_array($attribute['value']) ? ($attribute['value'][0] ?? null) : ($attribute['value'] ?? null);
522 $matched_label = null;
523 foreach ($options as $option) {
524 if (!is_array($option) || !isset($option['value'], $option['label'])) {
525 continue;
526 }
527 if ((string) $option['value'] === (string) $selected_value) {
528 $matched_label = (string) $option['label'];
529 break;
530 }
531 }
532 if ($matched_label !== null) {
533 echo esc_html($matched_label);
534 } elseif ($selected_value !== null && $selected_value !== '') {
535 echo esc_html((string) $selected_value);
536 } else {
537 echo esc_html('');
538 }
539 break;
540 case 'date':
541 echo esc_html(date_i18n(get_option('date_format'), strtotime($attribute['value'])));
542 break;
543 case 'color':
544 echo '<span class="yatra-booking-color-swatch" style="background-color: ' . esc_attr($attribute['value']) . ';" title="' . esc_attr($attribute['value']) . '"></span>';
545 break;
546 case 'textarea':
547 // Truncate long text for sidebar
548 $text = wp_strip_all_tags($attribute['value']);
549 echo esc_html(substr($text, 0, 25) . (strlen($text) > 25 ? '...' : ''));
550 break;
551 case 'number':
552 echo esc_html(number_format($attribute['value'], 0));
553 break;
554 default:
555 // Truncate long text for sidebar
556 echo esc_html(substr($attribute['value'], 0, 20) . (strlen($attribute['value']) > 20 ? '...' : ''));
557 break;
558 }
559 ?>
560 </div>
561 </div>
562 <?php endforeach; ?>
563 </div>
564 </div>
565 <?php endif; ?>
566
567 <!-- Coupon Code Section
568 Server-side checks whether a coupon is already
569 applied to this booking session so the correct UI
570 (toggle-to-enter vs applied-with-remove) is rendered
571 on the FIRST byte. Without this, the page always
572 shipped with the applied-coupon block `display:none`
573 and relied on the JS `loadCouponFromSession()` GET
574 to flip the visibility which fails on page refresh
575 in REST contexts where PHPSESSID doesn't propagate,
576 leaving the user without a remove button. -->
577 <?php
578 $applied_coupon = null;
579 if (!empty($booking) && is_object($booking)) {
580 $session_for_coupon = yatra_get_booking_session();
581 if (is_array($session_for_coupon) && !empty($session_for_coupon['coupon']['code'])) {
582 $applied_coupon = $session_for_coupon['coupon'];
583 }
584 }
585 $coupon_is_applied = !empty($applied_coupon);
586 $coupon_discount_amount_for_display = $coupon_is_applied
587 ? (float) ($applied_coupon['discount_amount'] ?? $applied_coupon['calculated_amount'] ?? 0)
588 : 0.0;
589 ?>
590 <div class="yatra-coupon-section">
591 <div class="yatra-coupon-toggle" <?php echo $coupon_is_applied ? 'style="display: none;"' : ''; ?>>
592 <button type="button" id="yatra-coupon-toggle-btn" class="yatra-coupon-toggle-btn">
593 <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
594 <path d="M20 12v6a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2v-6"></path>
595 <path d="M12 2v10"></path>
596 <path d="m4.93 10.93 1.41 1.41"></path>
597 <path d="m17.66 10.93-1.41 1.41"></path>
598 <circle cx="12" cy="2" r="2"></circle>
599 </svg>
600 <?php esc_html_e('Have a coupon code?', 'yatra'); ?>
601 <svg class="yatra-coupon-chevron" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
602 <polyline points="6 9 12 15 18 9"></polyline>
603 </svg>
604 </button>
605 </div>
606 <div class="yatra-coupon-form" id="yatra-coupon-form" style="display: none;">
607 <div class="yatra-coupon-input-group">
608 <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">
609 <button type="button" id="yatra-apply-coupon" class="yatra-apply-coupon-btn">
610 <?php esc_html_e('Apply', 'yatra'); ?>
611 </button>
612 </div>
613 <div id="yatra-coupon-message" class="yatra-coupon-message" style="display: none;"></div>
614 </div>
615 <!-- Applied Coupon Display — rendered visible on first paint when the
616 session already has a coupon, so the remove button is reachable
617 even before any JS runs. -->
618 <div class="yatra-applied-coupon" id="yatra-applied-coupon" <?php echo $coupon_is_applied ? '' : 'style="display: none;"'; ?>>
619 <div class="yatra-applied-coupon-info">
620 <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
621 <path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path>
622 <polyline points="22 4 12 14.01 9 11.01"></polyline>
623 </svg>
624 <span class="yatra-coupon-code-display"><?php echo $coupon_is_applied ? esc_html($applied_coupon['code']) : ''; ?></span>
625 <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>
626 </div>
627 <button type="button" id="yatra-remove-coupon" class="yatra-remove-coupon-btn" title="<?php esc_attr_e('Remove coupon', 'yatra'); ?>">
628 <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
629 <line x1="18" y1="6" x2="6" y2="18"></line>
630 <line x1="6" y1="6" x2="18" y2="18"></line>
631 </svg>
632 </button>
633 </div>
634 </div>
635
636 <!-- Additional Services Section (Premium Feature)
637 Renders as its own card BEFORE the Pricing Summary
638 (after the coupon code section). Checkbox toggles fire
639 the change handler in booking.js → updateServicesInSession()
640 → /booking/session POST → schedulePricingSummaryRefresh()
641 → /booking/summary GET → re-render the pricing-summary
642 partial below, which then shows the selected services
643 as compact line items in the price breakdown. -->
644 <?php
645 /**
646 * Filter: Get additional services for this trip
647 * Allows premium modules to add extra services to the booking
648 */
649 $additional_services = apply_filters('yatra_booking_additional_services', [], $trip_id, $total_travelers, $traveler_counts ?? [], $travel_date);
650
651 // Get selected services from session (set from popup)
652 $session = yatra_get_booking_session();
653 $selected_service_ids = isset($session['additional_services']) && is_array($session['additional_services'])
654 ? array_map('intval', $session['additional_services'])
655 : [];
656
657 if (!empty($additional_services)) :
658 ?>
659 <div class="yatra-additional-services-section">
660 <div class="yatra-services-header">
661 <h4><?php esc_html_e('Additional Services', 'yatra'); ?></h4>
662 <p class="yatra-services-subtitle"><?php esc_html_e('Enhance your trip with these optional add-ons', 'yatra'); ?></p>
663 </div>
664 <div class="yatra-services-list" id="yatra-additional-services">
665 <?php foreach ($additional_services as $service) :
666 $service_id = (int) $service['id'];
667 $service_name = $service['name'];
668 $service_description = (string) ($service['description'] ?? '');
669 $service_price = (float) $service['price'];
670 $service_price_per = $service['price_per'] ?? 'person';
671 $service_price_type = $service['price_type'] ?? 'fixed';
672 $is_required = isset($service['is_required']) && ($service['is_required'] === true || $service['is_required'] === 1 || $service['is_required'] === '1');
673 $is_included = isset($service['is_included']) && ($service['is_included'] === true || $service['is_included'] === 1 || $service['is_included'] === '1');
674 $is_selected = in_array($service_id, $selected_service_ids, false);
675
676 // Percentage services show "X% of trip price" (the actual
677 // dollar amount is rendered in the price-breakdown summary).
678 // Price Per does NOT apply to percentage (it's a % of the
679 // whole trip), so no per-person/day/booking suffix is added.
680 // Fixed services show the formatted amount plus the suffix.
681 if ($service_price_type === 'percentage') {
682 $pct_display = rtrim(rtrim(number_format($service_price, 2), '0'), '.');
683 /* translators: %s: percentage value, e.g. "17" */
684 $price_label = sprintf(__('%s%% of trip price', 'yatra'), $pct_display);
685 } else {
686 $price_label = yatra_format_price($service_price);
687 if ($service_price_per === 'person') {
688 $price_label .= ' ' . __('per person', 'yatra');
689 } elseif ($service_price_per === 'day') {
690 $price_label .= ' ' . __('per day', 'yatra');
691 } else {
692 $price_label .= ' ' . __('per booking', 'yatra');
693 }
694 }
695 if ($is_included) {
696 $price_label = __('Included', 'yatra');
697 }
698
699 $is_checked = $is_required || $is_included || $is_selected;
700 $is_disabled = $is_required || $is_included;
701 ?>
702 <?php
703 // Description shown as a hover tooltip on the
704 // whole row (no separate ⓘ icon). `title` lives
705 // on the outer .yatra-service-item so hovering
706 // anywhere in the block reveals the tooltip —
707 // the small icon variant was easy to miss.
708 $plain_description = $service_description !== '' ? wp_strip_all_tags($service_description) : '';
709 ?>
710 <div class="yatra-service-item <?php echo $is_required ? 'yatra-service-required' : ''; ?> <?php echo $is_checked ? 'yatra-service-selected' : ''; ?>"
711 data-service-id="<?php echo esc_attr((string) $service_id); ?>"
712 data-service-price="<?php echo esc_attr((string) $service_price); ?>"
713 data-service-price-per="<?php echo esc_attr($service_price_per); ?>"
714 data-is-required="<?php echo $is_required ? '1' : '0'; ?>"
715 <?php if ($plain_description !== '') : ?>
716 data-tooltip="<?php echo esc_attr($plain_description); ?>"
717 aria-label="<?php echo esc_attr($plain_description); ?>"
718 <?php endif; ?>>
719 <label class="yatra-service-label">
720 <input type="checkbox"
721 name="additional_services[]"
722 value="<?php echo esc_attr((string) $service_id); ?>"
723 <?php echo $is_checked ? 'checked' : ''; ?>
724 <?php echo $is_disabled ? 'disabled' : ''; ?>
725 form="yatra-booking-form">
726 <div class="yatra-service-info">
727 <span class="yatra-service-name"><?php echo esc_html($service_name); ?></span>
728 </div>
729 <span class="yatra-service-price"><?php echo esc_html($price_label); ?></span>
730 </label>
731 </div>
732 <?php endforeach; ?>
733 </div>
734 </div>
735 <?php endif; ?>
736
737 <!-- Price Breakdown services + dynamic-pricing summary + totals all live
738 in one card so the sidebar reads as a single "Pricing Summary" panel. -->
739 <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'); ?>">
740 <?php
741 // Get checkout model from booking object
742 $checkout = $booking->checkout ?? null;
743
744 // Surface the dynamic-pricing breakdown as a template
745 // variable so the partial can render the DP line items.
746 // CalculationService stuffs the breakdown into
747 // `pricing_calculation.dynamic_pricing` via the
748 // `yatra_price_breakdown` filter; without exposing it
749 // here the partial's DP block stays dark on first load.
750 $dynamic_pricing = $pricing_calculation['dynamic_pricing'] ?? null;
751 $currency = $pricing_calculation['currency'] ?? null;
752
753 // The pricing-summary partial calls $render_additional_services()
754 // at the right spot — right after the traveler/per-person rows
755 // and before the Trip Subtotal — so the services row shares the
756 // same compact "label … price" layout as the traveler row.
757
758 // Include pricing summary template (uses $checkout model)
759 include YATRA_PLUGIN_PATH . 'templates/partials/pricing-summary.php';
760 ?>
761 </div>
762
763 <!-- Cancellation Policy -->
764 <?php if (!empty($trip->cancellation_policy)): ?>
765 <div class="yatra-summary-info yatra-cancellation-policy">
766 <h4><?php esc_html_e('Cancellation Policy', 'yatra'); ?></h4>
767 <div class="yatra-cancellation-policy-content">
768 <?php
769 // Process and display the cancellation policy as a paragraph
770 $policy_text = wp_strip_all_tags($trip->cancellation_policy);
771
772 // Truncate if too long for booking summary
773 $max_length = 200;
774 if (strlen($policy_text) > $max_length) {
775 $truncated_text = substr($policy_text, 0, $max_length);
776 // Find last complete sentence within truncation
777 $last_period = strrpos($truncated_text, '.');
778 $last_exclamation = strrpos($truncated_text, '!');
779 $last_question = strrpos($truncated_text, '?');
780
781 $last_sentence_end = max($last_period, $last_exclamation, $last_question);
782
783 if ($last_sentence_end > $max_length * 0.7) {
784 // If we have a complete sentence, end there
785 $display_text = substr($truncated_text, 0, $last_sentence_end + 1);
786 } else {
787 // Otherwise, truncate at word boundary
788 $display_text = substr($truncated_text, 0, strrpos($truncated_text, ' ')) . '...';
789 }
790 } else {
791 $display_text = $policy_text;
792 }
793
794 echo '<p>' . esc_html($display_text) . '</p>';
795
796 // Show "more" indicator if truncated
797 if (strlen($policy_text) > $max_length) {
798 echo '<p class="yatra-policy-more">';
799 echo esc_html__('Full policy available on trip details page', 'yatra');
800 echo '</p>';
801 }
802 ?>
803 </div>
804 </div>
805 <?php endif; ?>
806
807 <!-- Booking Assurance -->
808 <div class="yatra-summary-info">
809 <h4><?php esc_html_e('Booking Assurance', 'yatra'); ?></h4>
810 <ul>
811 <li>
812 <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;">
813 <path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path>
814 <polyline points="22 4 12 14.01 9 11.01"></polyline>
815 </svg>
816 <?php esc_html_e('Instant confirmation', 'yatra'); ?>
817 </li>
818 <li>
819 <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;">
820 <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>
821 </svg>
822 <?php esc_html_e('24/7 customer support', 'yatra'); ?>
823 </li>
824 <li>
825 <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;">
826 <rect x="1" y="4" width="22" height="16" rx="2" ry="2"></rect>
827 <line x1="1" y1="10" x2="23" y2="10"></line>
828 </svg>
829 <?php esc_html_e('Secure payment processing', 'yatra'); ?>
830 </li>
831 </ul>
832 </div>
833
834 <!-- Selected Gateway Info -->
835 <div class="yatra-gateway-info" id="yatra-gateway-info" style="display: none;">
836 <h4><?php esc_html_e('Payment Information', 'yatra'); ?></h4>
837 <div id="yatra-gateway-details"></div>
838 </div>
839 </div>
840 <?php endif; ?>
841 </div>
842 </div>
843 </div>
844 </div>
845
846