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/booking.js +112 -15 3.0.8 → 3.0.16 View file →
@@ -11,22 +11,45 @@
11 11 // runtime is enqueued (it is — `yatra-booking` declares `wp-i18n` as a
12 12 // dependency in FrontendAssetsProvider). Falls back to the source string if
13 13 // wp.i18n is unavailable so the page never breaks on older environments.
14 14 (function () {
15 - if (typeof window.__ === 'function') return;
16 - if (window.wp && window.wp.i18n && typeof window.wp.i18n.__ === 'function') {
17 - window.__ = function (text, domain) { return window.wp.i18n.__(text, domain || 'yatra'); };
18 - window._n = function (s, p, n, domain) { return window.wp.i18n._n(s, p, n, domain || 'yatra'); };
19 - window._x = function (text, ctx, domain) { return window.wp.i18n._x(text, ctx, domain || 'yatra'); };
20 - window.sprintf = window.sprintf || (window.wp.i18n.sprintf || function (fmt) { return fmt; });
15 + var i18n = (window.wp && window.wp.i18n) ? window.wp.i18n : null;
16 + if (i18n && typeof i18n.__ === 'function') {
17 + window.__ = function (text, domain) { return i18n.__(text, domain || 'yatra'); };
18 + window._n = function (s, p, n, domain) { return i18n._n(s, p, n, domain || 'yatra'); };
19 + window._x = function (text, ctx, domain) { return i18n._x(text, ctx, domain || 'yatra'); };
20 + // Always bind WordPress's own sprintf (it substitutes %d/%s/%1$s).
21 + // Do NOT defer to a pre-existing window.sprintf: a theme or other
22 + // plugin can define an incompatible global that leaves placeholders
23 + // unsubstituted (the cause of literal "%d"/"%s" in the UI).
24 + if (typeof i18n.sprintf === 'function') {
25 + window.sprintf = function () { return i18n.sprintf.apply(i18n, arguments); };
26 + } else if (typeof window.sprintf !== 'function') {
27 + window.sprintf = function (fmt) { return fmt; };
28 + }
21 29 } else {
22 - window.__ = function (text) { return text; };
23 - window._n = function (s, p, n) { return n === 1 ? s : p; };
24 - window._x = function (text) { return text; };
25 - window.sprintf = window.sprintf || function (fmt) { return fmt; };
30 + // wp.i18n not present — install passthroughs only if nothing usable
31 + // exists yet (don't clobber a real binding installed earlier).
32 + if (typeof window.__ !== 'function') window.__ = function (text) { return text; };
33 + if (typeof window._n !== 'function') window._n = function (s, p, n) { return n === 1 ? s : p; };
34 + if (typeof window._x !== 'function') window._x = function (text) { return text; };
35 + if (typeof window.sprintf !== 'function') window.sprintf = function (fmt) { return fmt; };
26 36 }
27 37 })();
28 38
39 +// Parse a numeric min/max attribute while preserving a legitimate 0.
40 +// `parseInt(el.getAttribute('max')) || 20` treated max="0" — what a sold-out
41 +// departure renders — as "unset" and fell back to the placeholder ceiling, so
42 +// the guest selector stayed usable and ignored the departure's real capacity.
43 +// Only an absent or non-numeric value may fall back. Installed once and shared
44 +// by every frontend script, so load order does not matter.
45 +if (typeof window.yatraNumOr !== 'function') {
46 + window.yatraNumOr = function (raw, fallback) {
47 + var parsed = parseInt(raw, 10);
48 + return isNaN(parsed) ? fallback : parsed;
49 + };
50 +}
51 +
29 52 (function($) {
30 53 'use strict';
31 54
32 55 // API configuration - available throughout the module
@@ -54,8 +77,43 @@
54 77
55 78 $(document).ready(function() {
56 79 const $form = $('#yatra-booking-form');
57 80 const $submitBtn = $('#yatra-submit-booking');
81 +
82 + // --- Guest "Create an account" reveal ---
83 + // The optional account-creation block (#yatra-account-fields) ships hidden
84 + // with non-required password inputs. Without this handler the guest can
85 + // tick "Create an account" but never see a password field, so either no
86 + // account is created (empty password) or autofill populates the hidden
87 + // field asymmetrically and the server rejects with "Passwords do not
88 + // match" — with no visible field to correct. Reveal + require the fields
89 + // only when opted in; hide + clear + un-require otherwise so a pure-guest
90 + // submit is never blocked and no stale/autofilled password is sent.
91 + (function initCreateAccountToggle() {
92 + const $accountFields = $('#yatra-account-fields');
93 + const $checkbox = $('#create-account');
94 + if (!$accountFields.length || !$checkbox.length) {
95 + return;
96 + }
97 + const $pw = $accountFields.find('#account_password');
98 + const $pwConfirm = $accountFields.find('#account_password_confirm');
99 +
100 + const applyState = function (checked) {
101 + if (checked) {
102 + $accountFields.show();
103 + $pw.add($pwConfirm).attr('required', 'required');
104 + } else {
105 + $accountFields.hide();
106 + $pw.add($pwConfirm).removeAttr('required').val('');
107 + }
108 + };
109 +
110 + // Honor the current state on load (covers browser-restored checkboxes).
111 + applyState($checkbox.is(':checked'));
112 + $checkbox.on('change', function () {
113 + applyState($(this).is(':checked'));
114 + });
115 + })();
58 116 const isRemainingPayment = Boolean(window.yatraBookingData?.isRemainingPayment) || ($form.data('is-remaining-payment') === 'yes');
59 117 const remainingAmount = isRemainingPayment
60 118 ? parseFloat(window.yatraBookingData?.remainingAmount ?? $form.data('payment-due')) || 0
61 119 : 0;
@@ -625,9 +683,14 @@
625 683 '<p style="font-size: 18px; color: #6b7280; margin-bottom: 8px;">' + message + '</p>' +
626 684 '<p style="font-size: 16px; color: #111827; font-weight: 600;">' + referenceText + '</p>' +
627 685 footerHtml +
628 686 '</div>');
629 -
687 +
688 + // The booking-form header ("Complete Your Booking" + subtitle) is a
689 + // SIBLING of the form, not inside it — replacing the form alone left
690 + // that header showing above the confirmation. Hide it so the success
691 + // screen doesn't repeat the booking-form heading.
692 + $form.siblings('.yatra-booking-header').hide();
630 693 $form.html($success);
631 694 }
632 695
633 696 // =====================
@@ -667,10 +730,10 @@
667 730
668 731 const $btn = $(this);
669 732 const $input = $('#number-of-travelers');
670 733 let currentValue = parseInt($input.val()) || 1;
671 - const min = parseInt($input.attr('min')) || 1;
672 - const max = parseInt($input.attr('max')) || 20;
734 + const min = window.yatraNumOr($input.attr('min'), 1);
735 + const max = window.yatraNumOr($input.attr('max'), 20);
673 736
674 737 if ($btn.hasClass('yatra-qty-plus')) {
675 738 if (currentValue < max) {
676 739 currentValue++;
@@ -700,10 +763,10 @@
700 763 const $btn = $(this);
701 764 const $row = $btn.closest('.yatra-quantity-row');
702 765 const $input = $row.find('.yatra-qty-input');
703 766 let currentValue = parseInt($input.val()) || 0;
704 - const min = parseInt($input.attr('min')) || 0;
705 - const max = parseInt($input.attr('max')) || 20;
767 + const min = window.yatraNumOr($input.attr('min'), 0);
768 + const max = window.yatraNumOr($input.attr('max'), 20);
706 769
707 770 if ($btn.hasClass('yatra-qty-plus')) {
708 771 if (currentValue < max) {
709 772 currentValue++;
@@ -1089,8 +1152,25 @@
1089 1152 /**
1090 1153 * Submit booking to server
1091 1154 */
1092 1155 function submitBookingToServer(bookingData, originalBtnHtml) {
1156 + // reCAPTCHA v3: attach a token when the booking form is protected,
1157 + // then run the real submit. Wraps every call path (direct submit and
1158 + // the gateway `proceedWithSubmission` hook). No-op unless the operator
1159 + // opted booking into reCAPTCHA (off by default), so payment flows are
1160 + // unchanged otherwise.
1161 + const yatraRc = window.yatraRecaptcha;
1162 + if (yatraRc && yatraRc.protects && yatraRc.protects('booking') && !bookingData.recaptcha_token) {
1163 + yatraRc.execute('booking').then(function (recaptchaToken) {
1164 + if (recaptchaToken) { bookingData.recaptcha_token = recaptchaToken; }
1165 + submitBookingToServerInner(bookingData, originalBtnHtml);
1166 + });
1167 + return;
1168 + }
1169 + submitBookingToServerInner(bookingData, originalBtnHtml);
1170 + }
1171 +
1172 + function submitBookingToServerInner(bookingData, originalBtnHtml) {
1093 1173 const { base: restBase, isPlain } = getRestBase();
1094 1174 const siteBase = (window.yatraBookingData?.siteUrl || window.location.origin || '').replace(/\/$/, '');
1095 1175 let createUrl;
1096 1176 if (isPlain) {
@@ -1827,8 +1907,24 @@
1827 1907 password: password,
1828 1908 confirm_password: confirmPassword
1829 1909 };
1830 1910
1911 + // reCAPTCHA v3: this form posts straight to /auth/register, so it must
1912 + // carry its own token when the operator protects registration — the
1913 + // server rejects an empty one outright, whatever the score threshold.
1914 + // Resolves to '' when reCAPTCHA is off or unavailable, so the normal
1915 + // (unprotected) flow is completely unchanged.
1916 + const yatraRcReg = window.yatraRecaptcha;
1917 + const regTokenPromise = (yatraRcReg && typeof yatraRcReg.protects === 'function'
1918 + && yatraRcReg.protects('registration') && typeof yatraRcReg.execute === 'function')
1919 + ? yatraRcReg.execute('registration')
1920 + : Promise.resolve('');
1921 +
1922 + regTokenPromise.catch(function () { return ''; }).then(function (regToken) {
1923 + if (regToken) {
1924 + registerData.recaptcha_token = regToken;
1925 + }
1926 +
1831 1927 fetch(apiUrl + '/auth/register', {
1832 1928 method: 'POST',
1833 1929 headers: {
1834 1930 'Content-Type': 'application/json',
@@ -1892,8 +1988,9 @@
1892 1988 $btn.prop('disabled', false);
1893 1989 $btnText.show();
1894 1990 $btnLoading.hide();
1895 1991 });
1992 + }); // end reCAPTCHA token wrapper
1896 1993 });
1897 1994
1898 1995 // ---------------------------------------------------------------------
1899 1996 // Date field picker upgrade (fast year navigation for Date of Birth)