PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.2.8
Yatra – Travel Booking & Tour Operator Software v3.0.2.8
3.0.16 3.0.15 3.0.14 3.0.14.1 3.0.14.2 3.0.12 3.0.13 3.0.11 3.0.10 3.0.9 3.0.8 3.0.7 3.0.6 3.0.5 3.0.5.1 3.0.4 3.0.3 3.0.2.9 3.0.2.7 3.0.2.8 3.0.2.6 trunk 1.0.0 2.0.0 2.0.1 All 84 releases
yatra / templates / partials / single-trip / content-sidebar.php

content-sidebar.php in Yatra – Travel Booking & Tour Operator Software 3.0.2.8, at templates/partials/single-trip/content-sidebar.php

462 lines 25.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 if (!defined('ABSPATH')) {
3 exit;
4 }
5 ?>
6 <aside class="yatra-trip-sidebar" id="booking">
7 <?php
8 // Determine if this is a multi-day trip
9 $is_multi_day = ($trip->getDurationDays() ?? 1) > 1;
10
11 // Check for group discounts availability early for the badge (premium feature)
12 $sidebar_has_group_discounts = false;
13 $sidebar_group_discounts_data = [];
14 $sidebar_group_discount_summary = '';
15 try {
16 // Check if group discounts method exists (premium feature)
17 if (class_exists('\Yatra\Services\DiscountService') &&
18 method_exists('\Yatra\Services\DiscountService', 'getGroupDiscountsForTrip')) {
19 $discountService = new \Yatra\Services\DiscountService();
20 $groupDiscountsResult = $discountService->getGroupDiscountsForTrip((int) $trip->getId());
21
22
23 if (!empty($groupDiscountsResult)) {
24 $sidebar_has_group_discounts = true;
25 $all_ranges = [];
26
27 // Process ALL group discount rules
28 foreach ($groupDiscountsResult as $discount) {
29 $ranges = [];
30
31 // Try different field names where ranges might be stored
32 $possible_fields = ['group_discount_ranges', 'ranges', 'discount_ranges'];
33 foreach ($possible_fields as $field) {
34 if (!empty($discount->$field)) {
35 if (is_string($discount->$field)) {
36 $ranges = json_decode($discount->$field, true);
37 } elseif (is_array($discount->$field)) {
38 $ranges = $discount->$field;
39 }
40 break;
41 }
42 }
43
44 // If no ranges found, create a basic structure from other fields
45 if (empty($ranges)) {
46 $ranges = [];
47 // Check if it's a simple discount with min/max fields
48 $min_size = !empty($discount->min_group_size) ? (int) $discount->min_group_size : 0;
49 $max_size = !empty($discount->max_group_size) ? (int) $discount->max_group_size : null;
50
51 // Handle different field names for discount value and type
52 $discount_value = 0;
53 $discount_type = 'percentage';
54
55 if (!empty($discount->amount)) {
56 $discount_value = (float) $discount->amount;
57 } elseif (!empty($discount->discount_amount)) {
58 $discount_value = (float) $discount->discount_amount;
59 }
60
61 if (!empty($discount->type)) {
62 $discount_type = $discount->type;
63 } elseif (!empty($discount->discount_type)) {
64 $discount_type = $discount->discount_type;
65 }
66
67 // If no min_size is set, assume it's a general group discount starting from 2 people
68 if ($min_size === 0) {
69 $min_size = 2;
70 }
71
72 if ($min_size > 0 && $discount_value > 0) {
73 $ranges[] = [
74 'min_group_size' => $min_size,
75 'max_group_size' => $max_size,
76 'discount_amount' => $discount_value,
77 'discount_type' => $discount_type,
78 'discount_percentage' => $discount_type === 'percentage' ? $discount_value : null,
79 'discount_code' => $discount->code ?? ''
80 ];
81 }
82 } else {
83 // Add discount code to existing ranges if available
84 foreach ($ranges as &$range) {
85 if (!isset($range['discount_code'])) {
86 $range['discount_code'] = $discount->code ?? '';
87 }
88 }
89 }
90
91 // Merge ranges from this discount rule
92 $all_ranges = array_merge($all_ranges, $ranges);
93 }
94
95 $sidebar_group_discounts_data = $all_ranges;
96 }
97 }
98 } catch (\Exception $e) {
99 // Silently fail if premium features are not available
100 $sidebar_has_group_discounts = false;
101 }
102
103 // Prepare availability data for JavaScript
104 $availability_json = [];
105 if ($has_availability) {
106 foreach ($trip->getAvailabilityDates() as $avail) {
107 $availability_json[] = [
108 'id' => (int) $avail->id,
109 'date' => $avail->departure_date,
110 'departure_date' => $avail->departure_date,
111 'return_date' => (isset($avail->return_date) && $avail->return_date !== '')
112 ? $avail->return_date
113 : (isset($avail->arrival_date) ? $avail->arrival_date : null),
114 'price' => $avail->effective_price ?? $avail->original_price,
115 'original_price' => $avail->original_price,
116 'discounted_price' => $avail->discounted_price,
117 'seats_available' => $avail->seats_available,
118 'seats_total' => $avail->seats_total,
119 'status' => $avail->status,
120 'is_limited' => $avail->is_limited ?? false,
121 'is_sold_out' => $avail->is_sold_out ?? false,
122 'pricing_type' => (!empty($avail->price_types) && is_array($avail->price_types)) ? 'traveler_based' : $pricing_type,
123 'price_types' => !empty($avail->price_types) ? $avail->price_types : [],
124 ];
125 }
126 }
127
128 // Pricing — resolved via centralized TripPricingService (single source of truth)
129 // SingleTripController pre-computes effective_price_min, min_category_original_price, max_discount_percentage
130 $displayPricing = \Yatra\Services\TripPricingService::resolveDisplayPricing($trip);
131
132 $pricing = [
133 'has_price' => $displayPricing['effective_price_min'] > 0,
134 'current_price' => $displayPricing['effective_price_min'] > 0 ? yatra_format_price($displayPricing['effective_price_min']) : '',
135 'original_price' => $displayPricing['has_discount'] ? yatra_format_price($displayPricing['min_category_original_price']) : '',
136 'price_prefix' => $displayPricing['price_prefix'],
137 'has_discount' => $displayPricing['has_discount'],
138 'raw_current_price' => $displayPricing['effective_price_min'],
139 'raw_original_price' => $displayPricing['min_category_original_price'],
140 'is_traveler_based' => $displayPricing['has_traveler_pricing'],
141 ];
142
143 $discount = [
144 'has_discount' => $displayPricing['has_discount'] && $displayPricing['max_discount_percentage'] > 0,
145 'discount_text' => $displayPricing['max_discount_percentage'] > 0
146 ? sprintf(__('Save up to %d%%', 'yatra'), $displayPricing['max_discount_percentage']) : '',
147 'discount_percentage' => $displayPricing['max_discount_percentage'],
148 ];
149 ?>
150 <div class="yatra-booking-card"
151 data-has-availability="<?php echo $has_availability ? 'true' : 'false'; ?>"
152 data-is-multi-day="<?php echo $is_multi_day ? 'true' : 'false'; ?>"
153 data-pricing-type="<?php echo esc_attr($pricing_type); ?>"
154 data-availability='<?php echo esc_attr(json_encode($availability_json)); ?>'
155 data-group-discounts='<?php echo esc_attr(json_encode($sidebar_group_discounts_data)); ?>'>
156
157 <?php if ($sidebar_has_group_discounts): ?>
158 <!-- Group Discount Badge - Minimal with Tooltip -->
159 <div class="yatra-group-discount-badge-minimal yatra-has-tooltip">
160 <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
161 <path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path>
162 <circle cx="9" cy="7" r="4"></circle>
163 <path d="M23 21v-2a4 4 0 0 0-3-3.87"></path>
164 <path d="M16 3.13a4 4 0 0 1 0 7.75"></path>
165 </svg>
166 <span><?php echo esc_html__('Group discount available', 'yatra'); ?></span>
167 <svg class="yatra-info-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
168 <circle cx="12" cy="12" r="10"></circle>
169 <line x1="12" y1="16" x2="12" y2="12"></line>
170 <line x1="12" y1="8" x2="12.01" y2="8"></line>
171 </svg>
172 <div class="yatra-tooltip">
173 <div class="yatra-tooltip-header">
174 <strong><?php echo esc_html__('Group Discount Tiers', 'yatra'); ?></strong>
175 </div>
176 <div class="yatra-tooltip-content">
177 <?php if (!empty($sidebar_group_discounts_data) && is_array($sidebar_group_discounts_data)): ?>
178 <?php foreach ($sidebar_group_discounts_data as $range): ?>
179 <div class="yatra-discount-tier">
180 <span class="yatra-tier-size">
181 <?php
182 $min = (int) ($range['min_group_size'] ?? 0);
183 $max = isset($range['max_group_size']) && $range['max_group_size'] > 0 ? (int) $range['max_group_size'] : null;
184 if ($max) {
185 echo sprintf(esc_html__('%d-%d travelers', 'yatra'), $min, $max);
186 } else {
187 echo sprintf(esc_html__('%d+ travelers', 'yatra'), $min);
188 }
189 ?>
190 </span>
191 <span class="yatra-tier-discount">
192 <?php
193 $discount_type = $range['discount_type'] ?? 'percentage';
194 $discount_value = (float) ($range['discount_amount'] ?? $range['discount_percentage'] ?? 0);
195
196 if ($discount_type === 'percentage') {
197 echo esc_html(sprintf(__('%s%% OFF', 'yatra'), number_format($discount_value, 1)));
198 } else {
199 echo esc_html(sprintf(__('%s OFF', 'yatra'), yatra_format_price($discount_value)));
200 }
201 ?>
202 </span>
203 </div>
204 <?php endforeach; ?>
205 <?php endif; ?>
206 </div>
207 </div>
208 </div>
209 <?php endif; ?>
210
211 <!-- Price Display -->
212 <div class="yatra-booking-price">
213 <?php if ($discount['has_discount']): ?>
214 <div class="yatra-booking-discount-badge">
215 <?php echo esc_html($discount['discount_text']); ?>
216 </div>
217 <?php endif; ?>
218 <div class="yatra-booking-price-main">
219 <?php if ($pricing['has_price']): ?>
220 <?php if ($has_availability || $has_traveler_pricing): ?>
221 <span class="yatra-booking-price-label-top"><?php echo esc_html__('From', 'yatra'); ?></span>
222 <?php endif; ?>
223 <?php if ($pricing['has_discount'] && !empty($pricing['original_price'])): ?>
224 <span class="yatra-booking-price-original"><?php echo esc_html($pricing['original_price']); ?></span>
225 <?php endif; ?>
226 <span class="yatra-booking-price-amount" id="display-price"><?php echo esc_html($pricing['current_price']); ?></span>
227 <span class="yatra-booking-price-label"><?php echo esc_html__('per person', 'yatra'); ?></span>
228 <?php else: ?>
229 <span class="yatra-booking-price-amount yatra-contact-pricing" id="display-price"><?php echo esc_html__('Contact for pricing', 'yatra'); ?></span>
230 <span class="yatra-booking-price-label"><?php echo esc_html__('per person', 'yatra'); ?></span>
231 <?php endif; ?>
232 </div>
233 </div>
234
235 <?php if ($trip->getBookingMode() === 'date_specific' && $has_availability): ?>
236 <!-- ========================================== -->
237 <!-- DATE-SPECIFIC BOOKING (Has Availability Dates/Rules) -->
238 <!-- ========================================== -->
239 <div class="yatra-availability-info">
240 <span class="yatra-availability-count">
241 <?php echo esc_html(sprintf(_n('%d departure available', '%d departures available', count($trip->getAvailabilityDates()), 'yatra'), count($trip->getAvailabilityDates()))); ?>
242 </span>
243 </div>
244
245 <form class="yatra-booking-form" data-booking-mode="date_specific">
246 <!-- Date Selection -->
247 <div class="yatra-booking-field-select">
248 <div class="yatra-booking-field-icon">
249 <?php echo yatra_svg_icon('calendar', 'yatra-icon-sm'); ?>
250 </div>
251 <input type="text"
252 id="travel_date"
253 name="travel_date"
254 class="yatra-booking-select yatra-datepicker"
255 placeholder="<?php esc_attr_e('Select date', 'yatra'); ?>"
256 readonly
257 required>
258 <svg class="yatra-select-arrow" fill="none" stroke="currentColor" viewBox="0 0 24 24">
259 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
260 </svg>
261 </div>
262
263 <!-- Travelers Selection - Respects Trip's Pricing Type -->
264 <?php
265 // Use helper function to prepare traveler data
266 $traveler_data = \Yatra\Controllers\SingleTripController::prepareTravelerSelectorData($trip, 'availability');
267
268 if ($traveler_data['has_traveler_pricing']):
269 // Setup variables for traveler-selector.php
270 $root_id = '';
271 $root_class = 'yatra-booking-field-select yatra-participants-select yatra-availability-participants';
272 $container_attrs = [];
273
274 $display_id = 'participants-display';
275 $display_class = 'yatra-participants-display yatra-availability-participants-display';
276 $display_attrs = [];
277
278 $dropdown_id = 'quantity-selector';
279 $dropdown_class = 'yatra-booking-quantity-selector yatra-availability-quantity-selector';
280 $dropdown_attrs = [];
281
282 $display_text = $traveler_data['traveler_display_text'];
283 $icon_html = yatra_svg_icon('users', 'yatra-icon-sm');
284 $rows = $traveler_data['traveler_rows'];
285
286 include YATRA_PLUGIN_PATH . 'templates/partials/traveler-selector.php';
287 else:
288 // Regular Pricing: Simple number of travelers input
289 ?>
290 <div class="yatra-booking-field-group">
291 <label for="num_travelers" class="yatra-booking-field-label"><?php echo esc_html__('Number of Travelers', 'yatra'); ?></label>
292 <?php
293 // Setup variables for traveler-input-simple.php
294 $input_id = 'num_travelers';
295 $input_name = 'num_travelers';
296 $value = 1;
297 $min = $trip->getMinTravelers() ?: 1;
298 $max = $trip->getMaxTravelers() ?: 20;
299 $data_price = $base_price;
300 $range_text = sprintf(__('Min: %d, Max: %d', 'yatra'), $min, $max);
301
302 include YATRA_PLUGIN_PATH . 'templates/partials/traveler-input-simple.php';
303 ?>
304 </div>
305 <?php endif; ?>
306 <?php else: ?>
307 <!-- ========================================== -->
308 <!-- FLEXIBLE BOOKING MODE (No Specific Availability Configured) -->
309 <!-- ========================================== -->
310 <div class="yatra-availability-info yatra-flexible-booking-info">
311 <span class="yatra-availability-count">
312 <?php echo esc_html__('Flexible booking available', 'yatra'); ?>
313 </span>
314 <?php if (!empty($trip->getAvailableFrom()) || !empty($trip->getAvailableTo())): ?>
315 <span class="yatra-availability-note">
316 <?php
317 if (!empty($trip->getAvailableFrom()) && !empty($trip->getAvailableTo())) {
318 echo esc_html(sprintf(__('Available: %s - %s', 'yatra'),
319 date_i18n(get_option('date_format'), strtotime($trip->getAvailableFrom())),
320 date_i18n(get_option('date_format'), strtotime($trip->getAvailableTo()))
321 ));
322 } elseif (!empty($trip->getAvailableFrom())) {
323 echo esc_html(sprintf(__('Available from: %s', 'yatra'),
324 date_i18n(get_option('date_format'), strtotime($trip->getAvailableFrom()))
325 ));
326 } else {
327 echo esc_html(sprintf(__('Available until: %s', 'yatra'),
328 date_i18n(get_option('date_format'), strtotime($trip->getAvailableTo()))
329 ));
330 }
331 ?>
332 </span>
333 <?php endif; ?>
334 </div>
335
336 <form class="yatra-booking-form" data-booking-mode="flexible">
337 <!-- Date Selection (Flexible) -->
338 <div class="yatra-booking-field-select">
339 <div class="yatra-booking-field-icon">
340 <?php echo yatra_svg_icon('calendar', 'yatra-icon-sm'); ?>
341 </div>
342 <input type="text"
343 id="travel_date"
344 name="travel_date"
345 class="yatra-booking-select yatra-datepicker"
346 placeholder="<?php esc_attr_e('Select date', 'yatra'); ?>"
347 data-min-date="<?php echo esc_attr($trip->getAvailableFrom() ?: date('Y-m-d')); ?>"
348 data-max-date="<?php echo esc_attr($trip->getAvailableTo() ?: ''); ?>"
349 readonly
350 required>
351 <svg class="yatra-select-arrow" fill="none" stroke="currentColor" viewBox="0 0 24 24">
352 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
353 </svg>
354 </div>
355
356 <!-- Travelers Selection -->
357 <?php
358 // Use helper function to prepare traveler data
359 $traveler_data = \Yatra\Controllers\SingleTripController::prepareTravelerSelectorData($trip, 'sidebar');
360
361 if ($traveler_data['has_traveler_pricing']):
362 // Setup variables for traveler-selector.php
363 $root_id = '';
364 $root_class = 'yatra-booking-field-select yatra-participants-select';
365 $container_attrs = [];
366
367 $display_id = 'participants-display';
368 $display_class = 'yatra-participants-display';
369 $display_attrs = [];
370
371 $dropdown_id = 'quantity-selector';
372 $dropdown_class = 'yatra-booking-quantity-selector';
373 $dropdown_attrs = [];
374
375 $display_text = $traveler_data['traveler_display_text'];
376 $icon_html = yatra_svg_icon('users', 'yatra-icon-sm');
377 $rows = $traveler_data['traveler_rows'];
378
379 include YATRA_PLUGIN_PATH . 'templates/partials/traveler-selector.php';
380 else:
381 // Regular Pricing: Simple number of travelers input
382 ?>
383 <div class="yatra-booking-field-group">
384 <label for="num_travelers" class="yatra-booking-field-label"><?php echo esc_html__('Number of Travelers', 'yatra'); ?></label>
385 <?php
386 // Setup variables for traveler-input-simple.php
387 $input_id = 'num_travelers';
388 $input_name = 'num_travelers';
389 $value = 1;
390 $min = $trip->getMinTravelers() ?: 1;
391 $max = $trip->getMaxTravelers() ?: 20;
392 $data_price = $base_price;
393 $range_text = sprintf(__('Min: %d, Max: %d', 'yatra'), $min, $max);
394
395 include YATRA_PLUGIN_PATH . 'templates/partials/traveler-input-simple.php';
396 ?>
397 </div>
398 <?php endif; ?>
399 <?php endif; ?>
400
401
402 <!-- Action Buttons -->
403 <button type="button" class="yatra-booking-button" id="check-availability-btn" data-trip-id="<?php echo esc_attr($trip->getId()); ?>">
404 <svg width="18" height="18" fill="none" stroke="currentColor" viewBox="0 0 24 24">
405 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"/>
406 </svg>
407 <?php echo esc_html__('Check Availability', 'yatra'); ?>
408 </button>
409
410 <button type="button" class="yatra-booking-enquiry-button" id="open-enquiry-modal" onclick="window.YatraEnquiry?.open?.(); return false;">
411 <svg width="18" height="18" fill="none" stroke="currentColor" viewBox="0 0 24 24">
412 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 10h.01M12 10h.01M16 10h.01M9 16H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-5l-5 5v-5z"/>
413 </svg>
414 <?php echo esc_html__('Make Enquiry', 'yatra'); ?>
415 </button>
416
417 <!-- Cancellation Policy Trust Signal -->
418 <?php
419 $cancellation_policy = '';
420 if (method_exists($trip, 'getCancellationPolicy')) {
421 $cancellation_policy = $trip->getCancellationPolicy();
422 } elseif (isset($trip->cancellation_policy)) {
423 $cancellation_policy = $trip->cancellation_policy;
424 }
425
426 $cancellation_text = '';
427 if (!empty($cancellation_policy)) {
428 // Extract cancellation info from policy
429 $policy_lower = strtolower($cancellation_policy);
430 if (strpos($policy_lower, 'free') !== false || strpos($policy_lower, 'no charge') !== false) {
431 if (preg_match('/(\d+)\s*(hour|day|week)s?\s*before/i', $cancellation_policy, $matches)) {
432 $time_value = $matches[1];
433 $time_unit = $matches[2];
434 $cancellation_text = sprintf(
435 __('Free cancellation up to %d %s%s before', 'yatra'),
436 $time_value,
437 $time_unit,
438 $time_value > 1 ? 's' : ''
439 );
440 } else {
441 $cancellation_text = __('Free cancellation', 'yatra');
442 }
443 } else {
444 // Non-free cancellation, show brief info
445 $cancellation_text = __('Cancellation policy applies', 'yatra');
446 }
447 } else {
448 // Default fallback when no policy is set
449 $cancellation_text = __('Cancellation policy may apply', 'yatra');
450 }
451 ?>
452 <div class="yatra-booking-trust">
453 <div class="yatra-booking-trust-icon">
454 <?php echo yatra_svg_icon('check', 'yatra-icon-xs'); ?>
455 </div>
456 <div class="yatra-booking-trust-text">
457 <strong><?php echo esc_html($cancellation_text); ?></strong>
458 </div>
459 </div>
460 </form>
461 </div>
462 </aside>