1// Build: 2026.08.29.33 2// Magic FOrm Handler 3// Intellam 4// Version: 2026.08.28.01 5window.addEventListener('unhandledrejection', event => { 6 console.error('Unhandled promise rejection:', event); 7}); 8(function() { // Wrap everything in an IIFE to avoid polluting global scope 9 10 const jqueryCdnUrl = 'https://code.jquery.com/jquery-3.7.1.min.js'; // Specify jQuery version/CDN 11 const jqueryChecksum = 'sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo='; 12 const recaptchaSiteKey = '6LetuZ4qAAAAAM9cFxB4vMcCK6fegkxDINYRjbNi'; // Recaptcha v3 site key 13 /* // To use this, recaptchaSiteKey above must be a var not const 14 var recaptchaSiteKeyLocal = document.getElementById('recaptchaKeyLocal'); 15 if (recaptchaSiteKeyLocal) 16 { 17 recaptchaSiteKey = recaptchaSiteKeyLocal.value; 18 }; 19 */ 20 const recaptchaApiUrl = `https://www.google.com/recaptcha/api.js?render=${recaptchaSiteKey}`; 21 22 const getCookieValue = (name) => ( 23 document.cookie.match('(^|;)\\s*' + name + '\\s*=\\s*([^;]+)')?.pop() || '' 24 ) 25 26 // Function containing all the jQuery-dependent form logic 27 function initFormHandler() { 28 // Local alias. jQuery is confirmed present by the time this runs, but $ may not be: 29 // WordPress releases it. Aliasing here keeps the eight $ uses below working without 30 // depending on a global that a page is entitled not to have. 31 const $ = window.jQuery; 32 33 $(document).ready(function() { 34 var signedup=(getCookieValue("newsletter-close") || getCookieValue("newsletter-signup")); 35 if (signedup) 36 { 37 $('.nl-popup,.subscribe-popup,.newsletter-popup').remove(); 38 }; 39 // Find all forms with the class 'magic-form' 40 $('form.magic-form').each(function() { 41 const $form = $(this); 42 const $errorDiv = $form.find('.form-error').first(); 43 const $successDiv = $form.find('.form-success').first(); 44 const successMessage = $form.data('success') || 'Thanks for signing up!'; 45 const successRedirect = $form.data('success-redirect'); 46 const disableRecaptcha = $form.data('disable-recap') || false; 47 const $formElements = $form.find('input, textarea, select, button'); 48 const $emailField = $form.find('input[name="emailaddress"]'); 49 const $recaptchaInput = $form.find('input[name="g-recaptcha-response"]'); 50 // Opt in with data-luma-register on the form. The event id itself is never sent - 51 // the server reads luma_calendar_id from this page's ACF modules. Optional 52 // data-luma-module holds the module's row index (ACF get_row_index()) for pages 53 // with more than one configured download-handler module. 54 const lumaRegister = $form.data('luma-register') || false; 55 const lumaModule = parseInt($form.data('luma-module'), 10) || 0; 56 57 // Serialize the form, appending the Luma opt-in fields when this form uses them. 58 const buildFormData = function() { 59 let data = $form.serialize(); 60 if (lumaRegister && typeof magicFormAjax !== 'undefined' && magicFormAjax.post_id) { 61 data += '&luma_register=1&luma_post_id=' + encodeURIComponent(magicFormAjax.post_id); 62 if (lumaModule > 0) { 63 data += '&luma_module=' + encodeURIComponent(lumaModule); 64 } 65 } 66 return data; 67 }; 68 69 let $checkbox = $form.find('input[name="consent"]'); 70 let $label = $checkbox.next('label'); 71 if ($label.length === 0) { 72 $label = $checkbox.closest('label'); 73 } 74 if (!disableRecaptcha && $label.length > 0) { 75 $label.append(' <span style="font:inherit;color:inherit;">Additionally, this site is protected by reCAPTCHA and the Google <a style="color:inherit;font:inherit" href="https://policies.google.com/privacy" target="_blank">Privacy Policy</a> and <a style="color:inherit; font:inherit;" href="https://policies.google.com/terms" target="_blank">Terms of Service</a> apply.</span>'); 76 $('<style>.grecaptcha-badge {visibility: hidden !important}</style>').appendTo('head'); 77 } 78 $form.on('submit', function(event) { 79 event.preventDefault(); 80 81 $errorDiv.hide().text(''); 82 $successDiv.hide().text(''); 83 $form.find('.error').removeClass('error'); 84 85 let isValid = true; 86 let errorMessage = ''; 87 88 // --- Validation --- 89 // 1. Required fields 90 $form.find('[required]').each(function() { /* ... validation logic ... */ 91 const $field = $(this); let value = $field.val(); 92 if ($field.is(':checkbox')) { /* Skip */ } 93 else if (typeof value === 'string') { value = value.trim(); if (value === '') { isValid = false; errorMessage = 'Please fill out all required fields.'; $field.addClass('error'); return false; } } 94 else if (value === null || value === '') { isValid = false; errorMessage = 'Please fill out all required fields.'; $field.addClass('error'); return false; } 95 }); 96 if (!isValid) { $errorDiv.text(errorMessage).show(); return; } 97 98 // 2. Consent checkbox 99 const $consentCheckbox = $form.find('input[name="consent"][type="checkbox"]'); 100 if ($consentCheckbox.length > 0 && !$consentCheckbox.is(':checked')) { 101 isValid = false; errorMessage = 'Please consent to proceed.'; $consentCheckbox.addClass('error'); $consentCheckbox.closest('.form-group').find('label[for="consent"]').addClass('error'); $errorDiv.text(errorMessage).show(); return; 102 } 103 104 // 3. Email format 105 if ($emailField.length > 0) { 106 const emailValue = $emailField.val().trim(); 107 const emailRegex = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/; 108 if (emailValue === '' && $emailField.is('[required]')) { isValid = false; errorMessage = 'Email address is required.'; $emailField.addClass('error'); } 109 else if (emailValue !== '' && !emailRegex.test(emailValue)) { isValid = false; errorMessage = 'Please enter a valid email address format.'; $emailField.addClass('error'); } 110 } 111 112 if (!isValid) { $errorDiv.text(errorMessage).show(); return; } 113 114 // --- Execute reCAPTCHA v3 --- 115 if (!disableRecaptcha) { 116 if (typeof grecaptcha === 'undefined' || typeof grecaptcha.execute === 'undefined') { 117 console.error("reCAPTCHA V3 library n
117ot loaded correctly or not ready."); 118 $errorDiv.text("Verification check failed. Please reload the page and try again.").show(); 119 $successDiv.hide(); 120 $formElements.prop('disabled', false); 121 return; // Stop submission 122 } 123 124 grecaptcha.ready(function() { 125 grecaptcha.execute(recaptchaSiteKey, {action: 'newsletter_signup'}) // Use a specific action name 126 .then(function(token) { 127 // *** Add token to hidden input *** 128 $recaptchaInput.val(token); 129 $successDiv.text('Submitting; please wait...').show(); 130 const formData = buildFormData(); 131 $formElements.prop('disabled', true); 132 133 // 4. Submit via AJAX 134 // Timeout covers the server's full external-call chain (reCAPTCHA, 135 // Zerobounce, two Customer.io calls, optional Luma Get Event + registration). 136 $.ajax({ url: '/wp-admin/admin-ajax.php?action=magic_form', type: 'POST', data: formData, dataType: 'json', timeout: 30000 }) 137 .done(function(response) { 138 if (response && response.magic_status === 'success') { 139 $successDiv.text(successMessage).show(); 140 $errorDiv.hide(); 141 142 // Set cookie 143 const expiresDate = new Date(); expiresDate.setFullYear(expiresDate.getFullYear() + 1); 144 document.cookie = "newsletter-close=true; expires=" + expiresDate.toUTCString() + "; path=/"; 145 146 // Facebook pixel 147 if(typeof fbq !== 'undefined') { 148 fbq('track', 'Lead'); 149 } 150 151 // Analytics Tracking 152 const analyticsEventName = $form.data('analytics'); 153 if (analyticsEventName) { 154 if (typeof analytics !== 'undefined' && typeof analytics.track === 'function') { 155 const analyticsSource = $form.data('source') || 'unknown'; 156 const $emailField = $form.find('input[name="emailaddress"]'); 157 const emailValue = $emailField.val().trim(); 158 try { analytics.track(analyticsEventName, { source: analyticsSource, email: emailValue }); } 159 catch (e) { console.error("Error calling analytics.track:", e); } 160 } else { console.warn("Analytics tracking specified, but analytics.track function not found."); } 161 } 162 $('.subscribe-popup,.nl-popup').fadeOut(1500); 163 // Redirect (keep elements disabled) 164 if (successRedirect) { setTimeout(function() { window.location.href = successRedirect; }, 1500); } 165 } else { 166 // Server-side failure - RE-ENABLE fields 167 errorMessage = (response && response.errorMessage) ? response.errorMessage : 'An unknown error occurred on the server.'; 168 $errorDiv.text(errorMessage).show(); $successDiv.hide(); $formElements.prop('disabled', false); 169 } 170 }) 171 .fail(function(jqXHR, textStatus, errorThrown) { 172 // AJAX failure - RE-ENABLE fields 173 let ajaxError = 'Could not submit the form. '; 174 if (textStatus === 'timeout') { ajaxError += 'The request timed out.'; } 175 else if (jqXHR.status === 0) { ajaxError += 'Check your network connection.'; } 176 else { ajaxError += `Server returned status ${jqXHR.status}.`; } 177 console.error("AJAX Error:", textStatus, errorThrown, jqXHR); 178 $errorDiv.text(ajaxError).show(); $successDiv.hide(); $formElements.prop('disabled', false); 179 }); 180 }).catch(function(error) { 181 console.error("reCAPTCHA execution failed:", error); 182 $errorDiv.text("Could not verify your request. Please try again.").show(); 183 $successDiv.hide(); 184 $formElements.prop('disabled', false); 185 }); 186 }); // End grecaptcha.ready 187 } 188 else 189 { 190 $successDiv.text('Submitting; please wait...').show(); 191 const formData = buildFormData(); 192 $formElements.prop('disabled', true); 193 194 // 4. Submit via AJAX 195 // Timeout covers the server's full external-call chain (reCAPTCHA, 196 // Zerobounce, two Customer.io calls, optional Luma Get Event + registration). 197 $.ajax({ url: '/wp-admin/admin-ajax.php?action=magic_form&mode=alt', type: 'POST', data: formData, dataType: 'json', timeout: 30000 }) 198 .done(function(response) { 199 if (response && response.magic_status === 'success') { 200 $successDiv.text(successMessage).show(); 201 $errorDiv.hide(); 202 203 // Set cookie 204 const expiresDate = new Date(); expiresDate.setFullYear(expiresDate.getFullYear() + 1); 205 document.cookie = "newsletter-close=true; expires=" + expiresDate.toUTCString() + "; path=/"; 206 207 // Analytics Tracking 208 const analyticsEventName = $form.data('analytics'); 209 if (analyticsEventName) { 210 if (typeof analytics !== 'undefined' && typeof analytics.track === 'function') { 211 const analyticsSource = $form.data('source') || 'unknown'; 212 const $emailField = $form.find('input[name="emailaddress"]'); 213 const emailValue = $emailField.val().trim(); 214 try { analytics.track(analyticsEventName, { source: anal
214yticsSource, email: emailValue }); } 215 catch (e) { console.error("Error calling analytics.track:", e); } 216 } else { console.warn("Analytics tracking specified, but analytics.track function not found."); } 217 } 218 $('.subscribe-popup,.nl-popup').fadeOut(1500); 219 // Redirect (keep elements disabled) 220 if (successRedirect) { setTimeout(function() { window.location.href = successRedirect; }, 1500); } 221 } else { 222 // Server-side failure - RE-ENABLE fields 223 errorMessage = (response && response.errorMessage) ? response.errorMessage : 'An unknown error occurred on the server.'; 224 $errorDiv.text(errorMessage).show(); $successDiv.hide(); $formElements.prop('disabled', false); 225 } 226 }) 227 .fail(function(jqXHR, textStatus, errorThrown) { 228 // AJAX failure - RE-ENABLE fields 229 let ajaxError = 'Could not submit the form. '; 230 if (textStatus === 'timeout') { ajaxError += 'The request timed out.'; } 231 else if (jqXHR.status === 0) { ajaxError += 'Check your network connection.'; } 232 else { ajaxError += `Server returned status ${jqXHR.status}.`; } 233 console.error("AJAX Error:", textStatus, errorThrown, jqXHR); 234 $errorDiv.text(ajaxError).show(); $successDiv.hide(); $formElements.prop('disabled', false); 235 }); 236 }; // end !disableRecaptcha 237 }); // End form.on('submit') 238 }); // End form.each 239 }); // End document.ready 240 } // End initFormHandler 241 242 // Helper function to load a script dynamically 243 function loadScript(src, callback) { 244 const script = document.createElement('script'); 245 script.src = src; 246 script.type = 'text/javascript'; 247 script.async = true; // Load asynchronously 248 script.onload = callback; // Call next step when loaded 249 script.onerror = function() { 250 console.error("Failed to load script: " + src); 251 // Handle error appropriately, maybe show a message 252 }; 253 document.head.appendChild(script); 254 } 255 256 // Function to check and load dependencies sequentially 257 function checkAndLoadDependencies() { 258 // Step 1: Check/Load jQuery 259 // The second half of this test used to be `|| typeof $ !== 'function'`, and that is 260 // true on every site running WordPress's own jQuery: core's copy ends with 261 // jQuery.noConflict(), so the $ alias is deliberately not defined. The handler 262 // therefore downloaded and ran a second jQuery from code.jquery.com on top of the one 263 // already on the page â 90 KB and a third-party request per page view, on every site 264 // with a magic-form. Only the presence of jQuery itself is asked about now; the alias 265 // is made locally in initFormHandler(). 266 if (typeof window.jQuery === 'undefined') { 267 loadScript(jqueryCdnUrl, checkAndLoadRecaptcha); // Load jQuery, then check reCAPTCHA 268 } else { 269 checkAndLoadRecaptcha(); // jQuery already loaded, check reCAPTCHA 270 } 271 } 272 273 // Function to check/load reCAPTCHA (called after jQuery is confirmed loaded) 274 function checkAndLoadRecaptcha() { 275 // Step 2: Check/Load reCAPTCHA 276 if (typeof window.grecaptcha === 'undefined') { 277 // Note: reCAPTCHA API script automatically handles its readiness. 278 // We just need to load it. The 'grecaptcha.ready' inside initFormHandler will wait. 279 loadScript(recaptchaApiUrl, initFormHandler); // Load reCAPTCHA, then init form handler 280 } else { 281 initFormHandler(); // reCAPTCHA already loaded, init form handler 282 } 283 } 284 285 // Start the dependency loading process 286 checkAndLoadDependencies(); 287 288})(); // End of IIFE 289
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.