PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.8
Yatra – Travel Booking & Tour Operator Software v3.0.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 / availability-card.php

availability-card.php in Yatra – Travel Booking & Tour Operator Software 3.0.8, at templates/partials/availability-card.php

582 lines 36.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Single departure card for trip availability (included in a loop).
4 * @package Yatra
5 * @var array $card @var int $index @var object $trip_data @var string $pricing_type @var string $selected_date_filter
6 */
7 defined('ABSPATH') || exit;
8 $card = $card ?? [];
9 $index = (int) ($index ?? 0);
10 $trip_data = $trip_data ?? (object) [];
11 $pricing_type = $pricing_type ?? 'regular';
12 $selected_date_filter = $selected_date_filter ?? '';
13 $trip_id = !empty($trip_data->id) ? (int) $trip_data->id : 0;
14 $max_travelers = (int) ($trip_data->max_travelers ?? 20);
15 $item_id = isset($card['id']) ? (string) $card['id'] : (string) $index;
16
17 // Get pricing from card (already calculated with dynamic pricing in Controller)
18 $sale_price = (float) ($card['sale_price'] ?? 0);
19 $original_price = (float) ($card['original_price'] ?? 0);
20
21 $initial_total_price = $sale_price;
22
23 $seats_available = (int) ($card['seats_available'] ?? 0);
24 $is_limited = !empty($card['is_limited']);
25 $card_status = $card['status'] ?? 'available';
26 $is_sold_out = isset($card['is_sold_out']) && $card['is_sold_out'];
27 if ($is_sold_out || $card_status === 'sold_out') {
28 $card_status = 'sold_out';
29 }
30
31 // Match Y-m-d even if DB returns datetime (e.g. 2026-08-13 00:00:00)
32 $yatra_card_date_norm = static function ($v): string {
33 $v = trim((string) $v);
34 return preg_match('/^(\d{4}-\d{2}-\d{2})/', $v, $m) ? $m[1] : $v;
35 };
36 $sel_norm = $yatra_card_date_norm($selected_date_filter ?? '');
37 $card_date_norm = $yatra_card_date_norm($card['data_date'] ?? '');
38 $is_selected_card = ($sel_norm !== '' && $card_date_norm !== '' && $sel_norm === $card_date_norm);
39 $should_be_open = $is_selected_card || $index === 0;
40
41 // Apply Yatra date format to availability header dates when the controller returns ISO.
42 $yatra_format_card_date_for_display = static function ($value): string {
43 $raw = trim((string) $value);
44 if ($raw === '') {
45 return '';
46 }
47 // Only transform ISO-like values; leave already-human strings unchanged.
48 if (!preg_match('/^\d{4}-\d{2}-\d{2}/', $raw)) {
49 return $raw;
50 }
51 return \Yatra\Helpers\FormatHelper::formatDate($raw);
52 };
53
54 $from_date_display = $yatra_format_card_date_for_display($card['from_date'] ?? '');
55 $to_date_display = $yatra_format_card_date_for_display($card['to_date'] ?? '');
56 $date_display_display = $yatra_format_card_date_for_display($card['date_display'] ?? '');
57
58 $dp_disp = $card['dynamic_pricing_display'] ?? [];
59 $dp_show_original = filter_var($dp_disp['show_original_price'] ?? true, FILTER_VALIDATE_BOOLEAN);
60 $dp_show_savings = filter_var($dp_disp['show_savings_badge'] ?? true, FILTER_VALIDATE_BOOLEAN);
61 $dp_show_urgency = filter_var($dp_disp['show_urgency_messages'] ?? false, FILTER_VALIDATE_BOOLEAN);
62 $dp_urgency_messages = [];
63 if (!empty($card['dynamic_pricing_urgency_messages']) && is_array($card['dynamic_pricing_urgency_messages'])) {
64 $dp_urgency_messages = $card['dynamic_pricing_urgency_messages'];
65 }
66
67 $dp_enabled = (bool) apply_filters('yatra_dynamic_pricing_enabled', false);
68
69 // Display amounts for this card (must match traveler row data-price, including per-category DP).
70 $display_sale_price = $sale_price;
71 $display_original_price = $original_price;
72 $card_pricing_type_header = $card['pricing_type'] ?? $pricing_type ?? 'regular';
73 if ($card_pricing_type_header === 'traveler_based' && !empty($card['traveler_pricing']) && is_array($card['traveler_pricing'])) {
74 $first_traveler = $card['traveler_pricing'][0];
75 $first_traveler = is_array($first_traveler) ? (object) $first_traveler : $first_traveler;
76 $pt_price = 0.0;
77 if (isset($first_traveler->effective_price) && (float) $first_traveler->effective_price > 0) {
78 $pt_price = (float) $first_traveler->effective_price;
79 } elseif (isset($first_traveler->discounted_price) && (float) $first_traveler->discounted_price > 0) {
80 $pt_price = (float) $first_traveler->discounted_price;
81 } elseif (isset($first_traveler->original_price) && (float) $first_traveler->original_price > 0) {
82 $pt_price = (float) $first_traveler->original_price;
83 }
84 if ($dp_enabled && $pt_price > 0) {
85 $pt_orig_dp = (float) ($first_traveler->original_price ?? 0);
86 $pt_disc_dp = (float) ($first_traveler->discounted_price ?? $first_traveler->sale_price ?? $first_traveler->effective_price ?? 0);
87 if ($pt_disc_dp <= 0) {
88 $pt_disc_dp = $pt_price;
89 }
90 $pt_price = (float) apply_filters('yatra_availability_price', $pt_price, $trip_id, [
91 'departure_date' => $card['date'] ?? null,
92 'spots_remaining' => $card['spots_remaining'] ?? null,
93 'availability_id' => $item_id,
94 'price_type_id' => $first_traveler->id ?? ($first_traveler->price_type_id ?? null),
95 'original_price' => $pt_orig_dp > 0 ? $pt_orig_dp : $pt_price,
96 'discounted_price' => $pt_disc_dp > 0 ? $pt_disc_dp : $pt_price,
97 ]);
98 }
99 if ($pt_price > 0) {
100 $display_sale_price = $pt_price;
101 }
102 $fo = (float) ($first_traveler->original_price ?? 0);
103 if ($fo > 0) {
104 $display_original_price = $fo;
105 }
106 }
107 $yatra_av_badges_row_original = $dp_show_original && $display_original_price > $display_sale_price && $display_original_price > 0;
108 $yatra_av_badges_row_savings = $dp_show_savings && !empty($card['discount_text']);
109 $yatra_av_discount_is_surge = $yatra_av_badges_row_savings && strpos((string) $card['discount_text'], '+') === 0;
110 $initial_total_price = $display_sale_price;
111 ?>
112 <div class="yatra-availability-card <?php echo $should_be_open ? 'open' : ''; ?> <?php echo $is_selected_card ? 'selected' : ''; ?> <?php echo $is_limited ? 'limited' : ''; ?> yatra-card-status-<?php echo esc_attr($card_status); ?>"
113 data-availability-id="<?php echo esc_attr($item_id); ?>"
114 data-month="<?php echo esc_attr($card['data_month'] ?? ''); ?>"
115 data-date="<?php echo esc_attr($card['data_date'] ?? ''); ?>"
116 data-price="<?php echo esc_attr($display_sale_price); ?>"
117 data-seats="<?php echo esc_attr($seats_available); ?>"
118 data-item="<?php echo esc_attr($item_id); ?>"
119 data-index="<?php echo esc_attr($index); ?>">
120
121 <!-- Card Header (Clickable) -->
122 <div class="yatra-availability-card-header yatra-availability-toggle" role="button" tabindex="0" aria-label="<?php esc_attr_e('Toggle availability details', 'yatra'); ?>">
123
124 <?php if (!empty($card['date_display'])): ?>
125 <!-- Day Trip: Show date as header -->
126 <div class="yatra-card-date-header">
127 <?php echo yatra_svg_icon('calendar', 'yatra-icon-sm'); ?>
128 <?php echo esc_html($date_display_display); ?>
129 </div>
130 <?php endif; ?>
131
132 <!-- Badge row: own line below day-trip date (when present), above the main grid — avoids overlap with price/seats -->
133 <div class="yatra-card-badges-container">
134 <?php if ($yatra_av_badges_row_original) : ?>
135 <span class="yatra-availability-badge-row__original" title="<?php esc_attr_e('Original price', 'yatra'); ?>"><?php echo yatra_format_price($display_original_price); ?></span>
136 <?php endif; ?>
137 <?php if ($yatra_av_badges_row_savings) : ?>
138 <span class="yatra-card-discount-badge <?php echo $yatra_av_discount_is_surge ? 'yatra-badge-higher' : 'yatra-badge-discount'; ?>"><?php echo esc_html((string) $card['discount_text']); ?></span>
139 <?php endif; ?>
140 <?php if ($dp_show_urgency && !empty($dp_urgency_messages)) : ?>
141 <?php foreach ($dp_urgency_messages as $dp_urgency_line) : ?>
142 <span class="yatra-availability-urgency-message"><?php echo esc_html((string) $dp_urgency_line); ?></span>
143 <?php endforeach; ?>
144 <?php endif; ?>
145 <?php
146 $card_status = $card['status'] ?? 'available';
147 $is_sold_out = isset($card['is_sold_out']) && $card['is_sold_out'];
148
149 if ($is_sold_out || $card_status === 'sold_out') {
150 echo '<div class="yatra-badge-status yatra-badge-sold-out">' . esc_html__('Sold Out', 'yatra') . '</div>';
151 } elseif ($card_status === 'limited') {
152 echo '<div class="yatra-badge-status yatra-badge-limited">' . esc_html__('Limited', 'yatra') . '</div>';
153 } elseif ($card_status === 'blocked') {
154 echo '<div class="yatra-badge-status yatra-badge-blocked">' . esc_html__('Blocked', 'yatra') . '</div>';
155 } elseif ($card_status === 'closed') {
156 echo '<div class="yatra-badge-status yatra-badge-closed">' . esc_html__('Closed', 'yatra') . '</div>';
157 } elseif ($card_status === 'cancelled') {
158 echo '<div class="yatra-badge-status yatra-badge-cancelled">' . esc_html__('Cancelled', 'yatra') . '</div>';
159 } else {
160 echo '<div class="yatra-badge-status yatra-badge-available">' . esc_html__('Available', 'yatra') . '</div>';
161 }
162 ?>
163 </div>
164
165 <div class="yatra-card-header-grid <?php echo !empty($card['date_display']) ? 'yatra-day-trip-grid' : ''; ?>">
166
167 <!-- Start/Departure -->
168 <div class="yatra-card-header-item">
169 <div class="yatra-card-header-label"><?php echo esc_html($card['from_label'] ?? __('Departure', 'yatra')); ?></div>
170 <div class="yatra-card-header-date"><?php echo esc_html($from_date_display); ?></div>
171 <div class="yatra-card-header-location"><?php echo esc_html($card['from_location'] ?? ''); ?></div>
172 </div>
173
174 <!-- End/Return -->
175 <div class="yatra-card-header-item">
176 <div class="yatra-card-header-label"><?php echo esc_html($card['to_label'] ?? __('Return', 'yatra')); ?></div>
177 <div class="yatra-card-header-date"><?php echo esc_html($to_date_display); ?></div>
178 <div class="yatra-card-header-location"><?php echo esc_html($card['to_location'] ?? ''); ?></div>
179 </div>
180
181 <!-- Seats -->
182 <div class="yatra-card-header-item">
183 <div class="yatra-card-header-label"><?php esc_html_e('Seats remaining', 'yatra'); ?></div>
184 <div class="yatra-card-header-seats <?php echo $is_limited ? 'limited' : ''; ?>"><?php echo esc_html($card['seats'] ?? '0'); ?></div>
185 <div class="yatra-card-header-sub"><?php esc_html_e('per departure', 'yatra'); ?></div>
186 </div>
187
188 <!-- Price (sale only; struck original + % badge sit in .yatra-card-badges-container with status) -->
189 <div class="yatra-card-header-price">
190 <div class="yatra-card-price-group yatra-card-price-group--stack">
191 <?php
192 echo '<span class="yatra-sale-price">' . yatra_format_price($display_sale_price) . '</span>';
193 ?>
194 </div>
195 <span class="yatra-price-label"><?php echo esc_html__('per person', 'yatra'); ?></span>
196 </div>
197
198 <!-- Toggle Arrow -->
199 <div class="yatra-card-header-arrow">
200 <svg width="20" height="20" viewBox="0 0 20 20" fill="none">
201 <path d="M5 7.5L10 12.5L15 7.5" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
202 </svg>
203 </div>
204 </div>
205 </div>
206
207 <!-- Card Body (Expandable) -->
208 <div class="yatra-availability-card-body">
209 <div class="yatra-card-body-content">
210 <div class="yatra-card-info-grid">
211 <div class="yatra-card-info-item">
212 <div class="yatra-card-info-icon">
213 <?php echo yatra_svg_icon('calendar', 'yatra-icon-sm'); ?>
214 </div>
215 <div class="yatra-card-info-content">
216 <div class="yatra-card-info-label"><?php esc_html_e('Duration', 'yatra'); ?></div>
217 <div class="yatra-card-info-value">
218 <?php
219 $duration_days = (int) ($trip_data->duration_days ?? 1);
220 echo esc_html($duration_days . ' ' . _n('Day', 'Days', $duration_days, 'yatra'));
221 ?>
222 </div>
223 </div>
224 </div>
225
226 <div class="yatra-card-info-item">
227 <div class="yatra-card-info-icon">
228 <?php echo yatra_svg_icon('users', 'yatra-icon-sm'); ?>
229 </div>
230 <div class="yatra-card-info-content">
231 <div class="yatra-card-info-label"><?php esc_html_e('Seats left', 'yatra'); ?></div>
232 <div class="yatra-card-info-value"><?php
233 /* translators: %d: number of seats remaining. */
234 echo esc_html(sprintf(_n('%d seat', '%d seats', $seats_available, 'yatra'), $seats_available));
235 ?></div>
236 </div>
237 </div>
238
239 <?php
240 $cancellation_policy = '';
241 if (method_exists($trip_data, 'getCancellationPolicy')) {
242 $cancellation_policy = $trip_data->getCancellationPolicy();
243 } elseif (isset($trip_data->cancellation_policy)) {
244 $cancellation_policy = $trip_data->cancellation_policy;
245 }
246
247 $cancellation_label = '';
248 $cancellation_value = '';
249 if (!empty($cancellation_policy)) {
250 // Extract cancellation info from policy
251 $policy_lower = strtolower($cancellation_policy);
252 if (strpos($policy_lower, 'free') !== false || strpos($policy_lower, 'no charge') !== false) {
253 $cancellation_label = __('Free Cancellation', 'yatra');
254 if (preg_match('/(\d+)\s*(hour|day|week)s?\s*before/i', $cancellation_policy, $matches)) {
255 $time_value = $matches[1];
256 $time_unit = $matches[2];
257 $cancellation_value = sprintf(
258 /* translators: 1: numeric amount, 2: time unit (hour/day/week), 3: plural "s" suffix if applicable. */
259 __('Up to %1$d %2$s%3$s before', 'yatra'),
260 $time_value,
261 $time_unit,
262 $time_value > 1 ? 's' : ''
263 );
264 } else {
265 $cancellation_value = __('No charge', 'yatra');
266 }
267 } else {
268 // Non-free cancellation
269 $cancellation_label = __('Cancellation Policy', 'yatra');
270 $cancellation_value = __('See details', 'yatra');
271 }
272 } else {
273 // Default fallback when no policy is set
274 $cancellation_label = __('Cancellation Policy', 'yatra');
275 $cancellation_value = __('May apply', 'yatra');
276 }
277 ?>
278 <div class="yatra-card-info-item">
279 <div class="yatra-card-info-icon">
280 <?php echo yatra_svg_icon('check', 'yatra-icon-sm'); ?>
281 </div>
282 <div class="yatra-card-info-content">
283 <div class="yatra-card-info-label"><?php echo esc_html($cancellation_label); ?></div>
284 <div class="yatra-card-info-value"><?php echo esc_html($cancellation_value); ?></div>
285 </div>
286 </div>
287 </div>
288
289 <div class="yatra-card-traveler-section">
290 <label class="yatra-card-traveler-label"><?php esc_html_e('Travelers', 'yatra'); ?></label>
291
292 <?php
293 // Use card-level pricing type as source of truth
294 $card_pricing_type = $card['pricing_type'] ?? $pricing_type ?? 'regular';
295 $card_price_types = [];
296
297 // Use availability-specific traveler_pricing for traveler-based pricing
298 if ($card_pricing_type === 'traveler_based' && !empty($card['traveler_pricing'])) {
299 $card_price_types = $card['traveler_pricing'];
300 }
301
302 // Normalize traveler pricing to objects if array
303 $normalized_price_types = [];
304 foreach ($card_price_types as $cpt) {
305 if (is_array($cpt)) {
306 $normalized_price_types[] = (object) $cpt;
307 } else {
308 $normalized_price_types[] = $cpt;
309 }
310 }
311 ?>
312
313 <?php if ($card_pricing_type === 'traveler_based' && !empty($normalized_price_types)): ?>
314 <!-- Traveler-based pricing: Show dynamic categories -->
315 <?php
316 // Build traveler rows directly from card-level traveler_pricing data
317 $traveler_rows = [];
318 $display_parts = [];
319 foreach ($normalized_price_types as $pt_index => $pt) {
320 $pt_label = $pt->category_label ?? $pt->label ?? __('Traveler', 'yatra');
321 $pt_pricing_mode = $pt->pricing_mode ?? 'per_person';
322 $pt_is_per_group = ($pt_pricing_mode === 'per_group');
323 $pt_category_id = $pt->category_id ?? $pt_index;
324 $pt_default = $pt_index === 0 ? 1 : 0;
325 $pt_min_qty = 0;
326 $pt_max_qty = (int) min($seats_available, $max_travelers);
327 // Cap a per-group "block" category at its max group
328 // size (still bounded by seats). "per_block" mode may
329 // exceed it (additional group blocks), so skip.
330 if ($pt_is_per_group && !empty($pt->max_pax)
331 && (($pt->group_overflow ?? 'block') !== 'per_block')) {
332 $pt_max_qty = (int) min($pt_max_qty, (int) $pt->max_pax);
333 }
334
335 // Determine price with fallback chain
336 $pt_price = 0;
337 if (isset($pt->effective_price) && $pt->effective_price > 0) {
338 $pt_price = (float) $pt->effective_price;
339 } elseif (isset($pt->discounted_price) && $pt->discounted_price > 0) {
340 $pt_price = (float) $pt->discounted_price;
341 } elseif (isset($pt->original_price) && $pt->original_price > 0) {
342 $pt_price = (float) $pt->original_price;
343 }
344
345 // Apply dynamic pricing to traveler category prices
346 if ($dp_enabled && $pt_price > 0) {
347 $pt_orig_dp = (float) ($pt->original_price ?? 0);
348 $pt_disc_dp = (float) ($pt->discounted_price ?? $pt->sale_price ?? $pt->effective_price ?? 0);
349 if ($pt_disc_dp <= 0) {
350 $pt_disc_dp = $pt_price;
351 }
352 $pt_price = apply_filters('yatra_availability_price', $pt_price, $trip_id, [
353 'departure_date' => $card['date'] ?? null,
354 'spots_remaining' => $card['spots_remaining'] ?? null,
355 'availability_id' => $item_id,
356 'price_type_id' => $pt->id ?? ($pt->price_type_id ?? null),
357 'original_price' => $pt_orig_dp > 0 ? $pt_orig_dp : $pt_price,
358 'discounted_price' => $pt_disc_dp > 0 ? $pt_disc_dp : $pt_price,
359 ]);
360 }
361
362 // Age info
363 $pt_age_text = '';
364 $pt_age_min = isset($pt->age_min) ? (int) $pt->age_min : null;
365 $pt_age_max = isset($pt->age_max) ? (int) $pt->age_max : null;
366 if ($pt_age_min !== null || $pt_age_max !== null) {
367 if ($pt_age_min !== null && $pt_age_max !== null) {
368 /* translators: 1: minimum age, 2: maximum age. */
369 $pt_age_text = sprintf(__('(Age %1$d-%2$d)', 'yatra'), $pt_age_min, $pt_age_max);
370 } elseif ($pt_age_min !== null) {
371 /* translators: %d: minimum age. */
372 $pt_age_text = sprintf(__('(Age %d+)', 'yatra'), $pt_age_min);
373 } else {
374 /* translators: %d: maximum age. */
375 $pt_age_text = sprintf(__('(Up to age %d)', 'yatra'), $pt_age_max);
376 }
377 }
378
379 // Build pricing label for per_group mode
380 $pricing_label = '';
381 if ($pt_is_per_group) {
382 if (!empty($pt->min_pax) && !empty($pt->max_pax)) {
383 /* translators: 1: minimum pax for the group price, 2: maximum pax. */
384 $pricing_label = sprintf(__('per group (%1$d-%2$d pax)', 'yatra'), $pt->min_pax, $pt->max_pax);
385 } elseif (!empty($pt->max_pax)) {
386 /* translators: %d: maximum pax for the group price. */
387 $pricing_label = sprintf(__('per group (up to %d pax)', 'yatra'), $pt->max_pax);
388 } elseif (!empty($pt->min_pax)) {
389 /* translators: %d: minimum pax for the group price. */
390 $pricing_label = sprintf(__('per group (%d+ pax)', 'yatra'), $pt->min_pax);
391 } else {
392 $pricing_label = __('per group', 'yatra');
393 }
394 }
395
396 // Build price HTML
397 $pt_price_html = '';
398 if ($pt_price > 0) {
399 $pt_price_html = '<div class="yatra-quantity-price-wrapper">';
400 $pt_price_html .= '<span class="yatra-quantity-price">' . yatra_format_price($pt_price) . '</span>';
401 if ($pt_is_per_group) {
402 $pt_price_html .= '<span class="yatra-pricing-mode-label yatra-pricing-mode-group">' . esc_html($pricing_label) . '</span>';
403 }
404 $pt_price_html .= '</div>';
405 }
406
407 $input_id = 'traveler_' . $pt_category_id;
408
409 $traveler_rows[] = [
410 'label' => $pt_label,
411 'subtitle' => $pt_age_text,
412 'price_html' => $pt_price_html,
413 'row_attrs' => [
414 'data-category-id' => $pt_category_id,
415 'data-price' => $pt_price,
416 'data-pricing-mode' => $pt_pricing_mode,
417 'data-group-overflow' => $pt->group_overflow ?? 'block',
418 'data-max-pax' => $pt->max_pax ?? '',
419 ],
420 'minus_disabled' => $pt_default <= $pt_min_qty,
421 'plus_disabled' => $pt_default >= $pt_max_qty,
422 'minus_attrs' => [
423 'data-target' => $input_id,
424 'data-item' => $item_id,
425 'data-category' => $pt_category_id,
426 /* translators: %s: traveler category label (e.g. "Adult", "Child"). */
427 'aria-label' => sprintf(__('Decrease %s', 'yatra'), $pt_label),
428 ],
429 'plus_attrs' => [
430 'data-target' => $input_id,
431 'data-item' => $item_id,
432 'data-category' => $pt_category_id,
433 /* translators: %s: traveler category label (e.g. "Adult", "Child"). */
434 'aria-label' => sprintf(__('Increase %s', 'yatra'), $pt_label),
435 ],
436 'input_attrs' => [
437 'id' => $input_id,
438 'name' => 'travelers[' . $pt_category_id . ']',
439 'value' => $pt_default,
440 'min' => $pt_min_qty,
441 'max' => $pt_max_qty,
442 'data-item' => $item_id,
443 'data-category' => $pt_category_id,
444 'data-category-label' => $pt_label,
445 'data-price' => $pt_price,
446 'data-pricing-mode' => $pt_pricing_mode,
447 'data-group-overflow' => $pt->group_overflow ?? 'block',
448 'data-max-pax' => $pt->max_pax ?? '',
449 ],
450 ];
451
452 if ($pt_default > 0) {
453 $display_parts[] = $pt_label . ' x ' . $pt_default;
454 }
455 }
456
457 $traveler_display_text = !empty($display_parts) ? implode(', ', $display_parts) : __('Select travelers', 'yatra');
458
459 // Setup variables for traveler-selector.php using availability-specific data
460 $root_id = '';
461 $root_class = 'yatra-booking-field-select yatra-participants-select yatra-availability-participants';
462 $container_attrs = [
463 'data-item' => $item_id,
464 ];
465
466 $display_id = 'participants-display';
467 $display_class = 'yatra-participants-display yatra-availability-participants-display';
468 $display_attrs = [
469 'data-item' => $item_id,
470 ];
471
472 $dropdown_id = '';
473 $dropdown_class = 'yatra-booking-quantity-selector yatra-availability-quantity-selector';
474 $dropdown_attrs = [
475 'data-item' => $item_id,
476 ];
477
478 $display_text = $traveler_display_text;
479 $icon_html = yatra_svg_icon('users', 'yatra-icon-sm');
480 $rows = $traveler_rows;
481
482 include YATRA_PLUGIN_PATH . 'templates/partials/traveler-selector.php';
483 ?>
484 <?php else: ?>
485 <!-- Regular pricing: Show simple number of travelers -->
486 <div class="yatra-booking-travelers-simple yatra-availability-travelers-simple" data-item="<?php echo esc_attr($item_id); ?>">
487 <div class="yatra-booking-field-icon">
488 <?php echo yatra_svg_icon('users', 'yatra-icon-sm'); ?>
489 </div>
490 <div class="yatra-quantity-controls-inline">
491 <button type="button" class="yatra-quantity-btn yatra-quantity-minus" data-target="num-travelers-<?php echo esc_attr($item_id); ?>" aria-label="<?php esc_attr_e('Decrease travelers', 'yatra'); ?>" disabled>
492 <svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
493 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 12H4"/>
494 </svg>
495 </button>
496 <input type="number"
497 id="num-travelers-<?php echo esc_attr($item_id); ?>"
498 class="yatra-quantity-input-simple yatra-availability-num-travelers"
499 data-item="<?php echo esc_attr($item_id); ?>"
500 value="1"
501 min="1"
502 max="<?php echo esc_attr(min($seats_available, $max_travelers)); ?>"
503 readonly
504 data-price="<?php echo esc_attr($display_sale_price); ?>">
505 <button type="button" class="yatra-quantity-btn yatra-quantity-plus" data-target="num-travelers-<?php echo esc_attr($item_id); ?>" aria-label="<?php esc_attr_e('Increase travelers', 'yatra'); ?>">
506 <svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
507 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"/>
508 </svg>
509 </button>
510 </div>
511 <span class="yatra-travelers-label"><?php esc_html_e('travelers', 'yatra'); ?></span>
512 </div>
513 <?php endif; ?>
514
515 <div class="yatra-card-action-help"><?php esc_html_e('Select travelers to update the total, then click Book Now.', 'yatra'); ?></div>
516 </div>
517 </div>
518
519 <!-- Booking Row at Bottom -->
520 <div class="yatra-card-booking-row">
521 <div class="yatra-card-total-box">
522 <div class="yatra-card-total-label"><?php esc_html_e('Total', 'yatra'); ?></div>
523 <div class="yatra-card-total-note" data-item="<?php echo esc_attr($item_id); ?>"><?php esc_html_e('for 1 traveler', 'yatra'); ?></div>
524 <div class="yatra-card-total-amount" data-item="<?php echo esc_attr($item_id); ?>" data-base-price="<?php echo esc_attr($initial_total_price); ?>">
525 <?php
526 $formatted_total = yatra_format_price($initial_total_price);
527 echo esc_html($formatted_total);
528 ?>
529 </div>
530 </div>
531 <?php
532 // Prepare price_types JSON for JavaScript
533 $price_types_json = '';
534 if (!empty($normalized_price_types)) {
535 $price_types_for_js = array_map(function($pt) {
536 $pt = (object) $pt;
537 return [
538 'category_id' => $pt->category_id ?? null,
539 'category_label' => $pt->category_label ?? $pt->label ?? '',
540 'original_price' => $pt->original_price ?? 0,
541 'sale_price' => $pt->sale_price ?? null,
542 'discounted_price' => $pt->discounted_price ?? null,
543 'effective_price' => $pt->effective_price ?? $pt->sale_price ?? $pt->discounted_price ?? $pt->original_price ?? 0,
544 'age_min' => $pt->age_min ?? null,
545 'age_max' => $pt->age_max ?? null,
546 ];
547 }, $normalized_price_types);
548 $price_types_json = wp_json_encode($price_types_for_js);
549 }
550
551 // Get departure time - prefer card data, fallback to from_date if it looks like time
552 $card_departure_time = '';
553 if (!empty($card['departure_time'])) {
554 $card_departure_time = $card['departure_time'];
555 } elseif (!empty($card['from_date']) && preg_match('/^\d{1,2}:\d{2}\s*(AM|PM)?$/i', $card['from_date'])) {
556 $card_departure_time = $card['from_date'];
557 }
558
559 // Is this a day trip?
560 $is_card_day_trip = !empty($card['is_day_trip']) || !empty($card['date_display']);
561 ?>
562 <button type="button"
563 class="yatra-card-book-btn"
564 data-trip-id="<?php echo esc_attr($trip_id); ?>"
565 data-availability-id="<?php echo esc_attr($item_id); ?>"
566 data-date="<?php echo esc_attr($card['data_date'] ?? ''); ?>"
567 data-departure-time="<?php echo esc_attr($card_departure_time); ?>"
568 data-is-day-trip="<?php echo $is_card_day_trip ? '1' : '0'; ?>"
569 data-pricing-type="<?php echo esc_attr($card_pricing_type); ?>"
570 data-price-types="<?php echo esc_attr($price_types_json); ?>"
571 data-price="<?php echo esc_attr($display_sale_price); ?>"
572 data-item="<?php echo esc_attr($item_id); ?>">
573 <?php echo yatra_svg_icon('shopping-cart', 'yatra-icon-sm'); ?>
574 <span class="yatra-card-book-btn-text">
575 <span class="yatra-card-book-btn-title"><?php esc_html_e('Book Now', 'yatra'); ?></span>
576 <span class="yatra-card-book-btn-subtitle"><?php esc_html_e('Continue to booking', 'yatra'); ?></span>
577 </span>
578 </button>
579 </div>
580 </div>
581 </div>
582