PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.16
Yatra – Travel Booking & Tour Operator Software v3.0.16
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
← All changes | templates/partials/availability-card.php +81 -12 3.0.9 → 3.0.16 View file →
@@ -27,8 +27,29 @@
27 27 if ($is_sold_out || $card_status === 'sold_out') {
28 28 $card_status = 'sold_out';
29 29 }
30 30
31 + // A sold-out departure is only bookable as a WAITLIST entry, and only
32 + // when the server would actually accept it — the same gate
33 + // BookingSessionController applies (WaitlistService::canJoinWaitlist).
34 + // When waitlist is unavailable the booking is rejected at the final
35 + // step, so the Book Now button must be blocked here rather than
36 + // failing late (the reported bug). Probing with 1 traveler is
37 + // sufficient: canJoinWaitlist requires seats_available < N, which for a
38 + // sold-out card (0 seats) holds for any N >= 1.
39 + $waitlist_available = false;
40 + if ($is_sold_out && class_exists('\\Yatra\\Services\\WaitlistService')) {
41 + $waitlist_probe = (object) [
42 + 'id' => (int) ($card['id'] ?? 0),
43 + 'seats_available' => $seats_available,
44 + ];
45 + $waitlist_available = \Yatra\Services\WaitlistService::canJoinWaitlist(
46 + $trip_data,
47 + $waitlist_probe,
48 + 1
49 + );
50 + }
51 +
31 52 // Match Y-m-d even if DB returns datetime (e.g. 2026-08-13 00:00:00)
32 53 $yatra_card_date_norm = static function ($v): string {
33 54 $v = trim((string) $v);
34 55 return preg_match('/^(\d{4}-\d{2}-\d{2})/', $v, $m) ? $m[1] : $v;
@@ -185,15 +206,15 @@
185 206 <div class="yatra-card-header-sub"><?php esc_html_e('per departure', 'yatra'); ?></div>
186 207 </div>
187 208
188 209 <!-- Price (sale only; struck original + % badge sit in .yatra-card-badges-container with status) -->
189 - <div class="yatra-card-header-price">
210 + <div class="yatra-card-header-price" data-price-unit="<?php echo esc_attr($card['price_unit'] ?? 'per_person'); ?>">
190 211 <div class="yatra-card-price-group yatra-card-price-group--stack">
191 212 <?php
192 213 echo '<span class="yatra-sale-price">' . yatra_format_price($display_sale_price) . '</span>';
193 214 ?>
194 215 </div>
195 - <span class="yatra-price-label"><?php echo esc_html__('per person', 'yatra'); ?></span>
216 + <span class="yatra-price-label"><?php echo esc_html($card['price_unit_label'] ?? __('per person', 'yatra')); ?></span>
196 217 </div>
197 218
198 219 <!-- Toggle Arrow -->
199 220 <div class="yatra-card-header-arrow">
@@ -218,9 +239,19 @@
218 239 <?php
219 240 // Prefer the per-card span (matches this card's departure/return
220 241 // dates); fall back to the trip default for sample cards.
221 242 $duration_days = (int) ($card['duration_days'] ?? $trip_data->duration_days ?? 1);
222 - echo esc_html($duration_days . ' ' . _n('Day', 'Days', $duration_days, 'yatra'));
243 + $duration_hours = (int) ($trip_data->duration_hours ?? 0);
244 + if ($duration_hours > 0) {
245 + // Hour-based day tour: "8 Hours", not "1 Day".
246 + echo esc_html(sprintf(
247 + /* translators: %d: number of hours. */
248 + _n('%d Hour', '%d Hours', $duration_hours, 'yatra'),
249 + $duration_hours
250 + ));
251 + } else {
252 + echo esc_html($duration_days . ' ' . _n('Day', 'Days', $duration_days, 'yatra'));
253 + }
223 254 ?>
224 255 </div>
225 256 </div>
226 257 </div>
@@ -332,8 +363,14 @@
332 363 if ($pt_is_per_group && !empty($pt->max_pax)
333 364 && (($pt->group_overflow ?? 'block') !== 'per_block')) {
334 365 $pt_max_qty = (int) min($pt_max_qty, (int) $pt->max_pax);
335 366 }
367 + // A sold-out departure has no seats left to offer, so the
368 + // first category must not still open at 1 traveler. Clamping
369 + // here also disables both +/- buttons below (0 <= 0 >= 0).
370 + if ($pt_default > $pt_max_qty) {
371 + $pt_default = $pt_max_qty;
372 + }
336 373
337 374 // Determine price with fallback chain
338 375 $pt_price = 0;
339 376 if (isset($pt->effective_price) && $pt->effective_price > 0) {
@@ -484,8 +521,17 @@
484 521 include YATRA_PLUGIN_PATH . 'templates/partials/traveler-selector.php';
485 522 ?>
486 523 <?php else: ?>
487 524 <!-- Regular pricing: Show simple number of travelers -->
525 + <?php
526 + // A sold-out departure offers no seats, so the counter must start
527 + // at 0 (not 1) and both controls stay disabled. Only the ceiling
528 + // was capacity-aware before, which left sold-out cards showing a
529 + // selectable traveler.
530 + $simple_max_qty = (int) min($seats_available, $max_travelers);
531 + $simple_qty = $simple_max_qty > 0 ? 1 : 0;
532 + $simple_min_qty = $simple_qty;
533 + ?>
488 534 <div class="yatra-booking-travelers-simple yatra-availability-travelers-simple" data-item="<?php echo esc_attr($item_id); ?>">
489 535 <div class="yatra-booking-field-icon">
490 536 <?php echo yatra_svg_icon('users', 'yatra-icon-sm'); ?>
491 537 </div>
@@ -494,18 +540,18 @@
494 540 <svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
495 541 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 12H4"/>
496 542 </svg>
497 543 </button>
498 - <input type="number"
499 - id="num-travelers-<?php echo esc_attr($item_id); ?>"
500 - class="yatra-quantity-input-simple yatra-availability-num-travelers"
544 + <input type="number"
545 + id="num-travelers-<?php echo esc_attr($item_id); ?>"
546 + class="yatra-quantity-input-simple yatra-availability-num-travelers"
501 547 data-item="<?php echo esc_attr($item_id); ?>"
502 - value="1"
503 - min="1"
504 - max="<?php echo esc_attr(min($seats_available, $max_travelers)); ?>"
548 + value="<?php echo esc_attr($simple_qty); ?>"
549 + min="<?php echo esc_attr($simple_min_qty); ?>"
550 + max="<?php echo esc_attr($simple_max_qty); ?>"
505 551 readonly
506 552 data-price="<?php echo esc_attr($display_sale_price); ?>">
507 - <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'); ?>">
553 + <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'); ?>"<?php echo $simple_qty >= $simple_max_qty ? ' disabled' : ''; ?>>
508 554 <svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
509 555 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"/>
510 556 </svg>
511 557 </button>
@@ -560,10 +606,27 @@
560 606
561 607 // Is this a day trip?
562 608 $is_card_day_trip = !empty($card['is_day_trip']) || !empty($card['date_display']);
563 609 ?>
564 - <button type="button"
565 - class="yatra-card-book-btn"
610 + <?php if ($is_sold_out && !$waitlist_available): ?>
611 + <?php // Sold out and no waitlist available: block booking here.
612 + // The server would reject this at the final step
613 + // (BookingSessionController → code 'sold_out'), so we
614 + // disable the button instead of letting the customer go
615 + // all the way through and fail. ?>
616 + <button type="button"
617 + class="yatra-card-book-btn yatra-card-book-btn--sold-out"
618 + disabled
619 + aria-disabled="true"
620 + data-item="<?php echo esc_attr($item_id); ?>">
621 + <span class="yatra-card-book-btn-text">
622 + <span class="yatra-card-book-btn-title"><?php esc_html_e('Sold Out', 'yatra'); ?></span>
623 + <span class="yatra-card-book-btn-subtitle"><?php esc_html_e('No seats available', 'yatra'); ?></span>
624 + </span>
625 + </button>
626 + <?php else: ?>
627 + <button type="button"
628 + class="yatra-card-book-btn<?php echo ($is_sold_out && $waitlist_available) ? ' yatra-card-book-btn--waitlist' : ''; ?>"
566 629 data-trip-id="<?php echo esc_attr($trip_id); ?>"
567 630 data-availability-id="<?php echo esc_attr($item_id); ?>"
568 631 data-date="<?php echo esc_attr($card['data_date'] ?? ''); ?>"
569 632 data-departure-time="<?php echo esc_attr($card_departure_time); ?>"
@@ -573,11 +636,17 @@
573 636 data-price="<?php echo esc_attr($display_sale_price); ?>"
574 637 data-item="<?php echo esc_attr($item_id); ?>">
575 638 <?php echo yatra_svg_icon('shopping-cart', 'yatra-icon-sm'); ?>
576 639 <span class="yatra-card-book-btn-text">
640 + <?php if ($is_sold_out && $waitlist_available): ?>
641 + <span class="yatra-card-book-btn-title"><?php esc_html_e('Join Waitlist', 'yatra'); ?></span>
642 + <span class="yatra-card-book-btn-subtitle"><?php esc_html_e('Sold out — join the waitlist', 'yatra'); ?></span>
643 + <?php else: ?>
577 644 <span class="yatra-card-book-btn-title"><?php esc_html_e('Book Now', 'yatra'); ?></span>
578 645 <span class="yatra-card-book-btn-subtitle"><?php esc_html_e('Continue to booking', 'yatra'); ?></span>
646 + <?php endif; ?>
579 647 </span>
580 648 </button>
649 + <?php endif; ?>
581 650 </div>
582 651 </div>
583 652 </div>