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 28/** 29 * A form belonging to a popup document that has not been moved into a modal yet. 30 * 31 * Elementor Pro caches a popup's markup as an HTML string in its handler's onInit(), then rebuilds 32 * the popup from that string on every open. Anything rendered into the form while it is still 33 * inline gets baked into the cache as dead markup, so these forms are left to the popup/show 34 * handler below. 35 */ 36function cfturnstile_elementor_is_unshown_popup(form) { 37 if (!form || !form.closest) return false; 38 return !!form.closest('[data-elementor-type="popup"]') && !form.closest('.elementor-popup-modal'); 39} 40 41/** 42 * True when this container is backed by a widget Turnstile still knows about. 43 * 44 * A container rebuilt from Elementor's popup cache carries markup copied from an earlier render 45 * that is no longer a live widget. turnstile.remove() only warns and returns on such a container 46 * (it does not throw, so try/catch does not detect it) while turnstile.render() appends alongside 47 * the copy - which is how a popup ends up with two widgets and two response inputs. getResponse() 48 * does throw for an unknown container, so it is the reliable probe. 49 */ 50function cfturnstile_elementor_widget_is_live(widget) { 51 if (!widget || !window.turnstile) return false; 52 try { 53 turnstile.getResponse(widget); 54 return true; 55 } catch (e) { 56 return false; 57 } 58} 59 60function cfturnstile_init_elementor_forms() { 61 var settings = window.cfturnstileElementorSettings || {}; 62 var sitekey = settings.sitekey || ''; 63 var position = settings.position || 'before'; 64 var mode = settings.mode || 'turnstile'; 65 var recaptchaSiteKey = settings.recaptchaSiteKey || ''; 66 var disableSubmit = settings.disableSubmit || false; 67 var widgetSize = settings.size || 'normal'; 68 69 if (!window._cft_elementor_idx) { window._cft_elementor_idx = 0; } 70 var elementorForms = document.querySelectorAll('.elementor-form:not(.cft-processed)'); 71 elementorForms.forEach(function(form) { 72 if (cfturnstile_elementor_is_unshown_popup(form)) { return; } 73 74 var index = window._cft_elementor_idx++; 75 if (form.querySelector('.cf-turnstile') || form.querySelector('.g-recaptcha') || form.querySelector('input[name="cfturnstile_failsafe"]')) { 76 form.classList.add('cft-processed'); 77 return; 78 } 79 80 var submitButton = form.querySelector('button[type="submit"]'); 81 82 // Failsafe modes: inject marker and optionally reCAPTCHA widget. 83 if (submitButton && (mode === 'allow' || mode === 'recaptcha')) { 84 var marker = document.createElement('input'); 85 marker.type = 'hidden'; 86 marker.name = 'cfturnstile_failsafe'; 87 marker.value = mode; 88 form.appendChild(marker); 89 90 if (mode === 'recaptcha' && recaptchaSiteKey) { 91 var recaptchaDiv = document.createElement('div'); 92 recaptchaDiv.className = 'g-recaptcha'; 93 recaptchaDiv.setAttribute('data-sitekey', recaptchaSiteKey); 94 recaptchaDiv.style.cssText = 'display: block; margin: 10px 0 15px 0; width: 100%;'; 95 96 if (position === 'after') { 97 submitButton.parentNode.insertBefore(recaptchaDiv, submitButton.nextSibling); 98 } else if (position === 'afterform') { 99 form.appendChild(recaptchaDiv); 100 } else { 101 submitButton.parentNode.insertBefore(recaptchaDiv, submitButton); 102 } 103 } 104 105 form.classList.add('cft-processed'); 106 return; 107 } 108 109 if (submitButton && window.turnstile && sitekey) { 110 // Disable submit button if option is enabled 111 if (disableSubmit) { 112 cfturnstile_elementor_set_submit(submitButton, false); 113 } 114 115 var labelEl = null; 116 if (settings.labelEnable && settings.labelText) { 117 labelEl = document.createElement('p'); 118 labelEl.className = 'cfturnstile-widget-label'; 119 labelEl.style.cssText = 'font-size: 14px; margin: 0 0 6px 0; width: 100%;';
120 if ((settings.appearance || 'always') === 'interaction-only') { 121 labelEl.className += ' cfturnstile-widget-label-interaction'; 122 labelEl.style.display = 'none'; 123 } 124 var smallEl = document.createElement('small'); 125 smallEl.textContent = settings.labelText; 126 labelEl.appendChild(smallEl); 127 } 128 129 var turnstileDiv = document.createElement('div'); 130 turnstileDiv.className = 'elementor-turnstile-field cf-turnstile'; 131 turnstileDiv.id = 'cf-turnstile-elementor-fallback-' + index; 132 turnstileDiv.style.cssText = 'display: block; margin: 10px 0 15px 0; width: 100%;'; 133 134 if (position === 'after') { 135 if (labelEl) submitButton.parentNode.insertBefore(labelEl, submitButton.nextSibling); 136 submitButton.parentNode.insertBefore(turnstileDiv, labelEl ? labelEl.nextSibling : submitButton.nextSibling); 137 } else if (position === 'afterform') { 138 if (labelEl) form.appendChild(labelEl); 139 form.appendChild(turnstileDiv); 140 } else { 141 if (labelEl) submitButton.parentNode.insertBefore(labelEl, submitButton); 142 submitButton.parentNode.insertBefore(turnstileDiv, submitButton); 143 } 144 145 turnstile.render('#cf-turnstile-elementor-fallback-' + index, { 146 sitekey: sitekey, 147 theme: settings.theme || 'auto', 148 size: widgetSize, 149 appearance: settings.appearance || 'always', 150 action: 'elementor-form', 151 callback: function(token) { 152 // Re-enable submit button when Turnstile is complete 153 if (disableSubmit && submitButton) { 154 cfturnstile_elementor_set_submit(submitButton, true); 155 } 156 if (typeof turnstileElementorCallback === 'function') { 157 turnstileElementorCallback(token); 158 } 159 }, 160 'error-callback': function() { 161 if (disableSubmit && submitButton) { cfturnstile_elementor_set_submit(submitButton, false); } 162 }, 163 'expired-callback': function() { 164 if (disableSubmit && submitButton) { cfturnstile_elementor_set_submit(submitButton, false); } 165 } 166 }); 167 168 if (labelEl && labelEl.classList.contains('cfturnstile-widget-label-interaction') && typeof window.cfturnstileInitInteractionLabels === 'function') { 169 window.cfturnstileInitInteractionLabels(); 170 } 171 172 form.classList.add('cft-processed'); 173 } 174 }); 175} 176 177// "Delay JavaScript" in performance plugins can run this file after DOMContentLoaded and 178// elementor/frontend/init have already fired (LiteSpeed Cache), or before jQuery has loaded 179// (Perfmatters, which this file is excluded from), so nothing here can rely on either. 180if (document.readyState === 'loading') { 181 document.addEventListener('DOMContentLoaded', function() { 182 cfturnstile_init_elementor_forms(); 183 }); 184} else { 185 cfturnstile_init_elementor_forms(); 186} 187 188// Elementor frontend init, to handle cached elements 189function cfturnstile_elementor_frontend_init() { 190 cfturnstile_init_elementor_forms(); 191 192 // Elementor runs on jQuery, so it has loaded by now 193 cfturnstile_elementor_jquery_events(); 194 195 // Hook into Elementor's widget ready system for forms loaded dynamically (e.g. in popups) 196 if (window.elementorFrontend && elementorFrontend.hooks) { 197 elementorFrontend.hooks.addAction('frontend/element_ready/form.default', function($scope) { 198 cfturnstile_init_elementor_forms(); 199 }); 200 elementorFrontend.hooks.addAction('frontend/element_ready/login.default', function($scope) { 201 cfturnstile_init_elementor_forms(); 202 }); 203 } 204} 205 206// Elementor sets its hooks just before it fires elementor/frontend/init, so if they exist it has already run. 207// Current Elementor fires the event natively as well, for when jQuery has not loaded yet. 208cfturnstile_elementor_jquery_events(); 209if (window.elementorFrontend && elementorFrontend.hooks) { 210 cfturnstile_elementor_frontend_init(); 211} else if (window.jQuery) { 212 jQuery(window).on('elementor/frontend/init', cfturnstile_elementor_frontend_init); 213} else { 214 window.addEventListener('elementor/frontend/init', cfturnstile_elementor_frontend_init); 215} 216 217// Elementor Pro fires the form and popup events through jQuery 218function cfturnstile_elementor_jquery_events() { 219 if (window._cft_elementor_jquery_events || !window.jQuery) return; 220 window._cft_elementor_jquery_events = true; 221 jQuery(document).on('submit_error submit_success', '.elementor-form', cfturnstile_elementor_submit_result); 222 jQuery(document).on('elementor/popup/show', cfturnstile_elementor_popup_show); 223 jQuery(document).on('elementor/popup/hide', cfturnstile_elementor_popup_hide); 224} 225 226// Re-render Turnstile only after Elementor reports a submit error (e.g. failed validation) 227function cfturnstile_elementor_rerender(form) { 228 if (!form || !window.turnstile) return; 229 var settings = window.cfturnstileElementorSettings || {}; 230 var widget = form.querySelector('.cf-turnstile'); 231 if (!widget) return; 232 var submitButton = form.querySelector('button[type="submit"]'); 233 var disableSubmit = settings.disableSubmit || false; 234 if (disableSubmit && submitButton) { cfturnstile_elementor_set_submit(submitButton, false); } 235 try { turnstile.remove(widget); } catch (e) {} 236 turnstile.render(widget, { 237 sitekey: settings.sitekey, 238 theme: settings.theme || 'auto', 239 size: settings.size || 'normal', 240 appearance: settings.appearance || 'always', 241 action: 'elementor-form', 242 callback: function(token) { 243 if (disableSubmit && submitButton) { cfturnstile_elementor_set_submit(submitButton, true); } 244 if (typeof turnstileElementorCallback === 'function') { 245 turnstileElementorCallback(token); 246 } 247 }, 248 'error-callback': function() { 249 if (disableSubmit && submitButton) { cfturnstile_elementor_set_submit(submitButton, false); } 250 }, 251 'expired-callback': function() { 252 if (disableSubmit && submitButton) { cfturnstile_elementor_set_submit(submitButton, false); } 253 } 254 }); 255} 256 257function cfturnstile_elementor_submit_result() { 258 var settings = window.cfturnstileElementorSettings || {}; 259 if ((settings.mode || 'turnstile') !== 'turnstile') return; 260 cfturnstile_elementor_rerender(this); 261} 262 263// Block submission until Turnstile is completed (client-side guard). 264// Server-side validation in cfturnstile_elementor_check is the source of truth; 265// this just avoids a wasted round-trip and gives immediate feedback. 266document.addEventListener('submit', function(event) { 267 var form = event.target; 268 if (!form.classList) return; 269 var isClassic = form.classList.contains('elementor-form');
270 var isAtomic = !isClassic && form.matches && form.matches(CFT_ATOMIC_FORM_SELECTOR); 271 if (!isClassic && !isAtomic) return; 272 var settings = window.cfturnstileElementorSettings || {}; 273 if ((settings.mode || 'turnstile') !== 'turnstile' || !window.turnstile) return; 274 275 var widget = form.querySelector('.cf-turnstile'); 276 if (!widget) return; 277 if (!cfturnstile_elementor_token(form)) { 278 event.preventDefault(); 279 event.stopImmediatePropagation(); 280 try { widget.scrollIntoView({ behavior: 'smooth', block: 'center' }); } catch (e) {} 281 } 282}, true); 283 284// Handle Elementor popup show events 285function cfturnstile_elementor_popup_show(event, id, instance) { 286 setTimeout(function() { 287 // First, inject Turnstile into any unprocessed forms inside the popup 288 cfturnstile_init_elementor_forms(); 289 290 var settings = window.cfturnstileElementorSettings || {}; 291 var mode = settings.mode || 'turnstile'; 292 var disableSubmit = settings.disableSubmit || false; 293 if (mode !== 'turnstile' || !window.turnstile) { 294 return; 295 } 296 297 // Re-render all Turnstile widgets inside popup modals (they need explicit render after DOM insertion) 298 var popupTurnstiles = document.querySelectorAll('.elementor-popup-modal .cf-turnstile'); 299 popupTurnstiles.forEach(function(widget) { 300 var failedText = widget.parentNode ? widget.parentNode.querySelector('.cf-turnstile-failed-text') : null; 301 if (failedText) { 302 failedText.style.display = 'none'; 303 } 304 305 // Find the form and submit button for this widget 306 var form = widget.closest('.elementor-form'); 307 var submitButton = form ? form.querySelector('button[type="submit"]') : null; 308 309 // A widget Turnstile still owns is already rendered, and may hold a solved token that 310 // re-rendering would throw away. 311 if (cfturnstile_elementor_widget_is_live(widget)) { 312 if (disableSubmit && submitButton) { 313 cfturnstile_elementor_set_submit(submitButton, !!cfturnstile_elementor_token(form)); 314 } 315 return; 316 } 317 318 // Disable submit button if option is enabled 319 if (disableSubmit && submitButton) { cfturnstile_elementor_set_submit(submitButton, false); } 320 321 // Orphan markup rebuilt from Elementor's popup cache: clear it, or render() appends a second 322 // widget and the form posts two cf-turnstile-response inputs - the first one already spent. 323 try { turnstile.remove(widget); } catch (e) {} 324 widget.innerHTML = ''; 325 326 turnstile.render(widget, { 327 sitekey: cfturnstileElementorSettings.sitekey, 328 appearance: cfturnstileElementorSettings.appearance || 'always', 329 action: 'elementor-form', 330 callback: function(token) { 331 // Re-enable submit button when Turnstile is complete 332 if (disableSubmit && submitButton) { cfturnstile_elementor_set_submit(submitButton, true); } 333 if (typeof turnstileElementorCallback === 'function') { 334 turnstileElementorCallback(token); 335 } 336 }, 337 'error-callback': function() { 338 if (disableSubmit && submitButton) { cfturnstile_elementor_set_submit(submitButton, false); } 339 }, 340 'expired-callback': function() { 341 if (disableSubmit && submitButton) { cfturnstile_elementor_set_submit(submitButton, false); } 342 }, 343 theme: cfturnstileElementorSettings.theme || 'auto', 344 size: cfturnstileElementorSettings.size || 'normal' 345 }); 346 }); 347 }, 500); 348} 349 350// Release the widgets of a popup that is closing. Elementor throws the popup DOM away and rebuilds 351// it from its cached string on the next open, so without this the widgets stay registered against 352// detached nodes for the rest of the page life. Deferred so the widget does not blink out of the 353// popup mid exit-animation - removing a detached container works just as well. 354function cfturnstile_elementor_popup_hide(event, id) { 355 var modal = document.getElementById('elementor-popup-modal-' + id); 356 if (!modal || !window.turnstile) return; 357 var widgets = Array.prototype.slice.call(modal.querySelectorAll('.cf-turnstile')); 358 if (!widgets.length) return; 359 setTimeout(function() {
360 widgets.forEach(function(widget) { 361 try { turnstile.remove(widget); } catch (e) {} 362 }); 363 }, 1000); 364} 365 366/* --------------------------------------------------------------------------- 367 * Elementor Atomic Forms (Elementor 4 "e-form" elements) 368 * 369 * Atomic forms are submitted by an Alpine handler bound to the form, which posts 370 * a FormData payload built only from the field widgets Elementor itself rendered 371 * (input/textarea/select carrying data-interaction-id). A hidden token input 372 * sitting inside the form is therefore never sent, so the token is appended to 373 * the outgoing request instead - see cfturnstile_atomic_patch_fetch() below. 374 * ------------------------------------------------------------------------ */ 375 376function cfturnstile_atomic_submit_button(form) { 377 return form ? form.querySelector('button[type="submit"], input[type="submit"]') : null; 378} 379 380/** 381 * Horizontal alignment of the widget inside its (column flex) wrapper. 382 */ 383function cfturnstile_atomic_align(align) { 384 if (align === 'center') return 'center'; 385 if (align === 'right') return 'flex-end'; 386 return 'flex-start'; 387} 388 389/** 390 * Place a node according to the configured widget position. Atomic forms are flex 391 * containers, so the node is inserted as a full width row of its own. 392 */ 393function cfturnstile_atomic_insert(form, node, position, submitButton) { 394 if (position === 'afterform' || !submitButton || !submitButton.parentNode) { 395 form.appendChild(node); 396 } else if (position === 'after') { 397 submitButton.parentNode.insertBefore(node, submitButton.nextSibling); 398 } else { 399 submitButton.parentNode.insertBefore(node, submitButton); 400 } 401} 402 403function cfturnstile_atomic_render_options(form) { 404 var settings = window.cfturnstileElementorSettings || {}; 405 var disableSubmit = settings.disableSubmit || false; 406 var submitButton = cfturnstile_atomic_submit_button(form); 407 return { 408 sitekey: settings.sitekey, 409 theme: settings.theme || 'auto', 410 size: settings.size || 'normal', 411 appearance: settings.appearance || 'always', 412 action: 'elementor-atomic-form', 413 callback: function(token) { 414 if (disableSubmit) { cfturnstile_elementor_set_submit(submitButton, true); } 415 if (typeof turnstileElementorCallback === 'function') { 416 turnstileElementorCallback(token); 417 } 418 }, 419 'error-callback': function() { 420 if (disableSubmit) { cfturnstile_elementor_set_submit(submitButton, false); } 421 }, 422 'expired-callback': function() { 423 if (disableSubmit) { cfturnstile_elementor_set_submit(submitButton, false); } 424 } 425 }; 426} 427 428function cfturnstile_init_elementor_atomic_forms() { 429 var settings = window.cfturnstileElementorSettings || {}; 430 var sitekey = settings.sitekey || ''; 431 var position = settings.position || 'before'; 432 var mode = settings.mode || 'turnstile'; 433 var recaptchaSiteKey = settings.recaptchaSiteKey || ''; 434 var disableSubmit = settings.disableSubmit || false; 435 436 if (!window._cft_atomic_idx) { window._cft_atomic_idx = 0; } 437 438 // Elementor re-renders form elements in place (editor edits, popups, Alpine refreshes), 439 // which drops the injected widget. Release those forms so they are processed again. 440 document.querySelectorAll(CFT_ATOMIC_FORM_SELECTOR + '.cft-processed').forEach(function(form) { 441 if (!form.querySelector('.cf-turnstile, .g-recaptcha, input[name="cfturnstile_failsafe"]')) { 442 form.classList.remove('cft-processed'); 443 } 444 }); 445 446 document.querySelectorAll(CFT_ATOMIC_FORM_SELECTOR + ':not(.cft-processed)').forEach(function(form) { 447 // Same popup caching trap as the classic forms above - wait until the popup is actually shown, 448 // at which point the observer below picks the form up with its atomic render options intact. 449 if (cfturnstile_elementor_is_unshown_popup(form)) { return; } 450 451 if (form.querySelector('.cf-turnstile, .g-recaptcha, input[name="cfturnstile_failsafe"]')) { 452 form.classList.add('cft-processed'); 453 return; 454 } 455 456 var submitButton = cfturnstile_atomic_submit_button(form); 457
458 // Failsafe modes: post a marker, and render reCAPTCHA in its place if configured. 459 if (mode === 'allow' || mode === 'recaptcha') { 460 var marker = document.createElement('input'); 461 marker.type = 'hidden'; 462 marker.name = 'cfturnstile_failsafe'; 463 marker.value = mode; 464 form.appendChild(marker); 465 466 if (mode === 'recaptcha' && recaptchaSiteKey) { 467 var recaptchaWrap = document.createElement('div'); 468 recaptchaWrap.className = 'cfturnstile-atomic-field'; 469 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;'; 470 var recaptchaDiv = document.createElement('div'); 471 recaptchaDiv.className = 'g-recaptcha'; 472 recaptchaDiv.setAttribute('data-sitekey', recaptchaSiteKey); 473 recaptchaWrap.appendChild(recaptchaDiv); 474 cfturnstile_atomic_insert(form, recaptchaWrap, position, submitButton); 475 } 476 477 form.classList.add('cft-processed'); 478 return; 479 } 480 481 // The submit button is rendered as a child widget, so it can still be missing on an 482 // early pass. Wait for the next pass rather than injecting into a half built form. 483 if (!submitButton || !window.turnstile || !sitekey) return; 484 485 if (disableSubmit) { 486 cfturnstile_elementor_set_submit(submitButton, false); 487 } 488 489 var index = window._cft_atomic_idx++; 490 491 var wrap = document.createElement('div'); 492 wrap.className = 'cfturnstile-atomic-field'; 493 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;'; 494 495 if (settings.labelEnable && settings.labelText) { 496 var labelEl = document.createElement('p'); 497 labelEl.className = 'cfturnstile-widget-label'; 498 labelEl.style.cssText = 'font-size: 14px; margin: 0 0 6px 0;'; 499 if ((settings.appearance || 'always') === 'interaction-only') { 500 labelEl.className += ' cfturnstile-widget-label-interaction'; 501 labelEl.style.display = 'none'; 502 } 503 var smallEl = document.createElement('small'); 504 smallEl.textContent = settings.labelText; 505 labelEl.appendChild(smallEl); 506 wrap.appendChild(labelEl); 507 } 508 509 var turnstileDiv = document.createElement('div'); 510 turnstileDiv.className = 'elementor-turnstile-field cf-turnstile'; 511 turnstileDiv.id = 'cf-turnstile-elementor-atomic-' + index; 512 wrap.appendChild(turnstileDiv); 513 514 cfturnstile_atomic_insert(form, wrap, position, submitButton); 515 516 turnstile.render(turnstileDiv, cfturnstile_atomic_render_options(form)); 517 518 if (settings.labelEnable && (settings.appearance || 'always') === 'interaction-only' && typeof window.cfturnstileInitInteractionLabels === 'function') { 519 window.cfturnstileInitInteractionLabels(); 520 } 521 522 form.classList.add('cft-processed'); 523 }); 524} 525 526/** 527 * Reset a widget after its form has been submitted, so a retry gets a fresh token. 528 * Elementor calls form.reset() on success, which empties the token input while the 529 * widget still holds the spent token - the global refresh helper cannot detect that. 530 */ 531function cfturnstile_atomic_reset(form) { 532 if (!form || !window.turnstile) return; 533 var widget = form.querySelector('.cf-turnstile'); 534 if (!widget) return; 535 var settings = window.cfturnstileElementorSettings || {}; 536 if (settings.disableSubmit) { 537 cfturnstile_elementor_set_submit(cfturnstile_atomic_submit_button(form), false); 538 } 539 try { turnstile.reset(widget); } catch (e) {} 540} 541 542/** 543 * Attach the token to the atomic form request. 544 * 545 * Elementor builds the FormData itself and only includes its own field widgets, so 546 * there is no markup based way to get the token to the server. The wrapper only 547 * touches requests carrying the atomic form action and leaves everything else alone. 548 */ 549function cfturnstile_atomic_patch_fetch() { 550 if (window._cfturnstileAtomicFetchPatched) return; 551 var originalFetch = window.fetch; 552 if (typeof originalFetch !== 'function') return; 553 window._cfturnstileAtomicFetchPatched = true; 554 555 window.fetch = function(input, init) { 556 var form = null; 557 try { 558 var body = init && init.body; 559 if (body instanceof FormData && body.get('action') === CFT_ATOMIC_AJAX_ACTION) { 560 var formId = body.get('form_id'); 561 if (formId && /^[A-Za-z0-9_-]+$/.test(formId)) {
562 form = document.querySelector(CFT_ATOMIC_FORM_SELECTOR + '[data-id="' + formId + '"]'); 563 } 564 if (form) { 565 var token = cfturnstile_elementor_token(form); 566 if (token) { 567 body.set('cf-turnstile-response', token); 568 } 569 var failsafe = form.querySelector('input[name="cfturnstile_failsafe"]'); 570 if (failsafe && failsafe.value) { 571 body.set('cfturnstile_failsafe', failsafe.value); 572 } 573 var recaptcha = form.querySelector('textarea[name="g-recaptcha-response"], input[name="g-recaptcha-response"]'); 574 if (recaptcha && recaptcha.value) { 575 body.set('g-recaptcha-response', recaptcha.value); 576 } 577 } 578 } 579 } catch (e) {} 580 581 // Callers reaching fetch from strict mode code (bundles, ES modules) invoke it bare, so 582 // `this` arrives undefined. Current Chrome tolerates that, but engines that hold fetch to 583 // its WebIDL receiver throw "Illegal invocation" - fall back to the global rather than 584 // making that a property of whether this plugin happens to be active. 585 var request = originalFetch.apply(this || window, arguments); 586 587 if (form) { 588 var reset = function() { 589 // Let Elementor apply its own result handling (including form.reset()) first. 590 setTimeout(function() { cfturnstile_atomic_reset(form); }, 500); 591 }; 592 request.then(reset, reset); 593 } 594 595 return request; 596 }; 597} 598 599(function() { 600 cfturnstile_atomic_patch_fetch(); 601 602 var scheduled = null; 603 function schedule() { 604 if (scheduled) return; 605 scheduled = setTimeout(function() { 606 scheduled = null; 607 cfturnstile_init_elementor_atomic_forms(); 608 }, 100); 609 } 610 611 if (document.readyState === 'loading') { 612 document.addEventListener('DOMContentLoaded', schedule); 613 } else { 614 schedule(); 615 } 616 617 // Atomic forms are mounted by Elementor's own handler system and can appear or be 618 // re-rendered at any point (popups, editor edits, lazy loaded content), so watch the 619 // document rather than hooking into a specific lifecycle event. 620 if (window.MutationObserver) { 621 new MutationObserver(schedule).observe(document.documentElement, { childList: true, subtree: true }); 622 } 623})();
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.