1(function () { 2 'use strict'; 3 4 if (!window.LCF7FallbackCapture || !window.fetch) { 5 return; 6 } 7 8 var config = window.LCF7FallbackCapture; 9 var pending = new WeakMap(); 10 var capturedTokens = Object.create(null); 11 var ignoredStatuses = ['init', 'validation_failed', 'acceptance_missing', 'invalid']; 12 13 function randomToken() { 14 if (window.crypto && typeof window.crypto.randomUUID === 'function') { 15 return window.crypto.randomUUID(); 16 } 17 18 if (window.crypto && typeof window.crypto.getRandomValues === 'function') { 19 var bytes = new Uint8Array(24); 20 window.crypto.getRandomValues(bytes); 21 return Array.prototype.map.call(bytes, function (byte) { 22 return byte.toString(16).padStart(2, '0'); 23 }).join(''); 24 } 25 26 return String(Date.now()) + '-' + Math.random().toString(36).slice(2) + Math.random().toString(36).slice(2); 27 } 28 29 function getFormFromEvent(event) { 30 if (event && event.target) { 31 if (event.target.matches && event.target.matches('form.wpcf7-form')) { 32 return event.target; 33 } 34 35 if (event.target.querySelector) { 36 return event.target.querySelector('form.wpcf7-form'); 37 } 38 } 39 40 return null; 41 } 42 43 function getSubmitButtonForm(target) { 44 if (!target || !target.closest) { 45 return null; 46 } 47 48 var button = target.closest('input[type="submit"], button[type="submit"], button:not([type]), .wpcf7-submit'); 49 50 if (!button) { 51 return null; 52 } 53 54 var form = button.form || button.closest('form.wpcf7-form'); 55 return form && form.matches && form.matches('form.wpcf7-form') ? form : null; 56 } 57 58 function ensureTokenInput(form, forceNew) { 59 var input = form.querySelector('input[name="_lcf7_capture_token"]'); 60 61 if (!input) { 62 input = document.createElement('input'); 63 input.type = 'hidden'; 64 input.name = '_lcf7_capture_token'; 65 form.appendChild(input); 66 } 67 68 if (forceNew || !input.value) { 69 input.value = randomToken(); 70 } 71 72 return input.value; 73 } 74 75 function formSnapshot(form, forceNewToken) { 76 var token = ensureTokenInput(form, !!forceNewToken); 77 var data = new FormData(form); 78 var fields = []; 79 var formId = parseInt(data.get('_wpcf7'), 10) || 0; 80 81 data.forEach(function (value, name) { 82 if (value instanceof File) { 83 if (value.name) { 84 fields.push({ 85 name: name + '_file_name', 86 value: String(value.name) 87 }); 88 } 89 return; 90 } 91 92 if (name.indexOf('_wpcf7') === 0 || name === '_lcf7_capture_token') { 93 return; 94 } 95 96 fields.push({ 97 name: name, 98 value: String(value) 99 }); 100 }); 101 102 return { 103 form: form, 104 formId: formId, 105 token: token, 106 fields: fields, 107 pageUrl: window.location.href, 108 timer: null, 109 sending: false, 110 sent: !!capturedTokens[token], 111 promise: null 112 }; 113 } 114 115 function clearPending(form) { 116 var item = pending.get(form); 117 118 if (item && item.timer) { 119 window.clearTimeout(item.timer); 120 } 121 122 pending.delete(form); 123 } 124 125 function clearCaptureToken(form) { 126 var input = form.querySelector('input[name="_lcf7_capture_token"]'); 127 128 if (input) { 129 input.value = ''; 130 } 131 132 clearPending(form); 133 } 134 135 function showSavedSuccess(form) { 136 var wrapper = form.closest('.wpcf7'); 137 var output = wrapper ? wrapper.querySelector('.wpcf7-response-output') : null; 138 139 form.classList.remove('invalid', 'failed', 'spam', 'aborted'); 140 form.classList.add('sent'); 141 form.setAttribute('data-status', 'sent'); 142 143 if (wrapper) { 144 wrapper.classList.remove('invalid', 'failed', 'spam', 'aborted'); 145 wrapper.classList.add('sent'); 146 } 147 148 if (output) { 149 output.textContent = config.successMessage || 'Thank you. Your submission has been received.'; 150 output.setAttribute('aria-hidden', 'false'); 151 } 152 } 153 154 function shouldReplaceError(status) { 155 return ['mail_failed', 'mail_error', 'error', 'aborted', 'transport_error', 'failed'].indexOf(status) !== -1; 156 } 157 158 function isCaptchaField(control) { 159 var name = control && control.name ? String(control.name) : ''; 160 161 return name === 'cf-turnstile-response' || 162 name === '_wpcf7_turnstile_response' || 163 name === 'g-recaptcha-response' || 164 name.indexOf('turnstile') !== -1 || 165 name.indexOf('recaptcha') !== -1; 166 } 167 168 function hasTurnstile(form) { 169 return !!form.querySelector( 170 'input[name="cf-turnstile-response"], input[name="_wpcf7_turnstile_response"], .cf-turnstile, .wpcf7-turnstile, [class*="turnstile"], iframe[src*="challenges.cloudflare.com"]' 171 ); 172 } 173 174 function isTurnstilePending(form) { 175 if (!hasTurnstile(form)) { 176 return false;
177 } 178 179 var response = form.querySelector('input[name="_wpcf7_turnstile_response"], input[name="cf-turnstile-response"]'); 180 return !response || !String(response.value || '').trim(); 181 } 182 183 // Validate normal user fields but deliberately exclude Turnstile/reCAPTCHA. 184 // This prevents blank or obviously invalid leads from being emailed while still 185 // allowing a complete lead to be captured before a delayed CAPTCHA is ready. 186 function normalFieldsAreValid(form) { 187 var controls = Array.prototype.slice.call(form.querySelectorAll('input, select, textarea')); 188 var checkedGroups = Object.create(null); 189 var valid = true; 190 191 controls.forEach(function (control) { 192 if (!valid || control.disabled || isCaptchaField(control)) { 193 return; 194 } 195 196 var type = String(control.type || '').toLowerCase(); 197 198 if (['hidden', 'submit', 'button', 'reset', 'image'].indexOf(type) !== -1) { 199 return; 200 } 201 202 var acceptance = control.closest ? control.closest('.wpcf7-acceptance') : null; 203 var acceptanceOptional = acceptance && acceptance.classList && acceptance.classList.contains('optional'); 204 var required = !!control.required || 205 control.getAttribute('aria-required') === 'true' || 206 (control.classList && control.classList.contains('wpcf7-validates-as-required')) || 207 (!!acceptance && !acceptanceOptional); 208 209 if ((type === 'checkbox' || type === 'radio') && required) { 210 var groupName = control.name || ('__single_' + controls.indexOf(control)); 211 212 if (checkedGroups[groupName] === undefined) { 213 var group = control.name 214 ? controls.filter(function (item) { 215 return item.name === control.name; 216 }) 217 : [control]; 218 var anyChecked = group.some(function (item) { 219 return !item.disabled && item.checked; 220 }); 221 checkedGroups[groupName] = anyChecked; 222 } 223 224 if (!checkedGroups[groupName]) { 225 valid = false; 226 } 227 return; 228 } 229 230 if (required) { 231 if (type === 'file') { 232 if (!control.files || !control.files.length) { 233 valid = false; 234 return; 235 } 236 } else if (!String(control.value || '').trim()) { 237 valid = false; 238 return; 239 } 240 } 241 242 if (String(control.value || '').trim() && typeof control.checkValidity === 'function' && !control.checkValidity()) { 243 valid = false; 244 } 245 }); 246 247 return valid; 248 } 249 250 function sendFallback(item, status, replaceError) { 251 if (!item || !item.formId || !item.token) { 252 return Promise.resolve(false); 253 } 254 255 if (item.sent || capturedTokens[item.token]) { 256 return Promise.resolve(true); 257 } 258 259 if (item.sending && item.promise) { 260 return item.promise; 261 } 262 263 item.sending = true; 264 265 item.promise = fetch(config.endpoint, { 266 method: 'POST', 267 credentials: 'same-origin', 268 keepalive: true, 269 headers: { 270 'Content-Type': 'application/json' 271 }, 272 body: JSON.stringify({ 273 nonce: config.nonce, 274 form_id: item.formId, 275 token: item.token, 276 status: status || 'error', 277 page_url: item.pageUrl, 278 fields: item.fields 279 }) 280 }).then(function (response) { 281 return response.json().then(function (body) { 282 return { 283 ok: response.ok, 284 body: body 285 }; 286 }); 287 }).then(function (result) { 288 item.sending = false;
289 item.promise = null; 290 291 if (!result.ok || !result.body || !result.body.saved) { 292 return false; 293 } 294 295 item.sent = true; 296 capturedTokens[item.token] = true; 297 298 if (replaceError) { 299 showSavedSuccess(item.form); 300 } 301 302 clearPending(item.form); 303 return true; 304 }).catch(function () { 305 item.sending = false; 306 item.promise = null; 307 return false; 308 }); 309 310 return item.promise; 311 } 312 313 function getOrCreateItem(form) { 314 var token = ensureTokenInput(form, false); 315 var item = pending.get(form); 316 317 if (!item || item.token !== token) { 318 item = formSnapshot(form, false); 319 pending.set(form, item); 320 } 321 322 return item; 323 } 324 325 // Capture immediately when the visitor presses Submit while Turnstile has not 326 // generated its token yet. This runs before CF7/Turnstile can reject or postpone 327 // the request and does not prevent their normal validation from continuing. 328 function captureBeforeDelayedTurnstile(form) { 329 if (!form || !isTurnstilePending(form) || !normalFieldsAreValid(form)) { 330 return; 331 } 332 333 var item = getOrCreateItem(form); 334 sendFallback(item, 'turnstile_pending', false); 335 } 336 337 function preSubmitPointerHandler(event) { 338 var form = getSubmitButtonForm(event.target); 339 340 if (form) { 341 captureBeforeDelayedTurnstile(form); 342 } 343 } 344 345 // Capture-phase listeners run before Contact Form 7's button/submit handlers. 346 document.addEventListener('pointerdown', preSubmitPointerHandler, true); 347 document.addEventListener('click', preSubmitPointerHandler, true); 348 349 // Keyboard Enter and normal form submission are covered here. The same token is 350 // reused, so pointer/click/submit and PHP hooks cannot create duplicate records. 351 document.addEventListener('submit', function (event) { 352 var form = event.target; 353 354 if (!form || !form.matches || !form.matches('form.wpcf7-form')) { 355 return; 356 } 357 358 var item = getOrCreateItem(form); 359 360 if (isTurnstilePending(form) && normalFieldsAreValid(form)) { 361 sendFallback(item, 'turnstile_pending', false); 362 } 363 364 if (item.timer) { 365 window.clearTimeout(item.timer); 366 } 367 368 item.timer = window.setTimeout(function () { 369 sendFallback(item, 'transport_error', true); 370 }, Math.max(4000, parseInt(config.timeout, 10) || 9000)); 371 }, true); 372 373 document.addEventListener('wpcf7invalid', function (event) { 374 var form = getFormFromEvent(event); 375 if (form) { 376 clearPending(form); 377 } 378 }, false); 379 380 document.addEventListener('wpcf7submit', function (event) { 381 var form = getFormFromEvent(event); 382 383 if (!form) { 384 return; 385 } 386 387 var status = event.detail && event.detail.apiResponse && event.detail.apiResponse.lcf7_original_status 388 ? String(event.detail.apiResponse.lcf7_original_status) 389 : (event.detail && event.detail.status ? String(event.detail.status) : 'error'); 390 391 if (ignoredStatuses.indexOf(status) !== -1) { 392 clearPending(form); 393 return; 394 } 395 396 var item = getOrCreateItem(form); 397 398 if (item.timer) { 399 window.clearTimeout(item.timer); 400 item.timer = null; 401 } 402 403 // No extra fallback POST after a normal CF7 response. PHP has already 404 // captured and dispatched the lead in the same request. Browser fallback is 405 // used only for Turnstile-pending or transport-timeout cases. 406 if (shouldReplaceError(status)) { 407 showSavedSuccess(form); 408 } 409 clearPending(form); 410 411 if (status === 'mail_sent') { 412 window.setTimeout(function () { 413 clearCaptureToken(form); 414 }, 0); 415 } 416 }, false); 417 418 document.addEventListener('wpcf7reset', function (event) { 419 var form = getFormFromEvent(event); 420 if (form) { 421 clearCaptureToken(form); 422 } 423 }, false); 424}());
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.