PageSourceSearch

https://akarali.com/wp-content/plugins/simple-cloudflare-turnstile/js/integrations/elementor-forms.js?ver=3.0

js akarali.com collected 2026-09-24 19:35:23 UTC 22,159 bytes, 523 lines download raw bytes

1// Elementor 4 "Atomic" forms are elements rather than widgets, so they carry no .elementor-form class.
2var CFT_ATOMIC_FORM_SELECTOR = 'form[data-element_type="e-form"]';
3var CFT_ATOMIC_AJAX_ACTION = 'elementor_pro_atomic_forms_send_form';
4
5function cfturnstile_elementor_set_submit(btn, enabled) {
6  if (!btn) return;
7  btn.style.pointerEvents = enabled ? 'auto' : 'none';
8  btn.style.opacity     = enabled ? '1'    : '0.5';
9}
10
11/**
12 * Current Turnstile token for a form, whichever way it is available.
13 */
14function cfturnstile_elementor_token(form) {
15  var widget = form ? form.querySelector('.cf-turnstile') : null;
16  if (!widget) return '';
17  var token = '';
18  if (window.turnstile) {
19    try { token = turnstile.getResponse(widget) || ''; } catch (e) {}
20  }
21  if (!token) {
22    var responseInput = form.querySelector('[name="cf-turnstile-response"]');
23    token = responseInput ? (responseInput.value || '') : '';
24  }
25  return token;
26}
27
28function cfturnstile_init_elementor_forms() {
29  var settings = window.cfturnstileElementorSettings || {};
30  var sitekey = settings.sitekey || '';
31  var position = settings.position || 'before';
32  var mode = settings.mode || 'turnstile';
33  var recaptchaSiteKey = settings.recaptchaSiteKey || '';
34  var disableSubmit = settings.disableSubmit || false;
35  var widgetSize = settings.size || 'normal';
36  
37  if (!window._cft_elementor_idx) { window._cft_elementor_idx = 0; }
38  var elementorForms = document.querySelectorAll('.elementor-form:not(.cft-processed)');
39  elementorForms.forEach(function(form) {
40    var index = window._cft_elementor_idx++;
41    if (form.querySelector('.cf-turnstile') || form.querySelector('.g-recaptcha') || form.querySelector('input[name="cfturnstile_failsafe"]')) {
42      form.classList.add('cft-processed');
43      return;
44    }
45    
46    var submitButton = form.querySelector('button[type="submit"]');
47
48    // Failsafe modes: inject marker and optionally reCAPTCHA widget.
49    if (submitButton && (mode === 'allow' || mode === 'recaptcha')) {
50      var marker = document.createElement('input');
51      marker.type = 'hidden';
52      marker.name = 'cfturnstile_failsafe';
53      marker.value = mode;
54      form.appendChild(marker);
55
56      if (mode === 'recaptcha' && recaptchaSiteKey) {
57        var recaptchaDiv = document.createElement('div');
58        recaptchaDiv.className = 'g-recaptcha';
59        recaptchaDiv.setAttribute('data-sitekey', recaptchaSiteKey);
60        recaptchaDiv.style.cssText = 'display: block; margin: 10px 0 15px 0; width: 100%;';
61
62        if (position === 'after') {
63          submitButton.parentNode.insertBefore(recaptchaDiv, submitButton.nextSibling);
64        } else if (position === 'afterform') {
65          form.appendChild(recaptchaDiv);
66        } else {
67          submitButton.parentNode.insertBefore(recaptchaDiv, submitButton);
68        }
69      }
70
71      form.classList.add('cft-processed');
72      return;
73    }
74
75    if (submitButton && window.turnstile && sitekey) {
76      // Disable submit button if option is enabled
77      if (disableSubmit) {
78        cfturnstile_elementor_set_submit(submitButton, false);
79      }
80
81      var labelEl = null;
82      if (settings.labelEnable && settings.labelText) {
83        labelEl = document.createElement('p');
84        labelEl.className = 'cfturnstile-widget-label';
85        labelEl.style.cssText = 'font-size: 14px; margin: 0 0 6px 0; width: 100%;';
86        if ((settings.appearance || 'always') === 'interaction-only') {
87          labelEl.className += ' cfturnstile-widget-label-interaction';
88          labelEl.style.display = 'none';
89        }
90        var smallEl = document.createElement('small');
91        smallEl.textContent = settings.labelText;
92        labelEl.appendChild(smallEl);
93      }
94
95      var turnstileDiv = document.createElement('div');
96      turnstileDiv.className = 'elementor-turnstile-field cf-turnstile';
97      turnstileDiv.id = 'cf-turnstile-elementor-fallback-' + index;
98      turnstileDiv.style.cssText = 'display: block; margin: 10px 0 15px 0; width: 100%;';
99
100      if (position === 'after') {
101        if (labelEl) submitButton.parentNode.insertBefore(labelEl, submitButton.nextSibling);
102        submitButton.parentNode.insertBefore(turnstileDiv, labelEl ? labelEl.nextSibling : submitButton.nextSibling);
103      } else if (position === 'afterform') {
104        if (labelEl) form.appendChild(labelEl);
105        form.appendChild(turnstileDiv);
106      } else {
107        if (labelEl) submitButton.parentNode.insertBefore(labelEl, submitButton);
108        submitButton.parentNode.insertBefore(turnstileDiv, submitButton);
109      }
110      
111      turnstile.render('#cf-turnstile-elementor-fallback-' + index, {
112        sitekey: sitekey,
113        theme: settings.theme || 'auto',
114        size: widgetSize,
115        appearance: settings.appearance || 'always',
116        callback: function(token) {
117          // Re-enable submit button when Turnstile is complete
118          if (disableSubmit && submitButton) {
119            cfturnstile_elementor_set_submit(submitButton, true);
120          }
121          if (typeof turnstileElementorCallback === 'function') {
122            turnstileElementorCallback(token);
123          }
124        },
125        'error-callback': function() {
126          if (disableSubmit && submitButton) { cfturnstile_elementor_set_submit(submitButton, false); }
127        },
128        'expired-callback': function() {
129          if (disableSubmit && submitButton) { cfturnstile_elementor_set_submit(submitButton, false); }
130        }
131      });
132
133      if (labelEl && labelEl.classList.contains('cfturnstile-widget-label-interaction') && typeof window.cfturnstileInitInteractionLabels === 'function') {
134        window.cfturnstileInitInteractionLabels();
135      }
136
137      form.classList.add('cft-processed');
138    }
139  });
140}
141
142document.addEventListener('DOMContentLoaded', function() {
143  cfturnstile_init_elementor_forms();
144});
145
146// Listen to Elementor frontend init to handle cached elements
147jQuery(window).on('elementor/frontend/init', function() {
148  cfturnstile_init_elementor_forms();
149
150  // Hook into Elementor's widget ready system for forms loaded dynamically (e.g. in popups)
151  if (window.elementorFrontend && elementorFrontend.hooks) {
152    elementorFrontend.hooks.addAction('frontend/element_ready/form.default', function($scope) {
153      cfturnstile_init_elementor_forms();
154    });
155    elementorFrontend.hooks.addAction('frontend/element_ready/login.default', function($scope) {
156      cfturnstile_init_elementor_forms();
157    });
158  }
159});
160
161// Re-render Turnstile only after Elementor reports a submit error (e.g. failed validation)
162function cfturnstile_elementor_rerender(form) {
163  if (!form || !window.turnstile) return;
164  var settings = window.cfturnstileElementorSettings || {};
165  var widget = form.querySelector('.cf-turnstile');
166  if (!widget) return;
167  var submitButton = form.querySelector('button[type="submit"]');
168  var disableSubmit = settings.disableSubmit || false;
169  if (disableSubmit && submitButton) { cfturnstile_elementor_set_submit(submitButton, false); }
170  try { turnstile.remove(widget); } catch (e) {}
171  turnstile.render(widget, {
172    sitekey: settings.sitekey,
173    theme: settings.theme || 'auto',
174    size: settings.size || 'normal',
175    appearance: settings.appearance || 'always',
176    callback: function(token) {
177      if (disableSubmit && submitButton) { cfturnstile_elementor_set_submit(submitButton, true); }
178      if (typeof turnstileElementorCallback === 'function') {
179        turnstileElementorCallback(token);
180      }
181    },
182    'error-callback': function() {
183      if (disableSubmit && submitButton) { cfturnstile_elementor_set_submit(submitButton, false); }
184    },
185    'expired-callback': function() {
186      if (disableSubmit && submitButton) { cfturnstile_elementor_set_submit(submitButton, false); }
187    }
188  });
189}
190
191jQuery(document).on('submit_error submit_success', '.elementor-form', function() {
192  var settings = window.cfturnstileElementorSettings || {};
193  if ((settings.mode || 'turnstile') !== 'turnstile') return;
194  cfturnstile_elementor_rerender(this);
195});
196
197// Block submission until Turnstile is completed (client-side guard).
198// Server-side validation in cfturnstile_elementor_check is the source of truth;
199// this just avoids a wasted round-trip and gives immediate feedback.
200document.addEventListener('submit', function(event) {
201  var form = event.target;
202  if (!form.classList) return;
203  var isClassic = form.classList.contains('elementor-form');
204  var isAtomic = !isClassic && form.matches && form.matches(CFT_ATOMIC_FORM_SELECTOR);
205  if (!isClassic && !isAtomic) return;
206  var settings = window.cfturnstileElementorSettings || {};
207  if ((settings.mode || 'turnstile') !== 'turnstile' || !window.turnstile) return;
208
209  var widget = form.querySelector('.cf-turnstile');
210  if (!widget) return;
211  if (!cfturnstile_elementor_token(form)) {
212    event.preventDefault();
213    event.stopImmediatePropagation();
214    try { widget.scrollIntoView({ behavior: 'smooth', block: 'center' }); } catch (e) {}
215  }
216}, true);
217
218// Handle Elementor popup show events (jQuery event - must use jQuery to listen)
219jQuery(document).on('elementor/popup/show', function(event, id, instance) {
220  setTimeout(function() {
221    // First, inject Turnstile into any unprocessed forms inside the popup
222    cfturnstile_init_elementor_forms();
223
224    var settings = window.cfturnstileElementorSettings || {};
225    var mode = settings.mode || 'turnstile';
226    var disableSubmit = settings.disableSubmit || false;
227    if (mode !== 'turnstile' || !window.turnstile) {
228      return;
229    }
230
231    // Re-render all Turnstile widgets inside popup modals (they need explicit render after DOM insertion)
232    var popupTurnstiles = document.querySelectorAll('.elementor-popup-modal .cf-turnstile');
233    popupTurnstiles.forEach(function(widget) {
234      var failedText = widget.parentNode ? widget.parentNode.querySelector('.cf-turnstile-failed-text') : null;
235      if (failedText) {
236        failedText.style.display = 'none';
237      }
238
239      // Find the form and submit button for this widget
240      var form = widget.closest('.elementor-form');
241      var submitButton = form ? form.querySelector('button[type="submit"]') : null;
242
243      // Disable submit button if option is enabled
244      if (disableSubmit && submitButton) { cfturnstile_elementor_set_submit(submitButton, false); }
245      
246      turnstile.remove(widget);
247      turnstile.render(widget, {
248        sitekey: cfturnstileElementorSettings.sitekey,
249        appearance: cfturnstileElementorSettings.appearance || 'always',
250        callback: function(token) {
251          // Re-enable submit button when Turnstile is complete
252          if (disableSubmit && submitButton) { cfturnstile_elementor_set_submit(submitButton, true); }
253          if (typeof turnstileElementorCallback === 'function') {
254            turnstileElementorCallback(token);
255          }
256        },
257        'error-callback': function() {
258          if (disableSubmit && submitButton) { cfturnstile_elementor_set_submit(submitButton, false); }
259        },
260        'expired-callback': function() {
261          if (disableSubmit && submitButton) { cfturnstile_elementor_set_submit(submitButton, false); }
262        },
263        theme: cfturnstileElementorSettings.theme || 'auto',
264        size: cfturnstileElementorSettings.size || 'normal'
265      });
266    });
267  }, 500);
268});
269
270/* ---------------------------------------------------------------------------
271 * Elementor Atomic Forms (Elementor 4 "e-form" elements)
272 *
273 * Atomic forms are submitted by an Alpine handler bound to the form, which posts
274 * a FormData payload built only from the field widgets Elementor itself rendered
275 * (input/textarea/select carrying data-interaction-id). A hidden token input
276 * sitting inside the form is therefore never sent, so the token is appended to
277 * the outgoing request instead - see cfturnstile_atomic_patch_fetch() below.
278 * ------------------------------------------------------------------------ */
279
280function cfturnstile_atomic_submit_button(form) {
281  return form ? form.querySelector('button[type="submit"], input[type="submit"]') : null;
282}
283
284/**
285 * Horizontal alignment of the widget inside its (column flex) wrapper.
286 */
287function cfturnstile_atomic_align(align) {
288  if (align === 'center') return 'center';
289  if (align === 'right') return 'flex-end';
290  return 'flex-start';
291}
292
293/**
294 * Place a node according to the configured widget position. Atomic forms are flex
295 * containers, so the node is inserted as a full width row of its own.
296 */
297function cfturnstile_atomic_insert(form, node, position, submitButton) {
298  if (position === 'afterform' || !submitButton || !submitButton.parentNode) {
299    form.appendChild(node);
300  } else if (position === 'after') {
301    submitButton.parentNode.insertBefore(node, submitButton.nextSibling);
302  } else {
303    submitButton.parentNode.insertBefore(node, submitButton);
304  }
305}
306
307function cfturnstile_atomic_render_options(form) {
308  var settings = window.cfturnstileElementorSettings || {};
309  var disableSubmit = settings.disableSubmit || false;
310  var submitButton = cfturnstile_atomic_submit_button(form);
311  return {
312    sitekey: settings.sitekey,
313    theme: settings.theme || 'auto',
314    size: settings.size || 'normal',
315    appearance: settings.appearance || 'always',
316    action: 'elementor-atomic-form',
317    callback: function(token) {
318      if (disableSubmit) { cfturnstile_elementor_set_submit(submitButton, true); }
319      if (typeof turnstileElementorCallback === 'function') {
320        turnstileElementorCallback(token);
321      }
322    },
323    'error-callback': function() {
324      if (disableSubmit) { cfturnstile_elementor_set_submit(submitButton, false); }
325    },
326    'expired-callback': function() {
327      if (disableSubmit) { cfturnstile_elementor_set_submit(submitButton, false); }
328    }
329  };
330}
331
332function cfturnstile_init_elementor_atomic_forms() {
333  var settings = window.cfturnstileElementorSettings || {};
334  var sitekey = settings.sitekey || '';
335  var position = settings.position || 'before';
336  var mode = settings.mode || 'turnstile';
337  var recaptchaSiteKey = settings.recaptchaSiteKey || '';
338  var disableSubmit = settings.disableSubmit || false;
339
340  if (!window._cft_atomic_idx) { window._cft_atomic_idx = 0; }
341
342  // Elementor re-renders form elements in place (editor edits, popups, Alpine refreshes),
343  // which drops the injected widget. Release those forms so they are processed again.
344  document.querySelectorAll(CFT_ATOMIC_FORM_SELECTOR + '.cft-processed').forEach(function(form) {
345    if (!form.querySelector('.cf-turnstile, .g-recaptcha, input[name="cfturnstile_failsafe"]')) {
346      form.classList.remove('cft-processed');
347    }
348  });
349
350  document.querySelectorAll(CFT_ATOMIC_FORM_SELECTOR + ':not(.cft-processed)').forEach(function(form) {
351    if (form.querySelector('.cf-turnstile, .g-recaptcha, input[name="cfturnstile_failsafe"]')) {
352      form.classList.add('cft-processed');
353      return;
354    }
355
356    var submitButton = cfturnstile_atomic_submit_button(form);
357
358    // Failsafe modes: post a marker, and render reCAPTCHA in its place if configured.
359    if (mode === 'allow' || mode === 'recaptcha') {
360      var marker = document.createElement('input');
361      marker.type = 'hidden';
362      marker.name = 'cfturnstile_failsafe';
363      marker.value = mode;
364      form.appendChild(marker);
365
366      if (mode === 'recaptcha' && recaptchaSiteKey) {
367        var recaptchaWrap = document.createElement('div');
368        recaptchaWrap.className = 'cfturnstile-atomic-field';
369        recaptchaWrap.style.cssText = 'display: flex; flex-direction: column; align-items: ' + cfturnstile_atomic_align(settings.align) + '; flex: 0 0 100%; width: 100%; margin: 10px 0 15px 0;';
370        var recaptchaDiv = document.createElement('div');
371        recaptchaDiv.className = 'g-recaptcha';
372        recaptchaDiv.setAttribute('data-sitekey', recaptchaSiteKey);
373        recaptchaWrap.appendChild(recaptchaDiv);
374        cfturnstile_atomic_insert(form, recaptchaWrap, position, submitButton);
375      }
376
377      form.classList.add('cft-processed');
378      return;
379    }
380
381    // The submit button is rendered as a child widget, so it can still be missing on an
382    // early pass. Wait for the next pass rather than injecting into a half built form.
383    if (!submitButton || !window.turnstile || !sitekey) return;
384
385    if (disableSubmit) {
386      cfturnstile_elementor_set_submit(submitButton, false);
387    }
388
389    var index = window._cft_atomic_idx++;
390
391    var wrap = document.createElement('div');
392    wrap.className = 'cfturnstile-atomic-field';
393    wrap.style.cssText = 'display: flex; flex-direction: column; align-items: ' + cfturnstile_atomic_align(settings.align) + '; flex: 0 0 100%; width: 100%; margin: 10px 0 15px 0;';
394
395    if (settings.labelEnable && settings.labelText) {
396      var labelEl = document.createElement('p');
397      labelEl.className = 'cfturnstile-widget-label';
398      labelEl.style.cssText = 'font-size: 14px; margin: 0 0 6px 0;';
399      if ((settings.appearance || 'always') === 'interaction-only') {
400        labelEl.className += ' cfturnstile-widget-label-interaction';
401        labelEl.style.display = 'none';
402      }
403      var smallEl = document.createElement('small');
404      smallEl.textContent = settings.labelText;
405      labelEl.appendChild(smallEl);
406      wrap.appendChild(labelEl);
407    }
408
409    var turnstileDiv = document.createElement('div');
410    turnstileDiv.className = 'elementor-turnstile-field cf-turnstile';
411    turnstileDiv.id = 'cf-turnstile-elementor-atomic-' + index;
412    wrap.appendChild(turnstileDiv);
413
414    cfturnstile_atomic_insert(form, wrap, position, submitButton);
415
416    turnstile.render(turnstileDiv, cfturnstile_atomic_render_options(form));
417
418    if (settings.labelEnable && (settings.appearance || 'always') === 'interaction-only' && typeof window.cfturnstileInitInteractionLabels === 'function') {
419      window.cfturnstileInitInteractionLabels();
420    }
421
422    form.classList.add('cft-processed');
423  });
424}
425
426/**
427 * Reset a widget after its form has been submitted, so a retry gets a fresh token.
428 * Elementor calls form.reset() on success, which empties the token input while the
429 * widget still holds the spent token - the global refresh helper cannot detect that.
430 */
431function cfturnstile_atomic_reset(form) {
432  if (!form || !window.turnstile) return;
433  var widget = form.querySelector('.cf-turnstile');
434  if (!widget) return;
435  var settings = window.cfturnstileElementorSettings || {};
436  if (settings.disableSubmit) {
437    cfturnstile_elementor_set_submit(cfturnstile_atomic_submit_button(form), false);
438  }
439  try { turnstile.reset(widget); } catch (e) {}
440}
441
442/**
443 * Attach the token to the atomic form request.
444 *
445 * Elementor builds the FormData itself and only includes its own field widgets, so
446 * there is no markup based way to get the token to the server. The wrapper only
447 * touches requests carrying the atomic form action and leaves everything else alone.
448 */
449function cfturnstile_atomic_patch_fetch() {
450  if (window._cfturnstileAtomicFetchPatched) return;
451  var originalFetch = window.fetch;
452  if (typeof originalFetch !== 'function') return;
453  window._cfturnstileAtomicFetchPatched = true;
454
455  window.fetch = function(input, init) {
456    var form = null;
457    try {
458      var body = init && init.body;
459      if (body instanceof FormData && body.get('action') === CFT_ATOMIC_AJAX_ACTION) {
460        var formId = body.get('form_id');
461        if (formId && /^[A-Za-z0-9_-]+$/.test(formId)) {
462          form = document.querySelector(CFT_ATOMIC_FORM_SELECTOR + '[data-id="' + formId + '"]');
463        }
464        if (form) {
465          var token = cfturnstile_elementor_token(form);
466          if (token) {
467            body.set('cf-turnstile-response', token);
468          }
469          var failsafe = form.querySelector('input[name="cfturnstile_failsafe"]');
470          if (failsafe && failsafe.value) {
471            body.set('cfturnstile_failsafe', failsafe.value);
472          }
473          var recaptcha = form.querySelector('textarea[name="g-recaptcha-response"], input[name="g-recaptcha-response"]');
474          if (recaptcha && recaptcha.value) {
475            body.set('g-recaptcha-response', recaptcha.value);
476          }
477        }
478      }
479    } catch (e) {}
480
481    // Callers reaching fetch from strict mode code (bundles, ES modules) invoke it bare, so
482    // `this` arrives undefined. Current Chrome tolerates that, but engines that hold fetch to
483    // its WebIDL receiver throw "Illegal invocation" - fall back to the global rather than
484    // making that a property of whether this plugin happens to be active.
485    var request = originalFetch.apply(this || window, arguments);
486
487    if (form) {
488      var reset = function() {
489        // Let Elementor apply its own result handling (including form.reset()) first.
490        setTimeout(function() { cfturnstile_atomic_reset(form); }, 500);
491      };
492      request.then(reset, reset);
493    }
494
495    return request;
496  };
497}
498
499(function() {
500  cfturnstile_atomic_patch_fetch();
501
502  var scheduled = null;
503  function schedule() {
504    if (scheduled) return;
505    scheduled = setTimeout(function() {
506      scheduled = null;
507      cfturnstile_init_elementor_atomic_forms();
508    }, 100);
509  }
510
511  if (document.readyState === 'loading') {
512    document.addEventListener('DOMContentLoaded', schedule);
513  } else {
514    schedule();
515  }
516
517  // Atomic forms are mounted by Elementor's own handler system and can appear or be
518  // re-rendered at any point (popups, editor edits, lazy loaded content), so watch the
519  // document rather than hooking into a specific lifecycle event.
520  if (window.MutationObserver) {
521    new MutationObserver(schedule).observe(document.documentElement, { childList: true, subtree: true });
522  }
523})();

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.