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 | assets/js/trip.js +65 -27 3.0.9 → 3.0.16 View file →
@@ -5,8 +5,21 @@
5 5 /* jshint esversion: 6 */
6 6
7 7 // Translation helpers (booking.js installs the same shims if it runs first;
8 8 // idempotent setup so either load order works).
9 +// Parse a numeric min/max attribute while preserving a legitimate 0.
10 +// `parseInt(el.getAttribute('max')) || 99` treated max="0" — what a sold-out
11 +// departure renders — as "unset" and fell back to the placeholder ceiling, so
12 +// the guest selector stayed usable and ignored the departure's real capacity.
13 +// Only an absent or non-numeric value may fall back. Installed once and shared
14 +// by every frontend script, so load order does not matter.
15 +if (typeof window.yatraNumOr !== 'function') {
16 + window.yatraNumOr = function (raw, fallback) {
17 + var parsed = parseInt(raw, 10);
18 + return isNaN(parsed) ? fallback : parsed;
19 + };
20 +}
21 +
9 22 (function () {
10 23 var i18n = (window.wp && window.wp.i18n) ? window.wp.i18n : null;
11 24 if (i18n && typeof i18n.__ === 'function') {
12 25 window.__ = function (text, domain) { return i18n.__(text, domain || 'yatra'); };
@@ -192,9 +205,14 @@
192 205 const decimalSep = (typeof cfg.decimalSeparator === 'string') ? cfg.decimalSeparator : '.';
193 206
194 207 const num = Number(amount) || 0;
195 208
196 - const formatted = new Intl.NumberFormat(undefined, {
209 + // Format with a FIXED 'en-US' base (grouping ',', decimal '.') so the
210 + // replace-with-configured-separators below is deterministic. With the
211 + // viewer's locale (undefined), a European browser already yields
212 + // '1.234,56' and the naive comma/dot replacement SWAPS the separators —
213 + // making the configured decimal separator appear ignored.
214 + const formatted = new Intl.NumberFormat('en-US', {
197 215 minimumFractionDigits: decimals,
198 216 maximumFractionDigits: decimals,
199 217 }).format(num)
200 218 .replace(/,/g, 'TEMP_THOUSAND')
@@ -1591,10 +1609,10 @@
1591 1609 if (!minusBtn || !plusBtn) return;
1592 1610
1593 1611 const updateButtonStates = () => {
1594 1612 const current = parseInt(input.value) || 1;
1595 - const min = parseInt(input.getAttribute('min')) || 1;
1596 - const max = parseInt(input.getAttribute('max')) || 20;
1613 + const min = window.yatraNumOr(input.getAttribute('min'), 1);
1614 + const max = window.yatraNumOr(input.getAttribute('max'), 20);
1597 1615
1598 1616 minusBtn.disabled = current <= min;
1599 1617 plusBtn.disabled = current >= max;
1600 1618 };
@@ -1604,9 +1622,9 @@
1604 1622 e.preventDefault();
1605 1623 e.stopPropagation();
1606 1624
1607 1625 const current = parseInt(input.value) || 1;
1608 - const min = parseInt(input.getAttribute('min')) || 1;
1626 + const min = window.yatraNumOr(input.getAttribute('min'), 1);
1609 1627
1610 1628 if (current > min) {
1611 1629 input.value = current - 1;
1612 1630 updateButtonStates();
@@ -1619,9 +1637,9 @@
1619 1637 e.preventDefault();
1620 1638 e.stopPropagation();
1621 1639
1622 1640 const current = parseInt(input.value) || 1;
1623 - const max = parseInt(input.getAttribute('max')) || 20;
1641 + const max = window.yatraNumOr(input.getAttribute('max'), 20);
1624 1642
1625 1643 if (current < max) {
1626 1644 input.value = current + 1;
1627 1645 updateButtonStates();
@@ -2365,15 +2383,15 @@
2365 2383 const row = travelerSelector.querySelector('.yatra-quantity-row[data-category-id="' + categoryId + '"]');
2366 2384 if (row) {
2367 2385 const cardInput = row.querySelector('input[type="number"]');
2368 2386 if (cardInput) {
2369 - const max = parseInt(cardInput.getAttribute('max')) || 99;
2387 + const max = window.yatraNumOr(cardInput.getAttribute('max'), 99);
2370 2388 cardInput.value = Math.min(value, max);
2371 2389
2372 2390 // Update button states
2373 2391 const minusBtn = row.querySelector('.yatra-quantity-minus');
2374 2392 const plusBtn = row.querySelector('.yatra-quantity-plus');
2375 - const min = parseInt(cardInput.getAttribute('min')) || 0;
2393 + const min = window.yatraNumOr(cardInput.getAttribute('min'), 0);
2376 2394 if (minusBtn) minusBtn.disabled = cardInput.value <= min;
2377 2395 if (plusBtn) plusBtn.disabled = cardInput.value >= max;
2378 2396 }
2379 2397 }
@@ -2384,9 +2402,9 @@
2384 2402
2385 2403 } else if (simpleTravelers && numTravelersInput) {
2386 2404 // Simple pricing - sync total count
2387 2405 const totalCount = parseInt(numTravelersInput.value) || 1;
2388 - const max = parseInt(simpleTravelers.getAttribute('max')) || 99;
2406 + const max = window.yatraNumOr(simpleTravelers.getAttribute('max'), 99);
2389 2407 simpleTravelers.value = Math.min(totalCount, max);
2390 2408
2391 2409 // Update button states
2392 2410 const controls = simpleTravelers.closest('.yatra-quantity-controls-inline');
@@ -2392,9 +2410,9 @@
2392 2410 const controls = simpleTravelers.closest('.yatra-quantity-controls-inline');
2393 2411 if (controls) {
2394 2412 const minusBtn = controls.querySelector('.yatra-quantity-minus');
2395 2413 const plusBtn = controls.querySelector('.yatra-quantity-plus');
2396 - const min = parseInt(simpleTravelers.getAttribute('min')) || 1;
2414 + const min = window.yatraNumOr(simpleTravelers.getAttribute('min'), 1);
2397 2415 if (minusBtn) minusBtn.disabled = simpleTravelers.value <= min;
2398 2416 if (plusBtn) plusBtn.disabled = simpleTravelers.value >= max;
2399 2417 }
2400 2418 }
@@ -2846,16 +2864,28 @@
2846 2864 const enquiriesUrl = baseUrlRaw.includes('/yatra/v1')
2847 2865 ? `${baseUrlRaw}/enquiries`
2848 2866 : `${baseUrlRaw}/yatra/v1/enquiries`;
2849 2867
2850 - fetch(enquiriesUrl, {
2851 - method: 'POST',
2852 - headers: {
2853 - 'Content-Type': 'application/json',
2854 - },
2855 - credentials: 'omit', // public endpoint
2856 - body: JSON.stringify(payload),
2857 - })
2868 + // reCAPTCHA v3: attach a fresh token when the enquiry form is protected.
2869 + const yatraRc = window.yatraRecaptcha;
2870 + const enquiryTokenPromise = (yatraRc && yatraRc.protects && yatraRc.protects('enquiry'))
2871 + ? yatraRc.execute('enquiry')
2872 + : Promise.resolve('');
2873 +
2874 + enquiryTokenPromise
2875 + .then(recaptchaToken => {
2876 + if (recaptchaToken) {
2877 + payload.recaptcha_token = recaptchaToken;
2878 + }
2879 + return fetch(enquiriesUrl, {
2880 + method: 'POST',
2881 + headers: {
2882 + 'Content-Type': 'application/json',
2883 + },
2884 + credentials: 'omit', // public endpoint
2885 + body: JSON.stringify(payload),
2886 + });
2887 + })
2858 2888 .then(response => response.json())
2859 2889 .then(result => {
2860 2890 if (result.success) {
2861 2891 this.showMessage('success', result.message || result.data?.message || __('Thank you for your enquiry! We will get back to you soon.', 'yatra'));
@@ -3678,10 +3708,10 @@
3678 3708 const isMinus = btn.classList.contains('yatra-quantity-minus');
3679 3709 const isPlus = btn.classList.contains('yatra-quantity-plus');
3680 3710
3681 3711 let current = parseInt(input.value) || 0;
3682 - const min = parseInt(input.getAttribute('min')) || 0;
3683 - const max = parseInt(input.getAttribute('max')) || 999;
3712 + const min = window.yatraNumOr(input.getAttribute('min'), 0);
3713 + const max = window.yatraNumOr(input.getAttribute('max'), 999);
3684 3714 let newValue = current;
3685 3715
3686 3716 if (isPlus && current < max) {
3687 3717 newValue = current + 1;
@@ -3753,10 +3783,10 @@
3753 3783 const isMinus = btn.classList.contains('yatra-quantity-minus');
3754 3784 const isPlus = btn.classList.contains('yatra-quantity-plus');
3755 3785
3756 3786 let current = parseInt(input.value) || 1;
3757 - const min = parseInt(input.getAttribute('min')) || 1;
3758 - const max = parseInt(input.getAttribute('max')) || 999;
3787 + const min = window.yatraNumOr(input.getAttribute('min'), 1);
3788 + const max = window.yatraNumOr(input.getAttribute('max'), 999);
3759 3789 let newValue = current;
3760 3790
3761 3791 if (isPlus && current < max) {
3762 3792 newValue = current + 1;
@@ -3866,8 +3896,9 @@
3866 3896 // Check for traveler-based pricing first
3867 3897 const categoryInputs = this.section.querySelectorAll(`.yatra-availability-category[data-item="${itemIndex}"]`);
3868 3898 let totalPrice = 0;
3869 3899 let totalTravelers = 0;
3900 + let totalGroups = 0; // per-group categories: number of group prices charged
3870 3901
3871 3902 if (categoryInputs.length > 0) {
3872 3903 // Traveler-based pricing: sum up each category's price * quantity (respecting pricing mode)
3873 3904 categoryInputs.forEach((input) => {
@@ -3879,11 +3910,11 @@
3879 3910 const maxPax = parseInt((row ? row.getAttribute('data-max-pax') : null) || input.getAttribute('data-max-pax')) || 0;
3880 3911
3881 3912 if (pricingMode === 'per_group') {
3882 3913 if (quantity > 0) {
3883 - totalPrice += (groupOverflow === 'per_block' && maxPax > 0)
3884 - ? price * Math.ceil(quantity / maxPax)
3885 - : price;
3914 + const blocks = (groupOverflow === 'per_block' && maxPax > 0) ? Math.ceil(quantity / maxPax) : 1;
3915 + totalPrice += price * blocks;
3916 + totalGroups += blocks;
3886 3917 }
3887 3918 } else {
3888 3919 totalPrice += quantity * price;
3889 3920 }
@@ -3911,14 +3942,21 @@
3911 3942 } else {
3912 3943 totalAmountElement.textContent = String(finalPrice.toFixed(2));
3913 3944 }
3914 3945
3915 - // Header "per person" must match chargable average (row totals + group discount), not static PHP.
3946 + // Header price must match the chargeable amount (row totals + group discount), not static PHP.
3947 + // Its unit follows the card's price label: "per person" → average per traveller;
3948 + // "per group" → the price per group actually charged (never divided by headcount,
3949 + // which would turn a flat $1,100 group price into "$550 per group" for two travellers).
3916 3950 const cardRoot = this.section.querySelector('.yatra-availability-card[data-item="' + itemIndex + '"]');
3917 - const headerSaleEl = cardRoot ? cardRoot.querySelector('.yatra-card-header-price .yatra-sale-price') : null;
3951 + const headerPriceWrap = cardRoot ? cardRoot.querySelector('.yatra-card-header-price') : null;
3952 + const headerSaleEl = headerPriceWrap ? headerPriceWrap.querySelector('.yatra-sale-price') : null;
3918 3953 if (headerSaleEl) {
3954 + const headerUnit = headerPriceWrap.getAttribute('data-price-unit') || 'per_person';
3919 3955 let perPerson = 0;
3920 - if (totalTravelers > 0) {
3956 + if (headerUnit === 'per_group') {
3957 + perPerson = totalGroups > 0 ? finalPrice / totalGroups : (totalPrice > 0 ? totalPrice : 0);
3958 + } else if (totalTravelers > 0) {
3921 3959 perPerson = finalPrice / totalTravelers;
3922 3960 } else if (totalPrice > 0) {
3923 3961 perPerson = totalPrice;
3924 3962 }