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 +141 -43 3.0.3 → 3.0.16 View file →
@@ -330,12 +330,15 @@
330 330 $pricing_mode = (!empty($pt->pricing_mode) && $pt->pricing_mode === 'per_group') ? 'per_group' : 'per_person';
331 331 $age_info = '';
332 332 if (isset($pt->age_min) || isset($pt->age_max)) {
333 333 if (isset($pt->age_min) && isset($pt->age_max)) {
334 - $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);
335 336 } elseif (isset($pt->age_min)) {
337 + /* translators: %d: minimum age. */
336 338 $age_info = sprintf(__('(Age %d+)', 'yatra'), $pt->age_min);
337 339 } else {
340 + /* translators: %d: maximum age. */
338 341 $age_info = sprintf(__('(Up to age %d)', 'yatra'), $pt->age_max);
339 342 }
340 343 }
341 344 $input_id = 'traveler_' . $category_id;
@@ -346,17 +349,21 @@
346 349 'row_attrs' => [
347 350 'data-category-id' => $category_id,
348 351 'data-price' => $category_price,
349 352 'data-pricing-mode' => $pricing_mode,
353 + 'data-group-overflow' => $pt->group_overflow ?? 'block',
354 + 'data-max-pax' => $pt->max_pax ?? '',
350 355 ],
351 356 'minus_disabled' => ($index !== 0 && $count <= 0),
352 357 'plus_disabled' => false,
353 358 'minus_attrs' => [
354 359 'data-target' => $input_id,
360 + /* translators: %s: traveler category label (e.g. "Adult", "Child"). */
355 361 'aria-label' => sprintf(__('Decrease %s', 'yatra'), $category_label),
356 362 ],
357 363 'plus_attrs' => [
358 364 'data-target' => $input_id,
365 + /* translators: %s: traveler category label (e.g. "Adult", "Child"). */
359 366 'aria-label' => sprintf(__('Increase %s', 'yatra'), $category_label),
360 367 ],
361 368 'input_attrs' => [
362 369 'id' => $input_id,
@@ -366,8 +373,10 @@
366 373 'max' => $trip->max_travelers ?: 20,
367 374 'data-category-label' => $category_label,
368 375 'data-price' => $category_price,
369 376 'data-pricing-mode' => $pricing_mode,
377 + 'data-group-overflow' => $pt->group_overflow ?? 'block',
378 + 'data-max-pax' => $pt->max_pax ?? '',
370 379 ],
371 380 ];
372 381 }
373 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');
@@ -418,10 +427,13 @@
418 427 <div class="yatra-summary-meta">
419 428 <?php
420 429 $duration_days = (int) ($trip->duration_days ?? 0);
421 430 $duration_nights = isset($trip->duration_nights) ? (int) $trip->duration_nights : null;
431 + $duration_hours = (int) ($trip->duration_hours ?? 0);
422 432 ?>
423 - <?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) : ?>
424 436 <?php if ($duration_days <= 1) : ?>
425 437 <span><?php esc_html_e('Day Trip', 'yatra'); ?></span>
426 438 <?php else : ?>
427 439 <span><?php echo esc_html(yatra_format_duration($duration_days, $duration_nights)); ?></span>
@@ -426,11 +438,37 @@
426 438 <?php else : ?>
427 439 <span><?php echo esc_html(yatra_format_duration($duration_days, $duration_nights)); ?></span>
428 440 <?php endif; ?>
429 441 <?php endif; ?>
430 - <?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 !== '') : ?>
431 469 <span>•</span>
432 - <span><?php echo esc_html(ucfirst($trip->difficulty_level)); ?></span>
470 + <span><?php echo esc_html($difficulty_label); ?></span>
433 471 <?php endif; ?>
434 472 </div>
435 473 </div>
436 474 </div>
@@ -525,11 +563,33 @@
525 563 </div>
526 564 </div>
527 565 <?php endif; ?>
528 566
529 - <!-- 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 + ?>
530 590 <div class="yatra-coupon-section">
531 - <div class="yatra-coupon-toggle">
591 + <div class="yatra-coupon-toggle" <?php echo $coupon_is_applied ? 'style="display: none;"' : ''; ?>>
532 592 <button type="button" id="yatra-coupon-toggle-btn" class="yatra-coupon-toggle-btn">
533 593 <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
534 594 <path d="M20 12v6a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2v-6"></path>
535 595 <path d="M12 2v10"></path>
@@ -551,17 +611,19 @@
551 611 </button>
552 612 </div>
553 613 <div id="yatra-coupon-message" class="yatra-coupon-message" style="display: none;"></div>
554 614 </div>
555 - <!-- Applied Coupon Display -->
556 - <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;"'; ?>>
557 619 <div class="yatra-applied-coupon-info">
558 620 <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
559 621 <path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path>
560 622 <polyline points="22 4 12 14.01 9 11.01"></polyline>
561 623 </svg>
562 - <span class="yatra-coupon-code-display"></span>
563 - <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>
564 626 </div>
565 627 <button type="button" id="yatra-remove-coupon" class="yatra-remove-coupon-btn" title="<?php esc_attr_e('Remove coupon', 'yatra'); ?>">
566 628 <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
567 629 <line x1="18" y1="6" x2="6" y2="18"></line>
@@ -570,9 +632,16 @@
570 632 </button>
571 633 </div>
572 634 </div>
573 635
574 - <!-- 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. -->
575 644 <?php
576 645 /**
577 646 * Filter: Get additional services for this trip
578 647 * Allows premium modules to add extra services to the booking
@@ -577,15 +646,15 @@
577 646 * Filter: Get additional services for this trip
578 647 * Allows premium modules to add extra services to the booking
579 648 */
580 649 $additional_services = apply_filters('yatra_booking_additional_services', [], $trip_id, $total_travelers, $traveler_counts ?? [], $travel_date);
581 -
650 +
582 651 // Get selected services from session (set from popup)
583 652 $session = yatra_get_booking_session();
584 - $selected_service_ids = isset($session['additional_services']) && is_array($session['additional_services'])
585 - ? 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'])
586 655 : [];
587 -
656 +
588 657 if (!empty($additional_services)) :
589 658 ?>
590 659 <div class="yatra-additional-services-section">
591 660 <div class="yatra-services-header">
@@ -592,53 +661,71 @@
592 661 <h4><?php esc_html_e('Additional Services', 'yatra'); ?></h4>
593 662 <p class="yatra-services-subtitle"><?php esc_html_e('Enhance your trip with these optional add-ons', 'yatra'); ?></p>
594 663 </div>
595 664 <div class="yatra-services-list" id="yatra-additional-services">
596 - <?php foreach ($additional_services as $service) :
665 + <?php foreach ($additional_services as $service) :
597 666 $service_id = (int) $service['id'];
598 667 $service_name = $service['name'];
599 - $service_description = $service['description'] ?? '';
668 + $service_description = (string) ($service['description'] ?? '');
600 669 $service_price = (float) $service['price'];
601 670 $service_price_per = $service['price_per'] ?? 'person';
602 - $service_image = $service['image_url'] ?? '';
603 - // Check is_required - handle various formats (bool, string "1", int 1)
671 + $service_price_type = $service['price_type'] ?? 'fixed';
604 672 $is_required = isset($service['is_required']) && ($service['is_required'] === true || $service['is_required'] === 1 || $service['is_required'] === '1');
605 673 $is_included = isset($service['is_included']) && ($service['is_included'] === true || $service['is_included'] === 1 || $service['is_included'] === '1');
606 674 $is_selected = in_array($service_id, $selected_service_ids, false);
607 -
608 - $price_label = yatra_format_price($service_price);
609 - if ($service_price_per === 'person') {
610 - $price_label .= ' ' . __('per person', 'yatra');
611 - } elseif ($service_price_per === 'day') {
612 - $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);
613 685 } else {
614 - $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 + }
615 694 }
616 695 if ($is_included) {
617 696 $price_label = __('Included', 'yatra');
618 697 }
619 -
620 - // Determine checked and disabled states
698 +
621 699 $is_checked = $is_required || $is_included || $is_selected;
622 700 $is_disabled = $is_required || $is_included;
623 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 + ?>
624 710 <div class="yatra-service-item <?php echo $is_required ? 'yatra-service-required' : ''; ?> <?php echo $is_checked ? 'yatra-service-selected' : ''; ?>"
625 - data-service-id="<?php echo esc_attr($service_id); ?>"
626 - 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); ?>"
627 713 data-service-price-per="<?php echo esc_attr($service_price_per); ?>"
628 - 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; ?>>
629 719 <label class="yatra-service-label">
630 - <input type="checkbox"
631 - name="additional_services[]"
632 - value="<?php echo esc_attr($service_id); ?>"
720 + <input type="checkbox"
721 + name="additional_services[]"
722 + value="<?php echo esc_attr((string) $service_id); ?>"
633 723 <?php echo $is_checked ? 'checked' : ''; ?>
634 724 <?php echo $is_disabled ? 'disabled' : ''; ?>
635 725 form="yatra-booking-form">
636 726 <div class="yatra-service-info">
637 727 <span class="yatra-service-name"><?php echo esc_html($service_name); ?></span>
638 - <?php if ($service_description) : ?>
639 - <span class="yatra-service-description"><?php echo esc_html($service_description); ?></span>
640 - <?php endif; ?>
641 728 </div>
642 729 <span class="yatra-service-price"><?php echo esc_html($price_label); ?></span>
643 730 </label>
644 731 </div>
@@ -646,20 +733,31 @@
646 733 </div>
647 734 </div>
648 735 <?php endif; ?>
649 736
650 -
651 - <!-- 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. -->
652 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'); ?>">
653 - <!-- This section is loaded/updated via AJAX -->
654 740 <?php
655 741 // Get checkout model from booking object
656 742 $checkout = $booking->checkout ?? null;
657 -
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 +
658 758 // Include pricing summary template (uses $checkout model)
659 759 include YATRA_PLUGIN_PATH . 'templates/partials/pricing-summary.php';
660 -
661 -
662 760 ?>
663 761 </div>
664 762
665 763 <!-- Cancellation Policy -->