PageSourceSearch

https://ruidosonews.com/wp-content/plugins/cherryroad-paywall/assets/js/crpw-auth.js?ver=2.8.8

js ruidosonews.com collected 2026-10-02 03:42:54 UTC 26,485 bytes, 508 lines download raw bytes

1/**
2 * CherryRoad Paywall — Frontend Auth JS
3 * Handles: login-only modal (Modal 1), unified account modal (Modals 2+3), logout, password toggle.
4 */
5(function () {
6    'use strict';
7
8    const restBase = crpwData.restUrl;
9    const nonce    = crpwData.nonce;
10    const homeUrl  = crpwData.homeUrl;
11
12    // ----------------------------------------------------------------
13    // Content URL tracking — set crpw_content_url cookie client-side.
14    // Done in JS (not PHP) so it works correctly on cached pages: cached
15    // pages skip PHP so a server-side cookie would never be set, but JS
16    // always runs in the browser. Stores the current article URL so the
17    // login / signup / checkout flows can redirect the visitor back here.
18    // Only set on singular content where this script loads, and never on
19    // the plugin's own flow pages (handled by not enqueuing there / guard below).
20    // ----------------------------------------------------------------
21    (function setContentUrlCookie() {
22        try {
23            // Don't overwrite on our own flow pages — match query/`crpw` markers
24            const path = window.location.pathname.toLowerCase();
25            const skip = ['/plans', '/checkout', '/dashboard', '/login', '/crpw-login', '/subscribe', '/reset', '/continue'];
26            for (const s of skip) {
27                if (path === s || path === s + '/' || path.indexOf(s + '/') === 0) return;
28            }
29            // Strip our own cache-bust param so the stored URL is clean
30            const url = new URL(window.location.href);
31            url.searchParams.delete('crpw_auth');
32            const clean = url.toString();
33
34            const secure = window.location.protocol === 'https:' ? '; Secure' : '';
35            const maxAge = 60 * 60; // 1 hour, mirrors previous PHP cookie lifetime
36            document.cookie = 'crpw_content_url=' + encodeURIComponent(clean) +
37                '; path=/; max-age=' + maxAge + '; SameSite=Lax' + secure;
38        } catch (e) { /* non-fatal — return-to-article is a convenience only */ }
39    })();
40
41    // Append ?crpw_auth=1 to redirect URLs after login — tells WP Rocket to bypass cache
42    // for that first page load. WP Rocket must have crpw_auth in its query string exclusions.
43    function cacheBustUrl( url ) {
44        if ( ! url ) return url;
45        const sep = url.includes('?') ? '&' : '?';
46        return url + sep + 'crpw_auth=1';
47    }
48
49    const apiPost = (endpoint, body) =>
50        fetch(restBase + endpoint, {
51            method:  'POST',
52            headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'X-WP-Nonce': nonce },
53            body: new URLSearchParams(body).toString(),
54        }).then(r => r.json());
55
56    // ================================================================
57    // Modal 1 — Login only (paywall gate Log In button, checkout page)
58    // ================================================================
59    const loginOverlay  = document.getElementById('crpw-modal-overlay');
60    const loginOpenBtn  = document.getElementById('crpw-open-modal');
61    const loginCloseBtn = document.getElementById('crpw-modal-close');
62
63    // Move to document.body — escapes any theme stacking context so
64    // position:fixed works correctly regardless of scroll position
65    if (loginOverlay) document.body.appendChild(loginOverlay);
66
67    function openLoginModal() {
68        if (!loginOverlay) return;
69        loginOverlay.removeAttribute('hidden');
70        requestAnimationFrame(() => requestAnimationFrame(() =>
71            loginOverlay.classList.add('crpw-modal-visible')
72        ));
73        document.body.style.overflow = 'hidden';
74        const first = loginOverlay.querySelector('input:not([type=hidden])');
75        if (first) setTimeout(() => first.focus(), 250);
76    }
77
78    function closeLoginModal() {
79        if (!loginOverlay) return;
80        loginOverlay.classList.remove('crpw-modal-visible');
81        document.body.style.overflow = '';
82        setTimeout(() => { loginOverlay.setAttribute('hidden', ''); if (loginOpenBtn) loginOpenBtn.focus(); }, 220);
83    }
84
85    if (loginOpenBtn)  loginOpenBtn.addEventListener('click', openLoginModal);
86    if (loginCloseBtn) loginCloseBtn.addEventListener('click', closeLoginModal);
87    if (loginOverlay)  loginOverlay.addEventListener('click', e => { if (e.target === loginOverlay) closeLoginModal(); });
88
89    document.addEventListener('keydown', e => {
90        if (e.key === 'Escape' && loginOverlay && !loginOverlay.hasAttribute('hidden')) closeLoginModal();
91    });
92
93    // Focus trap for login modal
94    if (loginOverlay) {
95        loginOverlay.addEventListener('keydown', function (e) {
96            if (e.key !== 'Tab') return;
97            const els   = loginOverlay.querySelectorAll('a[href],button:not([disabled]),input
97:not([disabled]),[tabindex]:not([tabindex="-1"])');
98            const first = els[0], last = els[els.length - 1];
99            if (e.shiftKey) { if (document.activeElement === first) { e.preventDefault(); last.focus(); } }
100            else            { if (document.activeElement === last)  { e.preventDefault(); first.focus(); } }
101        });
102    }
103
104    // Login form handler (Modal 1)
105    const loginForm = document.getElementById('crpw-login-form');
106    if (loginForm) {
107        const errorBox   = document.getElementById('crpw-modal-error')   || document.getElementById('crpw-login-error');
108        const successBox = document.getElementById('crpw-modal-success') || document.getElementById('crpw-login-success');
109        const submitBtn  = document.getElementById('crpw-submit-btn');
110        const btnText    = submitBtn?.querySelector('.crpw-btn-text');
111        const btnSpinner = submitBtn?.querySelector('.crpw-btn-spinner');
112
113        loginForm.addEventListener('submit', async function (e) {
114            e.preventDefault();
115            if (errorBox)   { errorBox.style.display = 'none'; errorBox.textContent = ''; }
116            if (successBox) { successBox.style.display = 'none'; }
117
118            const username   = loginForm.querySelector('[name="username"]')?.value.trim() ?? '';
119            const password   = loginForm.querySelector('[name="password"]')?.value ?? '';
120            const redirectTo = loginForm.querySelector('[name="redirect_to"]')?.value || window.location.href;
121
122            if (!username || !password) { showLoginError('Please enter your email/username and password.'); return; }
123
124            setLoginLoading(true);
125            try {
126                const json = await apiPost('login', { username, password, redirect_to: redirectTo });
127                if (json.status !== 'success') {
128                    showLoginError(json.message || 'Login failed. Please check your credentials.');
129                    return;
130                }                if (successBox) { successBox.textContent = '✅ Login successful! Loading…'; successBox.style.display = 'block'; }
131                setTimeout(() => { window.location.href = cacheBustUrl( json.next_url || redirectTo || homeUrl ); }, 700);
132            } catch (err) {
133                showLoginError('A network error occurred. Please try again.');
134            } finally {
135                setLoginLoading(false);
136            }
137        });
138
139        function showLoginError(msg) { if (!errorBox) return; errorBox.textContent = msg; errorBox.style.display = 'block'; }
140        function setLoginLoading(on) {
141            if (!submitBtn) return;
142            submitBtn.disabled = on;
143            if (btnText)    btnText.style.display    = on ? 'none' : '';
144            if (btnSpinner) btnSpinner.style.display = on ? 'inline-block' : 'none';
145        }
146    }
147
148    // ================================================================
149    // Unified Account Modal — Create Account + Login tabs
150    // Used from: paywall gate (Get Free Access), plans page (Subscribe + Free)
151    // Opened via: openAccountModal(config) called from crpw-plans.js and paywall gate buttons
152    // ================================================================
153    const accountOverlay  = document.getElementById('crpw-account-modal-overlay');
154    const accountCloseBtn = document.getElementById('crpw-account-modal-close');
155
156    // Move to document.body — escapes any theme stacking context
157    if (accountOverlay) document.body.appendChild(accountOverlay);
158
159    // Tab switcher — exposed globally so PHP onclick attributes work
160    window.crpwAccountSwitchTab = function(tab) {
161        const signupPane = document.getElementById('crpw-account-pane-signup');
162        const loginPane  = document.getElementById('crpw-account-pane-login');
163        const signupTab  = document.getElementById('crpw-account-tab-signup');
164        const loginTab   = document.getElementById('crpw-account-tab-login');
165        if (!signupPane || !loginPane) return;
166
167        if (tab === 'signup') {
168            signupPane.style.display = '';
169            loginPane.style.display  = 'none';
170            if (signupTab) signupTab.classList.add('crpw-modal-tab--active');
171            if (loginTab)  loginTab.classList.remove('crpw-modal-tab--active');
172        } else {
173            signupPane.style.display = 'none';
174            loginPane.style.display  = '';
175            if (loginTab)  loginTab.classList.add('crpw-modal-tab--active');
176            if (signupTab) signupTab.classList.remove('crpw-modal-tab--active');
177        }
178        // Clear errors on tab switch
179        const errBox = document.getElementById('crpw-account-error');
180        const loginErr = document.getElementById('crpw-account-login-error');
181        if (errBox)    { errBox.style.display = 'none'; errBox.textContent = ''; }
182        if (loginErr)  { loginErr.style.display = 'none'; loginErr.textContent = ''; }
183    };
184
185    // Open the unified account modal
186    // config: { mode: 'subscribe'|'free', planId, planName, planPrice, redirectTo }
187    window.openAccountModal = function(config) {
188        if (!accountOverlay) return;
189
190        const mode       = config.mode || 'free';
191        const planId     = config.planId || '';
192        const planName   = config.planName || '';
193        const planPrice  = config.planPrice || '';
194        const redirectTo = config.redirectTo || window.location.href;
195
196        // Set hidden fields
197        const planIdInput     = document.getElementById('crpw-account-plan-id');
198        const freeInput       = document.getElementById('crpw-account-free');
199        const signupRedirect  = document.getElementById('crpw-account-signup-redirect');
200        const loginRedirect   = document.getElementById('crpw-account-login-redirect');
201
202        if (planIdInput)    planIdInput.value = planId;
203        if (freeInput)      freeInput.value   = mode === 'free' ? '1' : '0';
204        if (signupRedirect) signupRedirect.value = redirectTo;
205        if (loginRedirect)  loginRedirect.value  = redirectTo;
206
207        // Plan summary bar — shown for paid subscribe
208        const planSummary = document.getElementById('crpw-account-plan-summary');
209        if (planSummary) {
210            if (mode === 'subscribe' && planName) {
211                planSummary.textContent = 'Selected: ' + planName + ' — ' + planPrice;
212                planSummary.style.display = 'block';
213            } else {
214                planSummary.style.display = 'none';
215            }
216        }
217
218        // Modal title — shown for free mode
219        const modalTitle = document.getElementById('crpw-account-modal-title');
220        if (modalTitle) modalTitle.style.display = mode === 'free' ? 'block' : 'none';
221
222        // Submit button text
223        const submitText = document.querySelector('#crpw-account-signup-submit .crpw-btn-text');
224        if (submitText) {
225            submitText.textContent = mode === 'free' ? 'Create Free Account' : 'Continue to Payment';
226        }
227
228        // Footer links — shown in free mode only
229        const signupFooter = document.getElementById('crpw-account-plans-footer');
230        const loginFooter  = document.getElementById('crpw-account-login-footer');
231        if (signupFooter) signupFooter.style.display = mode === 'free' ? 'block' : 'none';
232        if (loginFooter)  loginFooter.style.display  = mode === 'free' ? 'block' : 'none';
233
234        // Reset to signup tab, account step
235        crpwAccountSwitchTab('signup');
236        accountShowStep('account');
237
238        // Reset forms
239        document.getElementById('crpw-account-signup-form')?.reset();
240        document.getElementById('crpw-account-login-form')?.reset();
241
242        // Restore hidden field values after reset
243        if (planIdInput)    planIdInput.value = planId;
244        if (freeInput)      freeInput.value   = mode === 'free' ? '1' : '0';
245        if (signupRedirect) signupRedirect.value = redirectTo;
246        if (loginRedirect)  loginRedirect.value  = redirectTo;
247
248        // Reset password hint
249        const pwdHint = document.getElementById('crpw-account-password-hint');
250        if (pwdHint) pwdHint.style.color = '';
251
252        // Open
253        accountOverlay.removeAttribute('hidden');
254        requestAnimationFrame(() => requestAnimationFrame(() =>
255            accountOverlay.classList.add('crpw-modal-visible')
256        ));
257        document.body.style.overflow = 'hidden';
258        setTimeout(() => {
259            const first = accountOverlay.querySelector('#crpw-account-pane-signup input:not([type=hidden])');
260            if (first) first.focus();
261        }, 250);
262    };
263
264    function closeAccountModal() {
265        if (!accountOverlay) return;
266        accountOverlay.classList.remove('crpw-modal-visible');
267        document.body.style.overflow = '';
268        setTimeout(() => accountOverlay.setAttribute('hidden', ''), 220);
269    }
270
271    function accountShowStep(step) {
272        accountOverlay?.querySelectorAll('.crpw-subscribe-step').forEach(el => el.style.display = 'none');
273        const target = document.getElementById('crpw-account-step-' + step);
274        if (target) target.style.display = 'block';
275    }
276
277    if (accountCloseBtn) accountCloseBtn.addEventListener('click', closeAccountModal);
278    if (accountOverlay)  accountOverlay.addEventListener('click', e => { if (e.target === accountOverlay) closeAccountModal(); });
279    document.addEventListener('keydown', e => {
280        if (e.key === 'Escape' && accountOverlay && !accountOverlay.hasAttribute('hidden')) closeAccountModal();
281    });
282
283    // ----------------------------------------------------------------
284    // Get Free Access button on paywall gate
285    // ----------------------------------------------------------------
286    document.querySelectorAll('#crpw-open-free-modal').forEach(btn => {
287        btn.addEventListener('click', () => {
288            openAccountModal({ mode: 'free', redirectTo: window.location.href });
289        });
290    });
291
292    // ----------------------------------------------------------------
293    // Account modal — signup form
294    // ----------------------------------------------------------------
295    const accountSignupForm = document.getElementById('crpw-account-signup-form');
296    if (accountSignupForm) {
297        const errBox    = document.getElementById('crpw-account-error');
298        const submitBtn = document.getElementById('crpw-account-signup-submit');
299        const btnText   = submitBtn?.querySelector('.crpw-btn-text');
300        const btnSpin   = submitBtn?.querySelector('.crpw-btn-spinner');
301
302        accountSignupForm.addEventListener('submit', async function(e) {
303            e.preventDefault();
304            if (errBox) { errBox.style.display = 'none'; errBox.textContent = ''; }
305
306            // Client-side password validation
307            const pwd  = accountSignupForm.querySelector('[name="password"]')?.value || '';
308            const pwd2 = accountSignupForm.querySelector('[name="password2"]')?.value || '';
309            const isFree = document.getElementById('crpw-account-free')?.value === '1';
310
311            if (pwd !== pwd2) {
312                if (errBox) { errBox.textContent = 'Passwords do not match.'; errBox.style.display = 'block'; }
313                return;
314            }
315            if (pwd.length < 8 || !/[a-zA-Z]/.test(pwd) || !/[0-9!@#$%^&*()\-_=+\[\]{};':"\\|,.<>/?]/.test(pwd)) {
316                if (errBox) { errBox.textContent = 'Password must be at least 8 characters and contain a letter and a number or symbol.'; errBox.style.display = 'block'; }
317                return;
318            }
319
320            if (submitBtn) submitBtn.disabled = true;
321            if (btnText)   btnText.style.display = 'none';
322            if (btnSpin)   btnSpin.style.display = 'inline-block';
323
324            try {
325                const data = new FormData(accountSignupForm);
326                const json = await apiPost('signup', Object.fromEntries(data));
327
328                if (json.status === 'success') {
329                    if (isFree) {
330                        // Show success step with free account message
331                        const msg = document.getElementById('crpw-account-success-msg');
332                        if (msg) msg.textContent = 'Your free account has been created. You can now read complimentary articles.';
333                        const continueBtn = document.getElementById('crpw-account-continue-btn');
334                        if (continueBtn) continueBtn.textContent = 'Continue Reading';
335                        accountShowStep('success');
336                    } else {
337                        // Paid plan — redirect to checkout
338                        const planId = document.getElementById('crpw-account-plan-id')?.value;
339                        window.location.href = (crpwData.checkoutUrl || homeUrl + 'checkout/') + '?level=' + planId;
340                    }
341                } else if (json.status === 'verify_email') {
342                    // Account created but needs email verification before login
343                    const msg = document.getElementById('crpw-account-success-msg');
344                    if (msg) msg.textContent = json.message || 'Please check your email to verify your account before logging in.';
345                    const continueBtn = document.getElementById('crpw-account-continue-btn');
346                    if (continueBtn) {
347                        continueBtn.textContent = 'Close';
348                        continueBtn.onclick = () => closeAccountModal();
349                    }
350                    accountShowStep('success');
351                } else {
352                    let msg = 'An error occurred. Please try again.';
353                    if (typeof json.message === 'string') {
354                        msg = json.message;
355                    } else if (json.message?.crm_errors?.length) {
356                        msg = json.message.crm_errors.join(' ');
357                    } else if (json.message?.required_errors?.length) {
358                        msg = json.message.required_errors.join(' ');
359                    }
360                    if (errBox) { errBox.textContent = msg; errBox.style.display = 'block'; }
361                }
362            } catch (err) {
363                if (errBox) { errBox.textContent = 'A network error occurred. Please try again.'; errBox.style.display = 'block'; }
364            } finally {
365                if (submitBtn) submitBtn.disabled = false;
366                if (btnText)   btnText.style.display = '';
367                if (btnSpin)   btnSpin.style.display = 'none';
368            }
369        });
370    }
371
372    // ----------------------------------------------------------------
373    // Account modal — login form
374    // ----------------------------------------------------------------
375    const accountLoginForm = document.getElementById('crpw-account-login-form');
376    if (accountLoginForm) {
377        const errBox    = document.getElementById('crpw-account-login-error');
378        const succBox   = document.getElementById('crpw-account-login-success');
379        const submitBtn = document.getElementById('crpw-account-login-btn');
380        const btnText   = submitBtn?.querySelector('.crpw-btn-text');
381        const btnSpin   = submitBtn?.querySelector('.crpw-btn-spinner');
382
383        accountLoginForm.addEventListener('submit', async function(e) {
384            e.preventDefault();
385            if (errBox) { errBox.style.display = 'none'; errBox.textContent = ''; }
386            if (submitBtn) submitBtn.disabled = true;
387            if (btnText)   btnText.style.display = 'none';
388            if (btnSpin)   btnSpin.style.display = 'inline-block';
389
390            try {
391                const data = new FormData(accountLoginForm);
392                const json = await apiPost('login', Object.fromEntries(data));
393
394                if (json.status === 'success') {
395                    if (succBox) { succBox.textContent = '✅ Login successful! Loading…'; succBox.style.display = 'block'; }
396                    setTimeout(() => { window.location.href = cacheBustUrl( json.next_url || window.location.href ); }, 700);
397                } else if (json.status === 'verify_email') {
398                    // Not verified — show message, no redirect
399                    if (errBox) { errBox.textContent = json.message || 'Please verify your email address before logging in.'; errBox.style.display = 'block'; }
400                } else {
401                    if (errBox) { errBox.textContent = json.message || 'Login failed.'; errBox.style.display = 'block'; }
402                }
403            } catch (err) {
404                if (errBox) { errBox.textContent = 'A network error occurred. Please try again.'; errBox.style.display = 'block'; }
405            } finally {
406                if (submitBtn) submitBtn.disabled = false;
407                if (btnText)   btnText.style.display = '';
408                if (btnSpin)   btnSpin.style.display = 'none';
409            }
410        });
411    }
412
413    // ----------------------------------------------------------------
414    // Account modal — continue button (free success step)
415    // ----------------------------------------------------------------
416    document.getElementById('crpw-account-continue-btn')?.addEventListener('click', () => {
417        closeAccountModal();
418        window.location.reload();
419    });
420
421    // ----------------------------------------------------------------
422    // Password hint — live feedback on account signup form
423    // ----------------------------------------------------------------
424    const pwdInput = document.getElementById('crpw-account-password');
425    const pwdHint  = document.getElementById('crpw-account-password-hint');
426
427    if (pwdInput && pwdHint) {
428        pwdInput.addEventListener('input', function () {
429            const val        = pwdInput.value;
430            const hasLetter  = /[a-zA-Z]/.test(val);
431            const hasNumSym  = /[0-9!@#$%^&*()\-_=+\[\]{};':"\\|,.<>/?]/.test(val);
432            const longEnough = val.length >= 8;
433            const valid      = longEnough && hasLetter && hasNumSym;
434
435            pwdHint.style.color = val.length === 0 ? '' : valid ? '#15803d' : '#b91c1c';
436        });
437    }
438
439    // ----------------------------------------------------------------
440    // Email duplicate check — account signup form
441    // ----------------------------------------------------------------
442    let emailCheckTimer = null;
443    const emailInput = document.getElementById('crpw-account-email');
444    const emailMsg   = document.getElementById('crpw-account-email-msg');
445
446    if (emailInput && emailMsg) {
447        emailInput.addEventListener('blur', function() {
448            clearTimeout(emailCheckTimer);
449            const email = emailInput.value.trim();
450            if (!email || !email.includes('@')) return;
451
452            emailCheckTimer = setTimeout(async () => {
453                try {
454                    const json = await fetch(restBase + 'check_user?email=' + encodeURIComponent(email), {
455                        headers: { 'X-WP-Nonce': nonce }
456                    }).then(r => r.json());
457
458                    if (json.exists) {
459                        emailMsg.textContent = 'An account with this email already exists. Please log in.';
460                        emailMsg.className = 'crpw-field-msg exists';
461                    } else {
462                        emailMsg.textContent = '';
463                        emailMsg.className = 'crpw-field-msg';
464                    }
465                } catch(e) {
466                    emailMsg.textContent = '';
467                }
468            }, 400);
469        });
470
471        emailInput.addEventListener('input', () => {
472            emailMsg.textContent = '';
473            emailMsg.className = 'crpw-field-msg';
474        });
475    }
476
477    // ================================================================
478    // Logout form
479    // ================================================================
480    const logoutForm = document.getElementById('crpw-logout-form');
481    if (logoutForm) {
482        logoutForm.addEventListener('submit', async function (e) {
483            e.preventDefault();
484            const redirectTo = logoutForm.querySelector('[name="redirect_to"]')?.value || homeUrl;
485            try {
486                const json = await apiPost('logout', { redirect_to: redirectTo });
487                window.location.href = json.next_url || homeUrl;
488            } catch (err) { window.location.href = homeUrl; }
489        });
490    }
491
492    // ================================================================
493    // Password toggle — handles all instances across all modals
494    // ================================================================
495    document.querySelectorAll('.crpw-toggle-pw').forEach(function (btn) {
496        const input = btn.closest('.crpw-input-wrap')?.querySelector('input');
497        if (!input) return;
498        btn.addEventListener('click', function () {
499            const hidden = input.type === 'password';
500            input.type   = hidden ? 'text' : 'password';
501            btn.setAttribute('aria-label', hidden ? 'Hide password' : 'Show password');
502            btn.innerHTML = hidden
503                ? '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"/><line x1="1" y1="1" x2="23" y2="23"/></svg>'
504                : '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>';
505        });
506    });
507
508})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.