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 +235 -210 3.0.2.9 → 3.0.17 View file →
@@ -4,128 +4,19 @@
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 - }
127 -
128 19 // Pro: check whether this trip has booking disabled (enquiry-only mode)
129 20 $booking_disabled = method_exists($trip, 'isBookingDisabled') && $trip->isBookingDisabled();
130 21
131 22 // Pricing — resolved via centralized TripPricingService (single source of truth)
@@ -140,22 +31,49 @@
140 31 'has_discount' => $displayPricing['has_discount'],
141 32 'raw_current_price' => $displayPricing['effective_price_min'],
142 33 'raw_original_price' => $displayPricing['min_category_original_price'],
143 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'),
144 38 ];
145 39
146 40 $discount = [
147 41 'has_discount' => $displayPricing['has_discount'] && $displayPricing['max_discount_percentage'] > 0,
148 42 'discount_text' => $displayPricing['max_discount_percentage'] > 0
149 - ? 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 + ) : '',
150 48 'discount_percentage' => $displayPricing['max_discount_percentage'],
151 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 + : [];
152 70 ?>
153 71 <?php if ($booking_disabled): ?>
154 72 <div class="yatra-booking-card yatra-enquiry-only-card">
155 73 <!-- Price Display -->
156 74 <div class="yatra-booking-price">
157 - <?php if ($discount['has_discount']): ?>
75 + <?php if ($discount['has_discount'] && $dp_show_savings_sidebar): ?>
158 76 <div class="yatra-booking-discount-badge">
159 77 <?php echo esc_html($discount['discount_text']); ?>
160 78 </div>
161 79 <?php endif; ?>
@@ -161,13 +79,20 @@
161 79 <?php endif; ?>
162 80 <div class="yatra-booking-price-main">
163 81 <?php if ($pricing['has_price']): ?>
164 82 <span class="yatra-booking-price-label-top"><?php echo esc_html__('From', 'yatra'); ?></span>
165 - <?php if ($pricing['has_discount'] && !empty($pricing['original_price'])): ?>
83 + <?php if ($pricing['has_discount'] && !empty($pricing['original_price']) && $show_strike_original_sidebar): ?>
166 84 <span class="yatra-booking-price-original"><?php echo esc_html($pricing['original_price']); ?></span>
167 85 <?php endif; ?>
168 86 <span class="yatra-booking-price-amount" id="display-price"><?php echo esc_html($pricing['current_price']); ?></span>
169 - <span class="yatra-booking-price-label"><?php echo esc_html__('per person', 'yatra'); ?></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; ?>
170 95 <?php else: ?>
171 96 <span class="yatra-booking-price-amount yatra-contact-pricing" id="display-price"><?php echo esc_html__('Contact for pricing', 'yatra'); ?></span>
172 97 <?php endif; ?>
173 98 </div>
@@ -187,74 +112,30 @@
187 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"/>
188 113 </svg>
189 114 <?php echo esc_html__('Send Enquiry', 'yatra'); ?>
190 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 + ?>
191 122 </div>
192 123 <?php else: ?>
193 - <div class="yatra-booking-card"
194 - data-has-availability="<?php echo $has_availability ? 'true' : 'false'; ?>"
195 - data-is-multi-day="<?php echo $is_multi_day ? 'true' : 'false'; ?>"
196 - data-pricing-type="<?php echo esc_attr($pricing_type); ?>"
197 - data-availability='<?php echo esc_attr(json_encode($availability_json)); ?>'
198 - data-group-discounts='<?php echo esc_attr(json_encode($sidebar_group_discounts_data)); ?>'>
124 + <div class="yatra-booking-card">
199 125
200 - <?php if ($sidebar_has_group_discounts): ?>
201 - <!-- Group Discount Badge - Minimal with Tooltip -->
202 - <div class="yatra-group-discount-badge-minimal yatra-has-tooltip">
203 - <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
204 - <path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path>
205 - <circle cx="9" cy="7" r="4"></circle>
206 - <path d="M23 21v-2a4 4 0 0 0-3-3.87"></path>
207 - <path d="M16 3.13a4 4 0 0 1 0 7.75"></path>
208 - </svg>
209 - <span><?php echo esc_html__('Group discount available', 'yatra'); ?></span>
210 - <svg class="yatra-info-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
211 - <circle cx="12" cy="12" r="10"></circle>
212 - <line x1="12" y1="16" x2="12" y2="12"></line>
213 - <line x1="12" y1="8" x2="12.01" y2="8"></line>
214 - </svg>
215 - <div class="yatra-tooltip">
216 - <div class="yatra-tooltip-header">
217 - <strong><?php echo esc_html__('Group Discount Tiers', 'yatra'); ?></strong>
218 - </div>
219 - <div class="yatra-tooltip-content">
220 - <?php if (!empty($sidebar_group_discounts_data) && is_array($sidebar_group_discounts_data)): ?>
221 - <?php foreach ($sidebar_group_discounts_data as $range): ?>
222 - <div class="yatra-discount-tier">
223 - <span class="yatra-tier-size">
224 - <?php
225 - $min = (int) ($range['min_group_size'] ?? 0);
226 - $max = isset($range['max_group_size']) && $range['max_group_size'] > 0 ? (int) $range['max_group_size'] : null;
227 - if ($max) {
228 - echo sprintf(esc_html__('%d-%d travelers', 'yatra'), $min, $max);
229 - } else {
230 - echo sprintf(esc_html__('%d+ travelers', 'yatra'), $min);
231 - }
232 - ?>
233 - </span>
234 - <span class="yatra-tier-discount">
235 - <?php
236 - $discount_type = $range['discount_type'] ?? 'percentage';
237 - $discount_value = (float) ($range['discount_amount'] ?? $range['discount_percentage'] ?? 0);
238 -
239 - if ($discount_type === 'percentage') {
240 - echo esc_html(sprintf(__('%s%% OFF', 'yatra'), number_format($discount_value, 1)));
241 - } else {
242 - echo esc_html(sprintf(__('%s OFF', 'yatra'), yatra_format_price($discount_value)));
243 - }
244 - ?>
245 - </span>
246 - </div>
247 - <?php endforeach; ?>
248 - <?php endif; ?>
249 - </div>
250 - </div>
251 - </div>
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 + ?>
252 133 <?php endif; ?>
253 134
254 135 <!-- Price Display -->
255 136 <div class="yatra-booking-price">
256 - <?php if ($discount['has_discount']): ?>
137 + <?php if ($discount['has_discount'] && $dp_show_savings_sidebar): ?>
257 138 <div class="yatra-booking-discount-badge">
258 139 <?php echo esc_html($discount['discount_text']); ?>
259 140 </div>
260 141 <?php endif; ?>
@@ -262,16 +143,22 @@
262 143 <?php if ($pricing['has_price']): ?>
263 144 <?php if ($has_availability || $has_traveler_pricing): ?>
264 145 <span class="yatra-booking-price-label-top"><?php echo esc_html__('From', 'yatra'); ?></span>
265 146 <?php endif; ?>
266 - <?php if ($pricing['has_discount'] && !empty($pricing['original_price'])): ?>
147 + <?php if ($pricing['has_discount'] && !empty($pricing['original_price']) && $show_strike_original_sidebar): ?>
267 148 <span class="yatra-booking-price-original"><?php echo esc_html($pricing['original_price']); ?></span>
268 149 <?php endif; ?>
269 150 <span class="yatra-booking-price-amount" id="display-price"><?php echo esc_html($pricing['current_price']); ?></span>
270 - <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; ?>
271 159 <?php else: ?>
272 160 <span class="yatra-booking-price-amount yatra-contact-pricing" id="display-price"><?php echo esc_html__('Contact for pricing', 'yatra'); ?></span>
273 - <span class="yatra-booking-price-label"><?php echo esc_html__('per person', 'yatra'); ?></span>
274 161 <?php endif; ?>
275 162 </div>
276 163 </div>
277 164
@@ -280,25 +167,130 @@
280 167 <!-- DATE-SPECIFIC BOOKING (Has Availability Dates/Rules) -->
281 168 <!-- ========================================== -->
282 169 <div class="yatra-availability-info">
283 170 <span class="yatra-availability-count">
284 - <?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 + ?>
285 262 </span>
286 263 </div>
287 264
288 265 <form class="yatra-booking-form" data-booking-mode="date_specific">
289 266 <!-- Date Selection -->
290 - <div class="yatra-booking-field-select">
267 + <div class="yatra-booking-field-select<?php echo $yatra_use_date_dropdown ? ' yatra-date-as-dropdown' : ''; ?>">
291 268 <div class="yatra-booking-field-icon">
292 269 <?php echo yatra_svg_icon('calendar', 'yatra-icon-sm'); ?>
293 270 </div>
294 - <input type="text"
295 - id="travel_date"
296 - name="travel_date"
297 - class="yatra-booking-select yatra-datepicker"
298 - placeholder="<?php esc_attr_e('Select date', 'yatra'); ?>"
299 - readonly
300 - 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; ?>
301 293 <svg class="yatra-select-arrow" fill="none" stroke="currentColor" viewBox="0 0 24 24">
302 294 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
303 295 </svg>
304 296 </div>
@@ -339,10 +331,9 @@
339 331 $value = 1;
340 332 $min = $trip->getMinTravelers() ?: 1;
341 333 $max = $trip->getMaxTravelers() ?: 20;
342 334 $data_price = $base_price;
343 - $range_text = sprintf(__('Min: %d, Max: %d', 'yatra'), $min, $max);
344 -
335 +
345 336 include YATRA_PLUGIN_PATH . 'templates/partials/traveler-input-simple.php';
346 337 ?>
347 338 </div>
348 339 <?php endif; ?>
@@ -357,18 +348,24 @@
357 348 <?php if (!empty($trip->getAvailableFrom()) || !empty($trip->getAvailableTo())): ?>
358 349 <span class="yatra-availability-note">
359 350 <?php
360 351 if (!empty($trip->getAvailableFrom()) && !empty($trip->getAvailableTo())) {
361 - 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'),
362 355 date_i18n(get_option('date_format'), strtotime($trip->getAvailableFrom())),
363 356 date_i18n(get_option('date_format'), strtotime($trip->getAvailableTo()))
364 357 ));
365 358 } elseif (!empty($trip->getAvailableFrom())) {
366 - echo esc_html(sprintf(__('Available from: %s', 'yatra'),
359 + echo esc_html(sprintf(
360 + /* translators: %s: available-from date. */
361 + __('Available from: %s', 'yatra'),
367 362 date_i18n(get_option('date_format'), strtotime($trip->getAvailableFrom()))
368 363 ));
369 364 } else {
370 - echo esc_html(sprintf(__('Available until: %s', 'yatra'),
365 + echo esc_html(sprintf(
366 + /* translators: %s: available-until date. */
367 + __('Available until: %s', 'yatra'),
371 368 date_i18n(get_option('date_format'), strtotime($trip->getAvailableTo()))
372 369 ));
373 370 }
374 371 ?>
@@ -377,21 +374,45 @@
377 374 </div>
378 375
379 376 <form class="yatra-booking-form" data-booking-mode="flexible">
380 377 <!-- Date Selection (Flexible) -->
381 - <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' : ''; ?>">
382 388 <div class="yatra-booking-field-icon">
383 389 <?php echo yatra_svg_icon('calendar', 'yatra-icon-sm'); ?>
384 390 </div>
385 - <input type="text"
386 - id="travel_date"
387 - name="travel_date"
388 - class="yatra-booking-select yatra-datepicker"
389 - placeholder="<?php esc_attr_e('Select date', 'yatra'); ?>"
390 - data-min-date="<?php echo esc_attr($trip->getAvailableFrom() ?: date('Y-m-d')); ?>"
391 - data-max-date="<?php echo esc_attr($trip->getAvailableTo() ?: ''); ?>"
392 - readonly
393 - 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; ?>
394 415 <svg class="yatra-select-arrow" fill="none" stroke="currentColor" viewBox="0 0 24 24">
395 416 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
396 417 </svg>
397 418 </div>
@@ -432,10 +453,9 @@
432 453 $value = 1;
433 454 $min = $trip->getMinTravelers() ?: 1;
434 455 $max = $trip->getMaxTravelers() ?: 20;
435 456 $data_price = $base_price;
436 - $range_text = sprintf(__('Min: %d, Max: %d', 'yatra'), $min, $max);
437 -
457 +
438 458 include YATRA_PLUGIN_PATH . 'templates/partials/traveler-input-simple.php';
439 459 ?>
440 460 </div>
441 461 <?php endif; ?>
@@ -455,8 +475,12 @@
455 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"/>
456 476 </svg>
457 477 <?php echo esc_html__('Make Enquiry', 'yatra'); ?>
458 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 + ?>
459 483
460 484 <!-- Cancellation Policy Trust Signal -->
461 485 <?php
462 486 $cancellation_policy = '';
@@ -474,9 +498,10 @@
474 498 if (preg_match('/(\d+)\s*(hour|day|week)s?\s*before/i', $cancellation_policy, $matches)) {
475 499 $time_value = $matches[1];
476 500 $time_unit = $matches[2];
477 501 $cancellation_text = sprintf(
478 - __('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'),
479 504 $time_value,
480 505 $time_unit,
481 506 $time_value > 1 ? 's' : ''
482 507 );