PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.17
Yatra – Travel Booking & Tour Operator Software v3.0.17
3.0.17 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 All 85 releases
← All changes | templates/partials/single-trip/content-sidebar.php +274 -205 3.0.2.7 → 3.0.17 View file →
@@ -4,127 +4,21 @@
4 4 }
5 5 ?>
6 6 <aside class="yatra-trip-sidebar" id="booking">
7 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)
8 + // Group discounts (Advanced Discount / Pro): same payload as the public REST discoverability endpoint
12 9 $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;
10 + $sidebar_group_discount_cards = [];
11 + if (function_exists('yatra_single_trip_get_group_discounts')) {
12 + $gd = yatra_single_trip_get_group_discounts((int) $trip->getId());
13 + $sidebar_has_group_discounts = !empty($gd['has_group_discounts']);
14 + $sidebar_group_discount_cards = isset($gd['group_discounts_data']) && is_array($gd['group_discounts_data'])
15 + ? $gd['group_discounts_data']
16 + : [];
101 17 }
102 18
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 - }
19 + // Pro: check whether this trip has booking disabled (enquiry-only mode)
20 + $booking_disabled = method_exists($trip, 'isBookingDisabled') && $trip->isBookingDisabled();
127 21
128 22 // Pricing — resolved via centralized TripPricingService (single source of truth)
129 23 // SingleTripController pre-computes effective_price_min, min_category_original_price, max_discount_percentage
130 24 $displayPricing = \Yatra\Services\TripPricingService::resolveDisplayPricing($trip);
@@ -137,81 +31,111 @@
137 31 'has_discount' => $displayPricing['has_discount'],
138 32 'raw_current_price' => $displayPricing['effective_price_min'],
139 33 'raw_original_price' => $displayPricing['min_category_original_price'],
140 34 'is_traveler_based' => $displayPricing['has_traveler_pricing'],
35 + // "per person" / "per group (1-2 pax)" — follows the category the shown price comes from
36 + 'price_unit' => $displayPricing['price_unit'] ?? 'per_person',
37 + 'price_unit_label' => $displayPricing['price_unit_label'] ?? __('per person', 'yatra'),
141 38 ];
142 39
143 40 $discount = [
144 41 'has_discount' => $displayPricing['has_discount'] && $displayPricing['max_discount_percentage'] > 0,
145 42 'discount_text' => $displayPricing['max_discount_percentage'] > 0
146 - ? sprintf(__('Save up to %d%%', 'yatra'), $displayPricing['max_discount_percentage']) : '',
43 + ? sprintf(
44 + /* translators: %d: maximum discount percentage. */
45 + __('Save up to %d%%', 'yatra'),
46 + $displayPricing['max_discount_percentage']
47 + ) : '',
147 48 'discount_percentage' => $displayPricing['max_discount_percentage'],
148 49 ];
50 +
51 + $dp_display_sidebar = apply_filters('yatra_get_dynamic_pricing_display_settings', [
52 + 'show_original_price' => true,
53 + 'show_savings_badge' => true,
54 + 'show_urgency_messages' => false,
55 + ]);
56 + $dp_show_savings_sidebar = filter_var($dp_display_sidebar['show_savings_badge'] ?? true, FILTER_VALIDATE_BOOLEAN);
57 + $show_strike_original_sidebar = filter_var($dp_display_sidebar['show_original_price'] ?? true, FILTER_VALIDATE_BOOLEAN);
58 + $yatra_sidebar_urgency = function_exists('yatra_trip_card_dynamic_pricing_urgency_lines')
59 + ? yatra_trip_card_dynamic_pricing_urgency_lines((int) $trip->getId(), [
60 + 'base_sale_price' => (float) ($displayPricing['effective_price_min'] ?? 0),
61 + 'base_original_price' => (float) ($displayPricing['min_category_original_price'] ?? 0),
62 + 'sale_price' => (float) ($displayPricing['effective_price_min'] ?? 0),
63 + 'original_price' => (float) ($displayPricing['min_category_original_price'] ?? 0),
64 + 'departure_date' => null,
65 + 'spots_remaining' => null,
66 + 'availability_id' => null,
67 + 'surface' => 'single_sidebar',
68 + ])
69 + : [];
149 70 ?>
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>
71 + <?php if ($booking_disabled): ?>
72 + <div class="yatra-booking-card yatra-enquiry-only-card">
73 + <!-- Price Display -->
74 + <div class="yatra-booking-price">
75 + <?php if ($discount['has_discount'] && $dp_show_savings_sidebar): ?>
76 + <div class="yatra-booking-discount-badge">
77 + <?php echo esc_html($discount['discount_text']); ?>
207 78 </div>
79 + <?php endif; ?>
80 + <div class="yatra-booking-price-main">
81 + <?php if ($pricing['has_price']): ?>
82 + <span class="yatra-booking-price-label-top"><?php echo esc_html__('From', 'yatra'); ?></span>
83 + <?php if ($pricing['has_discount'] && !empty($pricing['original_price']) && $show_strike_original_sidebar): ?>
84 + <span class="yatra-booking-price-original"><?php echo esc_html($pricing['original_price']); ?></span>
85 + <?php endif; ?>
86 + <span class="yatra-booking-price-amount" id="display-price"><?php echo esc_html($pricing['current_price']); ?></span>
87 + <span class="yatra-booking-price-label" data-price-unit="<?php echo esc_attr($pricing['price_unit']); ?>"><?php echo esc_html($pricing['price_unit_label']); ?></span>
88 + <?php if (!empty($yatra_sidebar_urgency)) : ?>
89 + <?php foreach ($yatra_sidebar_urgency as $yatra_sb_urg) : ?>
90 + <div class="yatra-booking-dp-urgency" style="background-color:#fef3c7;color:#92400e;padding:6px 8px;border-radius:6px;margin-top:8px;font-size:12px;line-height:1.35;">
91 + <?php echo esc_html((string) $yatra_sb_urg); ?>
92 + </div>
93 + <?php endforeach; ?>
94 + <?php endif; ?>
95 + <?php else: ?>
96 + <span class="yatra-booking-price-amount yatra-contact-pricing" id="display-price"><?php echo esc_html__('Contact for pricing', 'yatra'); ?></span>
97 + <?php endif; ?>
208 98 </div>
99 + </div>
100 +
101 + <!-- Enquiry-only notice -->
102 + <div class="yatra-enquiry-notice" style="margin:12px 0 4px;padding:10px 12px;background:#f0f7ff;border:1px solid #cce0ff;border-radius:6px;font-size:0.85rem;color:#1a5fa8;display:flex;align-items:flex-start;gap:8px;">
103 + <svg width="16" height="16" fill="none" stroke="currentColor" viewBox="0 0 24 24" style="flex-shrink:0;margin-top:1px">
104 + <circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/>
105 + </svg>
106 + <span><?php esc_html_e('This trip is available for enquiry only. Please contact us to discuss dates and availability.', 'yatra'); ?></span>
107 + </div>
108 +
109 + <!-- Enquiry button -->
110 + <button type="button" class="yatra-booking-button" style="margin-top:12px;" id="open-enquiry-modal" onclick="window.YatraEnquiry?.open?.(); return false;">
111 + <svg width="18" height="18" fill="none" stroke="currentColor" viewBox="0 0 24 24">
112 + <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"/>
113 + </svg>
114 + <?php echo esc_html__('Send Enquiry', 'yatra'); ?>
115 + </button>
116 + <?php
117 + // AI: Chat-with-AI widget. The partial gates itself on the
118 + // operator's enable flag + module-active state so it's a no-op
119 + // when the feature is off.
120 + yatra_get_template('partials/single-trip/ai-chat-widget', ['trip' => $trip]);
121 + ?>
122 + </div>
123 + <?php else: ?>
124 + <div class="yatra-booking-card">
125 +
126 + <?php if ($sidebar_has_group_discounts && !empty($sidebar_group_discount_cards)): ?>
127 + <?php
128 + $group_discount_cards = $sidebar_group_discount_cards;
129 + $popover_context = 'booking';
130 + $popover_uid = 'sb-' . (int) $trip->getId();
131 + yatra_get_template('partials/single-trip/group-discount-booking-popover', compact('group_discount_cards', 'popover_context', 'popover_uid'));
132 + ?>
209 133 <?php endif; ?>
210 134
211 135 <!-- Price Display -->
212 136 <div class="yatra-booking-price">
213 - <?php if ($discount['has_discount']): ?>
137 + <?php if ($discount['has_discount'] && $dp_show_savings_sidebar): ?>
214 138 <div class="yatra-booking-discount-badge">
215 139 <?php echo esc_html($discount['discount_text']); ?>
216 140 </div>
217 141 <?php endif; ?>
@@ -219,16 +143,22 @@
219 143 <?php if ($pricing['has_price']): ?>
220 144 <?php if ($has_availability || $has_traveler_pricing): ?>
221 145 <span class="yatra-booking-price-label-top"><?php echo esc_html__('From', 'yatra'); ?></span>
222 146 <?php endif; ?>
223 - <?php if ($pricing['has_discount'] && !empty($pricing['original_price'])): ?>
147 + <?php if ($pricing['has_discount'] && !empty($pricing['original_price']) && $show_strike_original_sidebar): ?>
224 148 <span class="yatra-booking-price-original"><?php echo esc_html($pricing['original_price']); ?></span>
225 149 <?php endif; ?>
226 150 <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>
151 + <span class="yatra-booking-price-label" data-price-unit="<?php echo esc_attr($pricing['price_unit']); ?>"><?php echo esc_html($pricing['price_unit_label']); ?></span>
152 + <?php if (!empty($yatra_sidebar_urgency)) : ?>
153 + <?php foreach ($yatra_sidebar_urgency as $yatra_sb_urg) : ?>
154 + <div class="yatra-booking-dp-urgency" style="background-color:#fef3c7;color:#92400e;padding:6px 8px;border-radius:6px;margin-top:8px;font-size:12px;line-height:1.35;">
155 + <?php echo esc_html((string) $yatra_sb_urg); ?>
156 + </div>
157 + <?php endforeach; ?>
158 + <?php endif; ?>
228 159 <?php else: ?>
229 160 <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 161 <?php endif; ?>
232 162 </div>
233 163 </div>
234 164
@@ -237,25 +167,130 @@
237 167 <!-- DATE-SPECIFIC BOOKING (Has Availability Dates/Rules) -->
238 168 <!-- ========================================== -->
239 169 <div class="yatra-availability-info">
240 170 <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()))); ?>
171 + <?php
172 + $allAvailability = $trip->getAvailabilityDates();
173 + $today = date('Y-m-d');
174 +
175 + // Sidebar count should reflect the same resolved availability set (priority:
176 + // manual > recurring rule > trip default). We keep it lightweight here:
177 + // count upcoming departures (date + optional time) from the resolved list.
178 + $filtered = array_filter($allAvailability, static function ($a) use ($today) {
179 + if (!is_object($a)) {
180 + return false;
181 + }
182 + $date = (string) ($a->departure_date ?? '');
183 + if ($date === '' || $date < $today) {
184 + return false;
185 + }
186 + return true;
187 + });
188 +
189 + // Priority: manual availability dates > recurring rules > trip default.
190 + $bySource = [
191 + 'availability_date' => [],
192 + 'recurring_rule' => [],
193 + 'trip_default' => [],
194 + '' => [],
195 + ];
196 + foreach ($filtered as $a) {
197 + $src = strtolower(trim((string) ($a->source ?? '')));
198 + if (!isset($bySource[$src])) {
199 + $src = '';
200 + }
201 + $bySource[$src][] = $a;
202 + }
203 +
204 + $pick = [];
205 + if (!empty($bySource['availability_date'])) {
206 + $pick = $bySource['availability_date'];
207 + } elseif (!empty($bySource['recurring_rule'])) {
208 + $pick = $bySource['recurring_rule'];
209 + } elseif (!empty($bySource['trip_default'])) {
210 + $pick = $bySource['trip_default'];
211 + } else {
212 + $pick = $filtered;
213 + }
214 +
215 + // Count departures:
216 + // - if availability entries include departure_time, count unique (date + time) as departures
217 + // - otherwise count unique dates.
218 + $hasTimeSlots = false;
219 + foreach ($pick as $a) {
220 + $t = isset($a->departure_time) ? trim((string) $a->departure_time) : '';
221 + if ($t !== '' && $t !== '00:00:00') {
222 + $hasTimeSlots = true;
223 + break;
224 + }
225 + }
226 +
227 + $unique = [];
228 + foreach ($pick as $a) {
229 + $date = (string) ($a->departure_date ?? '');
230 + if ($date === '') {
231 + continue;
232 + }
233 + if ($hasTimeSlots) {
234 + $t = isset($a->departure_time) ? trim((string) $a->departure_time) : '';
235 + if ($t !== '' && $t !== '00:00:00') {
236 + $date .= '|' . $t;
237 + } else {
238 + // When some rows have time and others don't, avoid collapsing into date-only.
239 + $date .= '|__no_time__';
240 + }
241 + }
242 + $unique[$date] = true;
243 + }
244 + $countAvailable = count($unique);
245 +
246 + // Pro feature: a Pro module can hook `yatra_use_date_dropdown`
247 + // to replace the flatpickr calendar with a <select> of available
248 + // departures, and supplies the option list via
249 + // `yatra_single_trip_date_dropdown_options`. Free plugin renders
250 + // calendar by default (filter returns false / [] when Pro is
251 + // inactive or the setting is off).
252 + $dropdown_options = (array) apply_filters('yatra_single_trip_date_dropdown_options', [], $trip, $pick);
253 + $yatra_use_date_dropdown = (bool) apply_filters('yatra_use_date_dropdown', false, $trip)
254 + && !empty($dropdown_options);
255 +
256 + echo esc_html(sprintf(
257 + /* translators: %d: number of departures available. */
258 + _n('%d departure available', '%d departures available', $countAvailable, 'yatra'),
259 + $countAvailable
260 + ));
261 + ?>
242 262 </span>
243 263 </div>
244 264
245 265 <form class="yatra-booking-form" data-booking-mode="date_specific">
246 266 <!-- Date Selection -->
247 - <div class="yatra-booking-field-select">
267 + <div class="yatra-booking-field-select<?php echo $yatra_use_date_dropdown ? ' yatra-date-as-dropdown' : ''; ?>">
248 268 <div class="yatra-booking-field-icon">
249 269 <?php echo yatra_svg_icon('calendar', 'yatra-icon-sm'); ?>
250 270 </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>
271 + <?php if ($yatra_use_date_dropdown): ?>
272 + <select id="travel_date"
273 + name="travel_date"
274 + class="yatra-booking-select yatra-date-dropdown"
275 + required>
276 + <option value=""><?php esc_html_e('Select a departure date', 'yatra'); ?></option>
277 + <?php foreach ($dropdown_options as $opt): ?>
278 + <option value="<?php echo esc_attr($opt['value']); ?>"
279 + <?php if (!empty($opt['time'])): ?>data-departure-time="<?php echo esc_attr($opt['time']); ?>"<?php endif; ?>>
280 + <?php echo esc_html($opt['label']); ?>
281 + </option>
282 + <?php endforeach; ?>
283 + </select>
284 + <?php else: ?>
285 + <input type="text"
286 + id="travel_date"
287 + name="travel_date"
288 + class="yatra-booking-select yatra-datepicker"
289 + placeholder="<?php esc_attr_e('Select date', 'yatra'); ?>"
290 + readonly
291 + required>
292 + <?php endif; ?>
258 293 <svg class="yatra-select-arrow" fill="none" stroke="currentColor" viewBox="0 0 24 24">
259 294 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
260 295 </svg>
261 296 </div>
@@ -296,10 +331,9 @@
296 331 $value = 1;
297 332 $min = $trip->getMinTravelers() ?: 1;
298 333 $max = $trip->getMaxTravelers() ?: 20;
299 334 $data_price = $base_price;
300 - $range_text = sprintf(__('Min: %d, Max: %d', 'yatra'), $min, $max);
301 -
335 +
302 336 include YATRA_PLUGIN_PATH . 'templates/partials/traveler-input-simple.php';
303 337 ?>
304 338 </div>
305 339 <?php endif; ?>
@@ -314,18 +348,24 @@
314 348 <?php if (!empty($trip->getAvailableFrom()) || !empty($trip->getAvailableTo())): ?>
315 349 <span class="yatra-availability-note">
316 350 <?php
317 351 if (!empty($trip->getAvailableFrom()) && !empty($trip->getAvailableTo())) {
318 - echo esc_html(sprintf(__('Available: %s - %s', 'yatra'),
352 + echo esc_html(sprintf(
353 + /* translators: 1: available-from date, 2: available-to date. */
354 + __('Available: %1$s - %2$s', 'yatra'),
319 355 date_i18n(get_option('date_format'), strtotime($trip->getAvailableFrom())),
320 356 date_i18n(get_option('date_format'), strtotime($trip->getAvailableTo()))
321 357 ));
322 358 } elseif (!empty($trip->getAvailableFrom())) {
323 - echo esc_html(sprintf(__('Available from: %s', 'yatra'),
359 + echo esc_html(sprintf(
360 + /* translators: %s: available-from date. */
361 + __('Available from: %s', 'yatra'),
324 362 date_i18n(get_option('date_format'), strtotime($trip->getAvailableFrom()))
325 363 ));
326 364 } else {
327 - echo esc_html(sprintf(__('Available until: %s', 'yatra'),
365 + echo esc_html(sprintf(
366 + /* translators: %s: available-until date. */
367 + __('Available until: %s', 'yatra'),
328 368 date_i18n(get_option('date_format'), strtotime($trip->getAvailableTo()))
329 369 ));
330 370 }
331 371 ?>
@@ -334,21 +374,45 @@
334 374 </div>
335 375
336 376 <form class="yatra-booking-form" data-booking-mode="flexible">
337 377 <!-- Date Selection (Flexible) -->
338 - <div class="yatra-booking-field-select">
378 + <?php
379 + // Same Pro filter contract as the date_specific branch above.
380 + // Flexible trips have no discrete availability set, so $pick is
381 + // passed as null — the Pro hook synthesizes a list from the
382 + // trip's available_from/to window.
383 + $flex_dropdown_options = (array) apply_filters('yatra_single_trip_date_dropdown_options', [], $trip, null);
384 + $flex_use_date_dropdown = (bool) apply_filters('yatra_use_date_dropdown', false, $trip)
385 + && !empty($flex_dropdown_options);
386 + ?>
387 + <div class="yatra-booking-field-select<?php echo $flex_use_date_dropdown ? ' yatra-date-as-dropdown' : ''; ?>">
339 388 <div class="yatra-booking-field-icon">
340 389 <?php echo yatra_svg_icon('calendar', 'yatra-icon-sm'); ?>
341 390 </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>
391 + <?php if ($flex_use_date_dropdown): ?>
392 + <select id="travel_date"
393 + name="travel_date"
394 + class="yatra-booking-select yatra-date-dropdown"
395 + required>
396 + <option value=""><?php esc_html_e('Select a departure date', 'yatra'); ?></option>
397 + <?php foreach ($flex_dropdown_options as $opt): ?>
398 + <option value="<?php echo esc_attr($opt['value']); ?>"
399 + <?php if (!empty($opt['time'])): ?>data-departure-time="<?php echo esc_attr($opt['time']); ?>"<?php endif; ?>>
400 + <?php echo esc_html($opt['label']); ?>
401 + </option>
402 + <?php endforeach; ?>
403 + </select>
404 + <?php else: ?>
405 + <input type="text"
406 + id="travel_date"
407 + name="travel_date"
408 + class="yatra-booking-select yatra-datepicker"
409 + placeholder="<?php esc_attr_e('Select date', 'yatra'); ?>"
410 + data-min-date="<?php echo esc_attr($trip->getAvailableFrom() ?: date('Y-m-d')); ?>"
411 + data-max-date="<?php echo esc_attr($trip->getAvailableTo() ?: ''); ?>"
412 + readonly
413 + required>
414 + <?php endif; ?>
351 415 <svg class="yatra-select-arrow" fill="none" stroke="currentColor" viewBox="0 0 24 24">
352 416 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
353 417 </svg>
354 418 </div>
@@ -389,10 +453,9 @@
389 453 $value = 1;
390 454 $min = $trip->getMinTravelers() ?: 1;
391 455 $max = $trip->getMaxTravelers() ?: 20;
392 456 $data_price = $base_price;
393 - $range_text = sprintf(__('Min: %d, Max: %d', 'yatra'), $min, $max);
394 -
457 +
395 458 include YATRA_PLUGIN_PATH . 'templates/partials/traveler-input-simple.php';
396 459 ?>
397 460 </div>
398 461 <?php endif; ?>
@@ -412,8 +475,12 @@
412 475 <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 476 </svg>
414 477 <?php echo esc_html__('Make Enquiry', 'yatra'); ?>
415 478 </button>
479 + <?php
480 + // AI: Chat-with-AI widget — bookable-trip branch.
481 + yatra_get_template('partials/single-trip/ai-chat-widget', ['trip' => $trip]);
482 + ?>
416 483
417 484 <!-- Cancellation Policy Trust Signal -->
418 485 <?php
419 486 $cancellation_policy = '';
@@ -431,9 +498,10 @@
431 498 if (preg_match('/(\d+)\s*(hour|day|week)s?\s*before/i', $cancellation_policy, $matches)) {
432 499 $time_value = $matches[1];
433 500 $time_unit = $matches[2];
434 501 $cancellation_text = sprintf(
435 - __('Free cancellation up to %d %s%s before', 'yatra'),
502 + /* translators: 1: numeric time value, 2: time unit (hour/day/week), 3: plural suffix ("s" or empty). */
503 + __('Free cancellation up to %1$d %2$s%3$s before', 'yatra'),
436 504 $time_value,
437 505 $time_unit,
438 506 $time_value > 1 ? 's' : ''
439 507 );
@@ -458,5 +526,6 @@
458 526 </div>
459 527 </div>
460 528 </form>
461 529 </div>
530 + <?php endif; // end else (booking not disabled) ?>
462 531 </aside>