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/booking-content.php +155 -54 3.0.2.6 → 3.0.16 View file →
@@ -142,21 +142,24 @@
142 142 >
143 143 <?php
144 144 // Pass pricing type info to form fields partial
145 145 // Prefer booking pricing data, fallback to trip definition
146 + $trip_pricing_mode = \Yatra\Services\TripPricingService::resolvePricingType($trip);
146 147 $pricing_type = !empty($booking->pricing_type) ? $booking->pricing_type : ($trip->pricing_type ?? 'regular');
147 148 $price_types = !empty($booking->price_types) ? $booking->price_types : ($trip->price_types ?? []);
149 + if ($trip_pricing_mode === 'regular' && empty($booking->price_types)) {
150 + $pricing_type = 'regular';
151 + $price_types = [];
152 + }
148 153 if (empty($pricing_type) && !empty($price_types)) {
149 154 $pricing_type = 'traveler_based';
150 155 }
151 - if ($pricing_type === 'regular' && !empty($price_types)) {
152 - // Infer traveler-based if price types are present but flag not set
153 - $pricing_type = 'traveler_based';
154 - }
155 - // Ensure price_types is always the trip's price_types if booking lacks it
156 - if (empty($price_types) && !empty($trip->price_types)) {
156 + // Do not override explicit regular trip pricing with orphan trip_price_types rows
157 + if (empty($price_types) && !empty($trip->price_types) && $trip_pricing_mode === 'traveler_based') {
157 158 $price_types = $trip->price_types;
158 - $pricing_type = 'traveler_based';
159 + if (empty($pricing_type) || $pricing_type === 'regular') {
160 + $pricing_type = 'traveler_based';
161 + }
159 162 }
160 163 $traveler_counts = $booking->traveler_counts ?? [];
161 164
162 165 // Include shared booking form fields
@@ -172,9 +175,9 @@
172 175 </svg>
173 176 <span id="pay-button-text"><?php esc_html_e('Complete Booking', 'yatra'); ?></span>
174 177 <span id="pay-amount"><?php echo esc_html(yatra_format_price((float) ($summary_due_amount ?? 0), null, false)); ?></span>
175 178 </button>
176 - <a href="<?php echo esc_url(home_url('/trip/' . $trip->slug)); ?>" class="yatra-booking-cancel-btn">
179 + <a href="<?php echo esc_url(yatra_get_trip_permalink($trip)); ?>" class="yatra-booking-cancel-btn">
177 180 <?php esc_html_e('Cancel', 'yatra'); ?>
178 181 </a>
179 182 </div>
180 183 <div class="yatra-booking-form-errors" id="yatra-booking-form-errors" aria-live="polite"></div>
@@ -299,12 +302,12 @@
299 302 <div class="yatra-summary-form-group">
300 303 <label for="travel-date"><?php esc_html_e('Travel Date', 'yatra'); ?></label>
301 304 <div class="yatra-summary-readonly-field">
302 305 <?php if ($is_day_trip && !empty($departure_time)): ?>
303 - <span class="yatra-date-display"><?php echo esc_html(date_i18n('d M Y', strtotime($travel_date))); ?></span>
306 + <span class="yatra-date-display"><?php echo esc_html(\Yatra\Helpers\FormatHelper::formatDate((string) $travel_date)); ?></span>
304 307 <span class="yatra-time-display"><?php echo esc_html(\Yatra\Helpers\FormatHelper::formatTimeForDisplay($departure_time)); ?></span>
305 308 <?php else: ?>
306 - <?php echo esc_html(date_i18n('d M Y', strtotime($travel_date))); ?>
309 + <?php echo esc_html(\Yatra\Helpers\FormatHelper::formatDate((string) $travel_date)); ?>
307 310 <?php endif; ?>
308 311 </div>
309 312 <input type="hidden" id="travel-date" name="travel_date" form="yatra-booking-form" value="<?php echo esc_attr($travel_date); ?>">
310 313 <?php if (!empty($departure_time)): ?>
@@ -311,9 +314,9 @@
311 314 <input type="hidden" name="departure_time" form="yatra-booking-form" value="<?php echo esc_attr($departure_time); ?>">
312 315 <?php endif; ?>
313 316 </div>
314 317
315 - <?php if (!empty($trip->price_types) || (!empty($price_types) && $pricing_type === 'traveler_based')): ?>
318 + <?php if ($pricing_type === 'traveler_based' && !empty($price_types)): ?>
316 319 <!-- Traveler-based pricing: use shared traveler selector partial -->
317 320 <div class="yatra-summary-form-group yatra-traveler-categories">
318 321 <label><?php esc_html_e('Travelers', 'yatra'); ?></label>
319 322 <?php
@@ -327,12 +330,15 @@
327 330 $pricing_mode = (!empty($pt->pricing_mode) && $pt->pricing_mode === 'per_group') ? 'per_group' : 'per_person';
328 331 $age_info = '';
329 332 if (isset($pt->age_min) || isset($pt->age_max)) {
330 333 if (isset($pt->age_min) && isset($pt->age_max)) {
331 - $age_info = sprintf(__('(Age %d-%d)', 'yatra'), $pt->age_min, $pt->age_max);
334 + /* translators: 1: minimum age, 2: maximum age. */
335 + $age_info = sprintf(__('(Age %1$d-%2$d)', 'yatra'), $pt->age_min, $pt->age_max);
332 336 } elseif (isset($pt->age_min)) {
337 + /* translators: %d: minimum age. */
333 338 $age_info = sprintf(__('(Age %d+)', 'yatra'), $pt->age_min);
334 339 } else {
340 + /* translators: %d: maximum age. */
335 341 $age_info = sprintf(__('(Up to age %d)', 'yatra'), $pt->age_max);
336 342 }
337 343 }
338 344 $input_id = 'traveler_' . $category_id;
@@ -343,17 +349,21 @@
343 349 'row_attrs' => [
344 350 'data-category-id' => $category_id,
345 351 'data-price' => $category_price,
346 352 'data-pricing-mode' => $pricing_mode,
353 + 'data-group-overflow' => $pt->group_overflow ?? 'block',
354 + 'data-max-pax' => $pt->max_pax ?? '',
347 355 ],
348 356 'minus_disabled' => ($index !== 0 && $count <= 0),
349 357 'plus_disabled' => false,
350 358 'minus_attrs' => [
351 359 'data-target' => $input_id,
360 + /* translators: %s: traveler category label (e.g. "Adult", "Child"). */
352 361 'aria-label' => sprintf(__('Decrease %s', 'yatra'), $category_label),
353 362 ],
354 363 'plus_attrs' => [
355 364 'data-target' => $input_id,
365 + /* translators: %s: traveler category label (e.g. "Adult", "Child"). */
356 366 'aria-label' => sprintf(__('Increase %s', 'yatra'), $category_label),
357 367 ],
358 368 'input_attrs' => [
359 369 'id' => $input_id,
@@ -363,8 +373,10 @@
363 373 'max' => $trip->max_travelers ?: 20,
364 374 'data-category-label' => $category_label,
365 375 'data-price' => $category_price,
366 376 'data-pricing-mode' => $pricing_mode,
377 + 'data-group-overflow' => $pt->group_overflow ?? 'block',
378 + 'data-max-pax' => $pt->max_pax ?? '',
367 379 ],
368 380 ];
369 381 }
370 382 $traveler_display_text = !empty($traveler_rows) ? ($traveler_rows[0]['label'] . ' x ' . max(1, (int) $traveler_rows[0]['input_attrs']['value'])) : __('Traveler x 1', 'yatra');
@@ -415,10 +427,13 @@
415 427 <div class="yatra-summary-meta">
416 428 <?php
417 429 $duration_days = (int) ($trip->duration_days ?? 0);
418 430 $duration_nights = isset($trip->duration_nights) ? (int) $trip->duration_nights : null;
431 + $duration_hours = (int) ($trip->duration_hours ?? 0);
419 432 ?>
420 - <?php if ($duration_days > 0) : ?>
433 + <?php if ($duration_hours > 0) : ?>
434 + <span><?php echo esc_html(yatra_format_duration(0, null, $duration_hours)); ?></span>
435 + <?php elseif ($duration_days > 0) : ?>
421 436 <?php if ($duration_days <= 1) : ?>
422 437 <span><?php esc_html_e('Day Trip', 'yatra'); ?></span>
423 438 <?php else : ?>
424 439 <span><?php echo esc_html(yatra_format_duration($duration_days, $duration_nights)); ?></span>
@@ -423,11 +438,37 @@
423 438 <?php else : ?>
424 439 <span><?php echo esc_html(yatra_format_duration($duration_days, $duration_nights)); ?></span>
425 440 <?php endif; ?>
426 441 <?php endif; ?>
427 - <?php if (!empty($trip->difficulty_level)) : ?>
442 + <?php
443 + // Resolve difficulty_level to a human label.
444 + // The trip row stores difficulty_level as the
445 + // classification FK id (e.g. 32), and that's
446 + // what we'd otherwise render — "32" on the
447 + // sidebar. Prefer the joined `difficulty_name`
448 + // when present; fall back to a one-row lookup
449 + // in wp_yatra_classifications when only the
450 + // id arrived; finally fall back to the raw
451 + // string (legacy trips that stored
452 + // 'easy'/'moderate' before the FK migration).
453 + $difficulty_label = '';
454 + $difficulty_raw = $trip->difficulty_level ?? null;
455 + if (!empty($trip->difficulty_name)) {
456 + $difficulty_label = (string) $trip->difficulty_name;
457 + } elseif (is_numeric($difficulty_raw) && (int) $difficulty_raw > 0) {
458 + global $wpdb;
459 + $classifications_table = \Yatra\Database\Tables\ClassificationsTable::getTableName();
460 + $difficulty_label = (string) $wpdb->get_var($wpdb->prepare(
461 + "SELECT name FROM {$classifications_table} WHERE id = %d LIMIT 1",
462 + (int) $difficulty_raw
463 + ));
464 + } elseif (is_string($difficulty_raw) && $difficulty_raw !== '') {
465 + $difficulty_label = ucfirst($difficulty_raw);
466 + }
467 + ?>
468 + <?php if ($difficulty_label !== '') : ?>
428 469 <span>•</span>
429 - <span><?php echo esc_html(ucfirst($trip->difficulty_level)); ?></span>
470 + <span><?php echo esc_html($difficulty_label); ?></span>
430 471 <?php endif; ?>
431 472 </div>
432 473 </div>
433 474 </div>
@@ -522,11 +563,33 @@
522 563 </div>
523 564 </div>
524 565 <?php endif; ?>
525 566
526 - <!-- Coupon Code Section -->
567 + <!-- Coupon Code Section
568 + Server-side checks whether a coupon is already
569 + applied to this booking session so the correct UI
570 + (toggle-to-enter vs applied-with-remove) is rendered
571 + on the FIRST byte. Without this, the page always
572 + shipped with the applied-coupon block `display:none`
573 + and relied on the JS `loadCouponFromSession()` GET
574 + to flip the visibility — which fails on page refresh
575 + in REST contexts where PHPSESSID doesn't propagate,
576 + leaving the user without a remove button. -->
577 + <?php
578 + $applied_coupon = null;
579 + if (!empty($booking) && is_object($booking)) {
580 + $session_for_coupon = yatra_get_booking_session();
581 + if (is_array($session_for_coupon) && !empty($session_for_coupon['coupon']['code'])) {
582 + $applied_coupon = $session_for_coupon['coupon'];
583 + }
584 + }
585 + $coupon_is_applied = !empty($applied_coupon);
586 + $coupon_discount_amount_for_display = $coupon_is_applied
587 + ? (float) ($applied_coupon['discount_amount'] ?? $applied_coupon['calculated_amount'] ?? 0)
588 + : 0.0;
589 + ?>
527 590 <div class="yatra-coupon-section">
528 - <div class="yatra-coupon-toggle">
591 + <div class="yatra-coupon-toggle" <?php echo $coupon_is_applied ? 'style="display: none;"' : ''; ?>>
529 592 <button type="button" id="yatra-coupon-toggle-btn" class="yatra-coupon-toggle-btn">
530 593 <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
531 594 <path d="M20 12v6a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2v-6"></path>
532 595 <path d="M12 2v10"></path>
@@ -548,17 +611,19 @@
548 611 </button>
549 612 </div>
550 613 <div id="yatra-coupon-message" class="yatra-coupon-message" style="display: none;"></div>
551 614 </div>
552 - <!-- Applied Coupon Display -->
553 - <div class="yatra-applied-coupon" id="yatra-applied-coupon" style="display: none;">
615 + <!-- Applied Coupon Display — rendered visible on first paint when the
616 + session already has a coupon, so the remove button is reachable
617 + even before any JS runs. -->
618 + <div class="yatra-applied-coupon" id="yatra-applied-coupon" <?php echo $coupon_is_applied ? '' : 'style="display: none;"'; ?>>
554 619 <div class="yatra-applied-coupon-info">
555 620 <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
556 621 <path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path>
557 622 <polyline points="22 4 12 14.01 9 11.01"></polyline>
558 623 </svg>
559 - <span class="yatra-coupon-code-display"></span>
560 - <span class="yatra-coupon-discount"></span>
624 + <span class="yatra-coupon-code-display"><?php echo $coupon_is_applied ? esc_html($applied_coupon['code']) : ''; ?></span>
625 + <span class="yatra-coupon-discount"><?php echo $coupon_is_applied && $coupon_discount_amount_for_display > 0 ? '-' . esc_html(yatra_format_price($coupon_discount_amount_for_display)) : ''; ?></span>
561 626 </div>
562 627 <button type="button" id="yatra-remove-coupon" class="yatra-remove-coupon-btn" title="<?php esc_attr_e('Remove coupon', 'yatra'); ?>">
563 628 <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
564 629 <line x1="18" y1="6" x2="6" y2="18"></line>
@@ -567,9 +632,16 @@
567 632 </button>
568 633 </div>
569 634 </div>
570 635
571 - <!-- Additional Services Section (Premium Feature) -->
636 + <!-- Additional Services Section (Premium Feature)
637 + Renders as its own card BEFORE the Pricing Summary
638 + (after the coupon code section). Checkbox toggles fire
639 + the change handler in booking.js → updateServicesInSession()
640 + → /booking/session POST → schedulePricingSummaryRefresh()
641 + → /booking/summary GET → re-render the pricing-summary
642 + partial below, which then shows the selected services
643 + as compact line items in the price breakdown. -->
572 644 <?php
573 645 /**
574 646 * Filter: Get additional services for this trip
575 647 * Allows premium modules to add extra services to the booking
@@ -574,15 +646,15 @@
574 646 * Filter: Get additional services for this trip
575 647 * Allows premium modules to add extra services to the booking
576 648 */
577 649 $additional_services = apply_filters('yatra_booking_additional_services', [], $trip_id, $total_travelers, $traveler_counts ?? [], $travel_date);
578 -
650 +
579 651 // Get selected services from session (set from popup)
580 652 $session = yatra_get_booking_session();
581 - $selected_service_ids = isset($session['additional_services']) && is_array($session['additional_services'])
582 - ? array_map('intval', $session['additional_services'])
653 + $selected_service_ids = isset($session['additional_services']) && is_array($session['additional_services'])
654 + ? array_map('intval', $session['additional_services'])
583 655 : [];
584 -
656 +
585 657 if (!empty($additional_services)) :
586 658 ?>
587 659 <div class="yatra-additional-services-section">
588 660 <div class="yatra-services-header">
@@ -589,53 +661,71 @@
589 661 <h4><?php esc_html_e('Additional Services', 'yatra'); ?></h4>
590 662 <p class="yatra-services-subtitle"><?php esc_html_e('Enhance your trip with these optional add-ons', 'yatra'); ?></p>
591 663 </div>
592 664 <div class="yatra-services-list" id="yatra-additional-services">
593 - <?php foreach ($additional_services as $service) :
665 + <?php foreach ($additional_services as $service) :
594 666 $service_id = (int) $service['id'];
595 667 $service_name = $service['name'];
596 - $service_description = $service['description'] ?? '';
668 + $service_description = (string) ($service['description'] ?? '');
597 669 $service_price = (float) $service['price'];
598 670 $service_price_per = $service['price_per'] ?? 'person';
599 - $service_image = $service['image_url'] ?? '';
600 - // Check is_required - handle various formats (bool, string "1", int 1)
671 + $service_price_type = $service['price_type'] ?? 'fixed';
601 672 $is_required = isset($service['is_required']) && ($service['is_required'] === true || $service['is_required'] === 1 || $service['is_required'] === '1');
602 673 $is_included = isset($service['is_included']) && ($service['is_included'] === true || $service['is_included'] === 1 || $service['is_included'] === '1');
603 674 $is_selected = in_array($service_id, $selected_service_ids, false);
604 -
605 - $price_label = yatra_format_price($service_price);
606 - if ($service_price_per === 'person') {
607 - $price_label .= ' ' . __('per person', 'yatra');
608 - } elseif ($service_price_per === 'day') {
609 - $price_label .= ' ' . __('per day', 'yatra');
675 +
676 + // Percentage services show "X% of trip price" (the actual
677 + // dollar amount is rendered in the price-breakdown summary).
678 + // Price Per does NOT apply to percentage (it's a % of the
679 + // whole trip), so no per-person/day/booking suffix is added.
680 + // Fixed services show the formatted amount plus the suffix.
681 + if ($service_price_type === 'percentage') {
682 + $pct_display = rtrim(rtrim(number_format($service_price, 2), '0'), '.');
683 + /* translators: %s: percentage value, e.g. "17" */
684 + $price_label = sprintf(__('%s%% of trip price', 'yatra'), $pct_display);
610 685 } else {
611 - $price_label .= ' ' . __('per booking', 'yatra');
686 + $price_label = yatra_format_price($service_price);
687 + if ($service_price_per === 'person') {
688 + $price_label .= ' ' . __('per person', 'yatra');
689 + } elseif ($service_price_per === 'day') {
690 + $price_label .= ' ' . __('per day', 'yatra');
691 + } else {
692 + $price_label .= ' ' . __('per booking', 'yatra');
693 + }
612 694 }
613 695 if ($is_included) {
614 696 $price_label = __('Included', 'yatra');
615 697 }
616 -
617 - // Determine checked and disabled states
698 +
618 699 $is_checked = $is_required || $is_included || $is_selected;
619 700 $is_disabled = $is_required || $is_included;
620 701 ?>
702 + <?php
703 + // Description shown as a hover tooltip on the
704 + // whole row (no separate ⓘ icon). `title` lives
705 + // on the outer .yatra-service-item so hovering
706 + // anywhere in the block reveals the tooltip —
707 + // the small icon variant was easy to miss.
708 + $plain_description = $service_description !== '' ? wp_strip_all_tags($service_description) : '';
709 + ?>
621 710 <div class="yatra-service-item <?php echo $is_required ? 'yatra-service-required' : ''; ?> <?php echo $is_checked ? 'yatra-service-selected' : ''; ?>"
622 - data-service-id="<?php echo esc_attr($service_id); ?>"
623 - data-service-price="<?php echo esc_attr($service_price); ?>"
711 + data-service-id="<?php echo esc_attr((string) $service_id); ?>"
712 + data-service-price="<?php echo esc_attr((string) $service_price); ?>"
624 713 data-service-price-per="<?php echo esc_attr($service_price_per); ?>"
625 - data-is-required="<?php echo $is_required ? '1' : '0'; ?>">
714 + data-is-required="<?php echo $is_required ? '1' : '0'; ?>"
715 + <?php if ($plain_description !== '') : ?>
716 + data-tooltip="<?php echo esc_attr($plain_description); ?>"
717 + aria-label="<?php echo esc_attr($plain_description); ?>"
718 + <?php endif; ?>>
626 719 <label class="yatra-service-label">
627 - <input type="checkbox"
628 - name="additional_services[]"
629 - value="<?php echo esc_attr($service_id); ?>"
720 + <input type="checkbox"
721 + name="additional_services[]"
722 + value="<?php echo esc_attr((string) $service_id); ?>"
630 723 <?php echo $is_checked ? 'checked' : ''; ?>
631 724 <?php echo $is_disabled ? 'disabled' : ''; ?>
632 725 form="yatra-booking-form">
633 726 <div class="yatra-service-info">
634 727 <span class="yatra-service-name"><?php echo esc_html($service_name); ?></span>
635 - <?php if ($service_description) : ?>
636 - <span class="yatra-service-description"><?php echo esc_html($service_description); ?></span>
637 - <?php endif; ?>
638 728 </div>
639 729 <span class="yatra-service-price"><?php echo esc_html($price_label); ?></span>
640 730 </label>
641 731 </div>
@@ -643,20 +733,31 @@
643 733 </div>
644 734 </div>
645 735 <?php endif; ?>
646 736
647 -
648 - <!-- Price Breakdown -->
737 + <!-- Price Breakdown — services + dynamic-pricing summary + totals all live
738 + in one card so the sidebar reads as a single "Pricing Summary" panel. -->
649 739 <div class="yatra-summary-pricing" id="yatra-summary-pricing" data-pricing-type="<?php echo esc_attr($pricing_type); ?>" data-is-remaining="<?php echo esc_attr($is_remaining_payment ? 'yes' : 'no'); ?>">
650 - <!-- This section is loaded/updated via AJAX -->
651 740 <?php
652 741 // Get checkout model from booking object
653 742 $checkout = $booking->checkout ?? null;
654 -
743 +
744 + // Surface the dynamic-pricing breakdown as a template
745 + // variable so the partial can render the DP line items.
746 + // CalculationService stuffs the breakdown into
747 + // `pricing_calculation.dynamic_pricing` via the
748 + // `yatra_price_breakdown` filter; without exposing it
749 + // here the partial's DP block stays dark on first load.
750 + $dynamic_pricing = $pricing_calculation['dynamic_pricing'] ?? null;
751 + $currency = $pricing_calculation['currency'] ?? null;
752 +
753 + // The pricing-summary partial calls $render_additional_services()
754 + // at the right spot — right after the traveler/per-person rows
755 + // and before the Trip Subtotal — so the services row shares the
756 + // same compact "label … price" layout as the traveler row.
757 +
655 758 // Include pricing summary template (uses $checkout model)
656 759 include YATRA_PLUGIN_PATH . 'templates/partials/pricing-summary.php';
657 -
658 -
659 760 ?>
660 761 </div>
661 762
662 763 <!-- Cancellation Policy -->