PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.11
Yatra – Travel Booking & Tour Operator Software v3.0.11
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.11, at templates/partials/booking-content.php

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