PageSourceSearch

https://tpcomply.ai/wp-content/plugins/Wordpress-MultiPage-Form-1.8.1/assets/js/form-scripts.js?ver=1.9.3

js tpcomply.ai collected 2026-10-05 14:18:15 UTC 26,153 bytes, 695 lines download raw bytes

1/**
2 * Multi-Step Popup Form Scripts
3 * Two-Step Form
4 */
5
6(function ($) {
7    'use strict';
8
9    // Form Controller
10    class PTFMultiStepForm {
11        constructor(form) {
12            this.form = $(form);
13            this.currentStep = 1;
14            this.totalSteps = 2;
15            this.isSubmitting = false;
16
17            this.init();
18        }
19
20        init() {
21            this.bindEvents();
22            this.updateProgress();
23            this.form.attr('data-current-step', this.currentStep);
24            this.updateSubmitButtonState();
25            this.initRecaptchaV2();
26        }
27
28        /**
29         * Initialize reCAPTCHA v2 widget for Salesforce W2L browser-direct submission
30         */
31        initRecaptchaV2() {
32            if (!ptfForm.sfW2lRecaptcha || !ptfForm.sfW2lRecaptcha.active) return;
33
34            const widgetContainer = document.getElementById('ptf-recaptcha-v2-widget');
35            if (!widgetContainer) return;
36
37            let retryCount = 0;
38            const maxRetries = 20; // 10 seconds max
39
40            const renderWidget = () => {
41                if (typeof grecaptcha !== 'undefined' && typeof grecaptcha.render === 'function') {
42                    // Use grecaptcha.ready() for compatibility with both v2 and v3 script loading
43                    grecaptcha.ready(() => {
44                        try {
45                            this.recaptchaV2WidgetId = grecaptcha.render('ptf-recaptcha-v2-widget', {
46                                'sitekey': ptfForm.sfW2lRecaptcha.siteKey,
47                                'size': 'normal'
48                            });
49                        } catch (e) {
50                            console.error('reCAPTCHA v2 render error:', e);
51                        }
52                    });
53                } else if (retryCount < maxRetries) {
54                    retryCount++;
55                    setTimeout(renderWidget, 500);
56                } else {
57                    console.error('PTF: reCAPTCHA script failed to load after ' + maxRetries + ' retries.');
58                }
59            };
60
61            // Delay render to ensure script is loaded
62            if (document.readyState === 'complete') {
63                setTimeout(renderWidget, 300);
64            } else {
65                window.addEventListener('load', () => setTimeout(renderWidget, 300));
66            }
67        }
68
69        bindEvents() {
70            // Next button
71            this.form.on('click', '.ptf-btn-next', (e) => {
72                e.preventDefault();
73                this.nextStep();
74            });
75
76            // Previous button
77            this.form.on('click', '.ptf-btn-prev', (e) => {
78                e.preventDefault();
79                this.prevStep();
80            });
81
82            // Form submission
83            this.form.on('submit', (e) => {
84                e.preventDefault();
85                this.submitForm();
86            });
87
88            // Real-time validation
89            this.form.on('blur', 'input, select, textarea', (e) => {
90                this.validateField($(e.target));
91                this.updateSubmitButtonState();
92            });
93
94            // Email special check (during input)
95            this.form.on('input', 'input[data-corporate-only="true"]', (e) => {
96                this.checkCorporateEmail($(e.target));
97                this.updateSubmitButtonState();
98            });
99
100            // Checkbox change
101            this.form.on('change', 'input[type="checkbox"]', (e) => {
102                this.updateSubmitButtonState();
103
104                // Clear error for privacy consent checkbox
105                if ($(e.target).attr('name') === 'kvkk_consent') {
106                    const formGroup = $(e.target).closest('.ptf-consent-group');
107                    if ($(e.target).is(':checked')) {
108                        formGroup.removeClass('has-error');
109                        formGroup.find('.ptf-field-error').text('');
110                    }
111                }
112            });
113
114            // Clear error (on focus)
115            this.form.on('focus', 'input, select, textarea', (e) => {
116                const formGroup = $(e.target).closest('.ptf-form-group');
117                if (!$(e.target).attr('data-corporate-only')) {
118                    formGroup.removeClass('has-error');
119                }
120            });
121
122            // Enter key to go to next step
123            this.form.on('keypress', 'input', (e) => {
124                if (e.which === 13 && this.currentStep < this.totalSteps) {
125                    e.preventDefault();
126                    this.nextStep();
127                }
128            });
129
130            // Close error message
131            this.form.on('click', '.ptf-message-close', (e) => {
132                $(e.target).closest('.ptf-form-message').slideUp(200);
133            });
134        }
135
136        nextStep() {
137            if (this.validateStep(this.currentStep)) {
138                this.currentStep++;
139                this.showStep(this.currentStep);
140                this.updateProgress();
141                this.updateSubmitButtonState();
142            }
143        }
144
145        prevStep() {
146            if (this.currentStep > 1) {
147                this.currentStep--;
148                this.showStep(this.currentStep);
149                this.updateProgress();
150            }
151        }
152
153        showStep(step) {
154            // Hide all steps
155            this.form.find('.ptf-form-step').removeClass('active');
156
157            // Show target step
158            this.form.find(`.ptf-form-step[data-step="${step}"]`).addClass('active');
159
160            // Update data attribute (for CSS animations)
161            this.form.attr('data-current-step', step);
162
163            // Scroll to form top (if not popup)
164            if (!this.form.closest('.ptf-popup-container').length) {
165                $('html, body').animate({
166                    scrollTop: this.form.offset().top - 100
167                }, 300);
168            }
169        }
170
171        updateProgress() {
172            const progressSteps = this.form.find('.ptf-progress-step');
173
174            progressSteps.each((index, el) => {
175                const stepNum = $(el).data('step');
176                $(el).removeClass('active completed');
177
178                if (stepNum < this.currentStep) {
179                    $(el).addClass('completed');
180                } else if (stepNum === this.currentStep) {
181                    $(el).addClass('active');
182                }
183            });
184        }
185
186        validateStep(step) {
187            const stepEl = this.form.find(`.ptf-form-step[data-step="${step}"]`);
188            let isValid = true;
189
190            stepEl.find('[required]').each((index, field) => {
191                const $field = $(field);
192
193                if ($field.attr('name') === 'kvkk_consent') {
194                    return;
195                }
196
197                if (!this.validateField($field)) {
198                    isValid = false;
199                }
200            });
201
202            // Required multi-select question groups.
203            stepEl.find('.ptf-checkbox-group-inline[data-required="1"]').each((index, group) => {
204                const $group = $(group);
205                const formGroup = $group.closest('.ptf-form-group');
206                const errorEl = formGroup.find('.ptf-field-error');
207                const checkedCount = $group.find('input[type="checkbox"]:checked').length;
208
209                if (checkedCount === 0) {
210                    isValid = false;
211                    formGroup.addClass('has-error');
212                    errorEl.text(ptfForm.messages.required);
213                } else {
214                    formGroup.removeClass('has-error');
215                    errorEl.text('');
216                }
217            });
218
219            if (step === 1) {
220                const emailField = stepEl.find('input[name="email"]');
221                if (emailField.val() && !this.isCorporateEmail(emailField.val())) {
222                    isValid = false;
223                }
224
225                const kvkkCheckbox = stepEl.find('input[name="kvkk_consent"]');
226                if (!kvkkCheckbox.is(':checked')) {
227                    isValid = false;
228                    const formGroup = kvkkCheckbox.closest('.ptf-consent-group');
229                    formGroup.addClass('has-error');
230                    formGroup.find('.ptf-field-error').text(ptfForm.messages.checkbox_required);
231                }
232            }
233
234            if (!isValid) {
235                stepEl.find('.ptf-form-group.has-error:first input, .ptf-form-group.has-error:first select, .ptf-form-group.has-error:first textarea').focus();
236            }
237
238            return isValid;
239        }
240
241        validateField(field) {
242            const formGroup = field.closest('.ptf-form-group');
243            const errorEl = formGroup.find('.ptf-field-error');
244            const type = field.attr('type');
245            let value = field.val() ? String(field.val()).trim() : '';
246
247            if (type === 'radio') {
248                const name = field.attr('name');
249                const checked = formGroup.find(`input[type="radio"][name="${name}"]:checked`);
250                value = checked.length ? String(checked.val()).trim() : '';
251            }
252
253            let isValid = true;
254            let errorMessage = '';
255
256            // Required field validation
257            if (field.prop('required') && !value) {
258                isValid = false;
259                errorMessage = ptfForm.messages.required;
260            }
261
262            // Email format validation
263            else if (type === 'email' && value) {
264                if (!this.isValidEmail(value)) {
265                    isValid = false;
266                    errorMessage = ptfForm.messages.email;
267                } else if (field.attr('data-corporate-only') === 'true' && !this.isCorporateEmail(value)) {
268                    isValid = false;
269                    errorMessage = ptfForm.messages.corporate_email;
270                }
271            }
272
273            // Phone format validation
274            else if (type === 'tel' && value && !this.isValidPhone(value)) {
275                isValid = false;
276                errorMessage = ptfForm.messages.phone;
277            }
278
279            // Update error state
280            if (!isValid) {
281                formGroup.addClass('has-error');
282                errorEl.text(errorMessage);
283            } else {
284                formGroup.removeClass('has-error');
285                errorEl.text('');
286            }
287
288            return isValid;
289        }
290
291        checkCorporateEmail(field) {
292            const value = field.val().trim();
293            const formGroup = field.closest('.ptf-form-group');
294            const errorEl = formGroup.find('.ptf-field-error');
295
296            if (value && value.includes('@')) {
297                if (!this.isCorporateEmail(value)) {
298                    formGroup.addClass('has-error');
299                    errorEl.text(ptfForm.messages.corporate_email);
300                    return false;
301                } else if (this.isValidEmail(value)) {
302                    formGroup.removeClass('has-error');
303                    errorEl.text('');
304                    return true;
305                }
306            }
307            return true;
308        }
309
310        isCorporateEmail(email) {
311            if (!email || !email.includes('@')) return false;
312
313            const domain = email.split('@')[1].toLowerCase();
314            return !ptfForm.blockedDomains.includes(domain);
315        }
316
317        isValidEmail(email) {
318            const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
319            return regex.test(email);
320        }
321
322        isValidPhone(phone) {
323            const regex = /^[\d\s+\-()]{10,20}$/;
324            return regex.test(phone);
325        }
326
327        updateSubmitButtonState() {
328            const submitBtn = this.form.find('.ptf-btn-submit');
329            submitBtn.prop('disabled', false);
330        }
331
332        submitForm() {
333            // Validate all steps
334            for (let step = 1; step <= this.totalSteps; step++) {
335                if (!this.validateStep(step)) {
336                    this.showStep(step);
337                    this.currentStep = step;
338                    this.updateProgress();
339                    return;
340                }
341            }
342
343            if (this.isSubmitting) return;
344            this.isSubmitting = true;
345
346            // Show loading
347            this.showLoading(true);
348
349            // Validate reCAPTCHA v2 for Salesforce W2L (user must click "I'm not a robot" checkbox)
350            if (ptfForm.sfW2lRecaptcha && ptfForm.sfW2lRecaptcha.active) {
351                let v2Response = '';
352                if (typeof grecaptcha !== 'undefined' && this.recaptchaV2WidgetId !== undefined) {
353                    v2Response = grecaptcha.getResponse(this.recaptchaV2WidgetId);
354                }
355                if (!v2Response) {
356                    this.isSubmitting = false;
357                    this.showLoading(false);
358                    this.showError(ptfForm.messages.recaptcha_error || 'Please complete the reCAPTCHA verification.');
359                    return;
360                }
361            }
362
363            // Check if reCAPTCHA should be used
364            const recaptchaEnabled = ptfForm.recaptcha &&
365                                     ptfForm.recaptcha.enabled === true &&
366                                     ptfForm.recaptcha.siteKey &&
367                                     ptfForm.recaptcha.siteKey.length > 0 &&
368                                     typeof grecaptcha !== 'undefined';
369
370            // If reCAPTCHA is enabled and configured, get token
371            if (recaptchaEnabled) {
372                try {
373                    grecaptcha.ready(() => {
374                        try {
375                            grecaptcha.execute(ptfForm.recaptcha.siteKey, {action: 'submit_quote_form'})
376                                .then((token) => {
377                                    this.sendFormData(token);
378                                })
379                                .catch((error) => {
380                                    console.error('reCAPTCHA error:', error);
381                                    // Show error to user instead of silent fail
382                                    this.isSubmitting = false;
383                                    this.showLoading(false);
384                                    this.showError(ptfForm.messages.recaptcha_error || 'reCAPTCHA verification failed. Please try again.');
385                                });
386                        } catch (error) {
387                            console.error('reCAPTCHA execute error:', error);
388                            this.isSubmitting = false;
389                            this.showLoading(false);
390                            this.showError(ptfForm.messages.recaptcha_error || 'reCAPTCHA verification failed. Please check site configuration.');
391                        }
392                    });
393                } catch (error) {
394                    console.error('reCAPTCHA ready error:', error);
395                    this.isSubmitting = false;
396                    this.showLoading(false);
397                    this.showError(ptfForm.messages.recaptcha_error || 'reCAPTCHA failed to initialize.');
398                }
399            } else {
400                // Submit directly if reCAPTCHA is not configured or not enabled
401                this.sendFormData('');
402            }
403        }
404
405        sendFormData(recaptchaToken) {
406            // Collect form data
407            let formData = this.form.serialize();
408
409            // Add reCAPTCHA token
410            if (recaptchaToken) {
411                formData += '&recaptcha_token=' + encodeURIComponent(recaptchaToken);
412            }
413
414            // Send via AJAX
415            $.ajax({
416                url: ptfForm.ajaxurl,
417                type: 'POST',
418                data: formData,
419                success: (response) => {
420                    this.isSubmitting = false;
421                    this.showLoading(false);
422
423                    if (response.success) {
424                        // If W2L browser-direct data is returned, submit to Salesforce from browser
425                        if (response.data.sfW2lData && ptfForm.sfW2lRecaptcha && ptfForm.sfW2lRecaptcha.active) {
426                            this.submitToSalesforceViaIframe(response.data.sfW2lData);
427                        }
428                        this.showSuccess(response.data.message);
429                    } else {
430                        // Special reCAPTCHA error message
431                        if (response.data.recaptcha_failed) {
432                            this.showError(ptfForm.messages.recaptcha_error || response.data.message);
433                        } else {
434                            this.showError(response.data.message, response.data.errors);
435                        }
436                    }
437                },
438                error: (xhr, status, error) => {
439                    this.isSubmitting = false;
440                    this.showLoading(false);
441                    this.showError(ptfForm.messages.error);
442                    console.error('Form submission error:', error);
443                }
444            });
445        }
446
447        /**
448         * Submit mapped W2L data directly to Salesforce from the browser via hidden iframe.
449         * This bypasses server-side POST limitations and allows Salesforce to verify the reCAPTCHA v2 token.
450         */
451        submitToSalesforceViaIframe(w2lData) {
452            try {
453                // Get the reCAPTCHA v2 response token from the widget
454                let recaptchaResponse = '';
455                if (typeof grecaptcha !== 'undefined' && this.recaptchaV2WidgetId !== undefined) {
456                    recaptchaResponse = grecaptcha.getResponse(this.recaptchaV2WidgetId);
457                }
458
459                if (!recaptchaResponse) {
460                    console.warn('PTF: No reCAPTCHA v2 response token available for Salesforce W2L submission.');
461                    return;
462                }
463
464                // Create a hidden form targeting the hidden iframe
465                const hiddenForm = document.createElement('form');
466                hiddenForm.method = 'POST';
467                hiddenForm.action = 'https://webto.salesforce.com/servlet/servlet.WebToLead?encoding=UTF-8';
468                hiddenForm.target = 'ptf_sf_w2l_iframe';
469                hiddenForm.style.display = 'none';
470
471                // Add all mapped W2L fields
472                for (const [key, value] of Object.entries(w2lData)) {
473                    const input = document.createElement('input');
474                    input.type = 'hidden';
475                    input.name = key;
476                    input.value = value;
477                    hiddenForm.appendChild(input);
478                }
479
480                // Add captcha_settings with fresh timestamp
481                if (ptfForm.sfW2lRecaptcha.captchaSettings) {
482                    let captchaObj;
483                    try {
484                        captchaObj = JSON.parse(ptfForm.sfW2lRecaptcha.captchaSettings);
485                    } catch (e) {
486                        captchaObj = null;
487                    }
488                    if (captchaObj) {
489                        // Salesforce expects Unix timestamp in milliseconds as a stringified number
490                        captchaObj.ts = JSON.stringify(new Date().getTime());
491                        const captchaInput = document.createElement('input');
492                        captchaInput.type = 'hidden';
493                        captchaInput.name = 'captcha_settings';
494                        captchaInput.value = JSON.stringify(captchaObj);
495                        hiddenForm.appendChild(captchaInput);
496                    }
497                }
498
499                // Add the reCAPTCHA v2 response token
500                const recaptchaInput = document.createElement('input');
501                recaptchaInput.type = 'hidden';
502                recaptchaInput.name = 'g-recaptcha-response';
503                recaptchaInput.value = recaptchaResponse;
504                hiddenForm.appendChild(recaptchaInput);
505
506                // Append, submit, and cleanup
507                document.body.appendChild(hiddenForm);
508                hiddenForm.submit();
509
510                // Remove the form after a short delay
511                setTimeout(() => {
512                    if (hiddenForm.parentNode) {
513                        hiddenForm.parentNode.removeChild(hiddenForm);
514                    }
515                }, 2000);
516
517                console.log('PTF: Salesforce W2L browser-direct submission sent via hidden iframe.');
518            } catch (error) {
519                console.error('PTF: Salesforce W2L browser-direct submission failed:', error);
520            }
521        }
522
523        showLoading(show) {
524            const loadingEl = this.form.find('.ptf-form-loading');
525            const submitBtn = this.form.find('.ptf-btn-submit');
526
527            if (show) {
528                loadingEl.show();
529                submitBtn.prop('disabled', true).text(ptfForm.messages.sending);
530            } else {
531                loadingEl.hide();
532                submitBtn.prop('disabled', false);
533            }
534        }
535
536        showSuccess(message) {
537            // Hide form steps and progress
538            this.form.find('.ptf-form-step').hide();
539            this.form.find('.ptf-form-progress').hide();
540
541            // Show success message
542            const successEl = this.form.find('.ptf-form-success');
543            if (message) {
544                successEl.find('p').text(message);
545            }
546            successEl.show();
547
548            // Close popup after 5 seconds (if it's a popup)
549            const popup = this.form.closest('.ptf-popup-overlay');
550            if (popup.length) {
551                setTimeout(() => {
552                    PTFPopup.close();
553                    // Reset form
554                    setTimeout(() => this.resetForm(), 500);
555                }, 3000);
556            }
557        }
558
559        showError(message, fieldErrors = {}) {
560            // Show general error message in the form
561            if (message) {
562                const errorEl = this.form.find('.ptf-form-error-message');
563                errorEl.find('.ptf-message-text').text(message);
564                errorEl.slideDown(200);
565
566                // Auto hide after 5 seconds
567                setTimeout(() => {
568                    errorEl.slideUp(200);
569                }, 5000);
570            }
571
572            // Field-specific errors
573            if (fieldErrors && Object.keys(fieldErrors).length > 0) {
574                for (const field in fieldErrors) {
575                    let input = this.form.find(`[name="${field}"]`);
576                    if (!input.length) {
577                        input = this.form.find(`[name="${field}[]"]`);
578                    }
579                    const formGroup = input.first().closest('.ptf-form-group');
580                    formGroup.addClass('has-error');
581                    formGroup.find('.ptf-field-error').text(fieldErrors[field]);
582                }
583
584                // Go to first error step
585                const firstError = this.form.find('.ptf-form-group.has-error:first');
586                const errorStep = firstError.closest('.ptf-form-step').data('step');
587                if (errorStep && errorStep !== this.currentStep) {
588                    this.showStep(errorStep);
589                    this.currentStep = errorStep;
590                    this.updateProgress();
591                }
592            }
593        }
594
595        resetForm() {
596            // Reset form
597            this.form[0].reset();
598            this.currentStep = 1;
599
600            // Reset view
601            this.form.find('.ptf-form-step').hide();
602            this.form.find(`.ptf-form-step[data-step="1"]`).addClass('active').show();
603            this.form.find('.ptf-form-progress').show();
604            this.form.find('.ptf-form-success').hide();
605            this.form.find('.ptf-form-error-message').hide();
606            this.form.find('.ptf-form-group').removeClass('has-error');
607            this.form.find('.ptf-btn-submit').prop('disabled', true);
608
609            this.updateProgress();
610            this.form.attr('data-current-step', 1);
611        }
612    }
613
614    // Popup Controller
615    const PTFPopup = {
616        overlay: null,
617
618        init() {
619            this.overlay = $('#ptf-popup-overlay');
620            this.bindEvents();
621        },
622
623        bindEvents() {
624            // Popup triggers
625            $(document).on('click', '.ptf-popup-trigger', (e) => {
626                e.preventDefault();
627                this.open();
628            });
629
630            // Close button
631            $(document).on('click', '.ptf-popup-close', (e) => {
632                e.preventDefault();
633                this.close();
634            });
635
636            // Close on overlay click
637            this.overlay.on('click', (e) => {
638                if ($(e.target).is('.ptf-popup-overlay')) {
639                    this.close();
640                }
641            });
642
643            // Close on ESC key
644            $(document).on('keydown', (e) => {
645                if (e.key === 'Escape' && this.overlay.hasClass('active')) {
646                    this.close();
647                }
648            });
649        },
650
651        open() {
652            this.overlay.css('display', 'flex');
653
654            // Small delay for animation
655            setTimeout(() => {
656                this.overlay.addClass('active');
657            }, 10);
658
659            // Prevent body scroll
660            $('body').css('overflow', 'hidden');
661
662            // Focus first input
663            setTimeout(() => {
664                this.overlay.find('input:visible:first').focus();
665            }, 300);
666        },
667
668        close() {
669            this.overlay.removeClass('active');
670
671            // Hide after animation
672            setTimeout(() => {
673                this.overlay.css('display', 'none');
674            }, 300);
675
676            // Restore body scroll
677            $('body').css('overflow', '');
678        }
679    };
680
681    // Initialize on page load
682    $(document).ready(function () {
683        // Initialize all forms
684        $('.ptf-multistep-form').each(function () {
685            new PTFMultiStepForm(this);
686        });
687
688        // Initialize popup
689        PTFPopup.init();
690    });
691
692    // Global access
693    window.PTFPopup = PTFPopup;
694
695})(jQuery);

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.