1/** 2 * Double Opt-In â Universal Error Notification 3 * 4 * Listens for form submission events from any supported form plugin 5 * and checks the server-side AJAX endpoint for OptIn creation errors. 6 * Displays a toast notification if an error is found. 7 * 8 * @since 4.2.0 9 */ 10(function () { 11 'use strict'; 12 13 if ( typeof doiErrorNotification === 'undefined' ) { 14 return; 15 } 16 17 var config = doiErrorNotification; 18 var checking = false; 19 var cooldown = false; 20 21 /** 22 * Check the AJAX endpoint for a stored submission error. 23 */ 24 function checkForError() { 25 if ( checking || cooldown ) { 26 return; 27 } 28 checking = true; 29 30 var xhr = new XMLHttpRequest(); 31 xhr._doiInternal = true; // Prevent the XHR interceptor from re-triggering scheduleCheck 32 xhr.open( 'POST', config.ajaxUrl, true ); 33 xhr.setRequestHeader( 'Content-Type', 'application/x-www-form-urlencoded' ); 34 35 xhr.onreadystatechange = function () { 36 if ( xhr.readyState !== 4 ) { 37 return; 38 } 39 checking = false; 40 41 if ( xhr.status !== 200 ) { 42 return; 43 } 44 45 try { 46 var response = JSON.parse( xhr.responseText ); 47 } catch ( e ) { 48 return; 49 } 50 51 if ( response.success && response.data && response.data.error ) { 52 // Hide form confirmation messages when validation error should be visible 53 if ( response.data.error.hide_confirmation ) { 54 hideFormConfirmations(); 55 } 56 57 // Redirect to error page if configured, otherwise show toast 58 if ( response.data.redirect_url ) { 59 window.location.href = response.data.redirect_url; 60 return; 61 } 62 63 showNotification( response.data.error.message ); 64 65 // Prevent duplicate checks for 5 seconds 66 cooldown = true; 67 setTimeout( function () { 68 cooldown = false; 69 }, 5000 ); 70 } 71 }; 72 73 xhr.send( 74 'action=doi_check_submission_error&nonce=' + encodeURIComponent( config.nonce ) 75 ); 76 } 77 78 /** 79 * Schedule an error check with a short delay to ensure 80 * the server-side form processing has completed. 81 */ 82 function scheduleCheck() { 83 setTimeout( checkForError, 800 ); 84 } 85 86 /** 87 * Hide form plugin confirmation/success messages. 88 * 89 * Called when a validation error should be visible (block/redirect mode) 90 * so the user does not see a contradictory success message. 91 */ 92 function hideFormConfirmations() { 93 // WPForms confirmation containers 94 var wpfSelectors = '.wpforms-confirmation-container-full, .wpforms-confirmation-container'; 95 document.querySelectorAll( wpfSelectors ).forEach( function ( el ) { 96 el.style.display = 'none'; 97 } ); 98 99 // Gravity Forms confirmation wrapper 100 document.querySelectorAll( '.gform_confirmation_wrapper' ).forEach( function ( el ) { 101 el.style.display = 'none'; 102 } ); 103 104 // CF7 success response (only hide the success state, not error state) 105 document.querySelectorAll( '.wpcf7-response-output.wpcf7-mail-sent-ok' ).forEach( function ( el ) { 106 el.style.display = 'none'; 107 } ); 108 109 // Elementor Forms success message 110 document.querySelectorAll( '.elementor-message.elementor-message-success' ).forEach( function ( el ) { 111 el.style.display = 'none'; 112 } ); 113 } 114 115 /** 116 * Show a toast notification with the error message. 117 * 118 * @param {string} message The error message to display. 119 */ 120 function showNotification( message ) { 121 var existing = document.querySelector( '.doi-error-notification' ); 122 if ( existing ) { 123 existing.remove(); 124 } 125 126 var notification = document.createElement( 'div' ); 127 notification.className = 'doi-error-notification'; 128 notification.setAttribute( 'role', 'alert' ); 129 130 var content = document.createElement( 'div' ); 131 content.className = 'doi-error-notification__content'; 132 133 var icon = document.createElement( 'span' ); 134 icon.className = 'doi-error-notification__icon'; 135 icon.innerHTML = '⚠'; 136 137 var text = document.createElement( 'p' ); 138 text.className = 'doi-error-notification__message'; 139 text.textContent = message; 140 141 var closeBtn = document.createElement( 'button' ); 142 closeBtn.className = 'doi-error-notification__close'; 143 closeBtn.setAttribute( 'type', 'button' ); 144 closeBtn.innerHTML = '×'; 145 closeBtn.addEventListener( 'click', function () { 146 dismiss( notification ); 147 } ); 148 149 content.appendChild( icon ); 150 content.appendChild( text ); 151 content.appendChild( closeBtn ); 152 notification.appendChild( content ); 153 document.body.appendChild( notification ); 154
155 // Trigger enter animation 156 requestAnimationFrame( function () { 157 notification.classList.add( 'doi-error-notification--visible' ); 158 } ); 159 160 // Auto-dismiss after 10 seconds 161 setTimeout( function () { 162 dismiss( notification ); 163 }, 10000 ); 164 } 165 166 /** 167 * Dismiss a notification with exit animation. 168 * 169 * @param {HTMLElement} el The notification element. 170 */ 171 function dismiss( el ) { 172 if ( ! el || ! el.parentNode ) { 173 return; 174 } 175 el.classList.remove( 'doi-error-notification--visible' ); 176 el.classList.add( 'doi-error-notification--exit' ); 177 setTimeout( function () { 178 if ( el.parentNode ) { 179 el.parentNode.removeChild( el ); 180 } 181 }, 300 ); 182 } 183 184 // ------------------------------------------------------------------------- 185 // Form plugin event listeners 186 // ------------------------------------------------------------------------- 187 188 // Contact Form 7 (native DOM events) 189 document.addEventListener( 'wpcf7mailsent', scheduleCheck ); 190 document.addEventListener( 'wpcf7invalid', scheduleCheck ); 191 document.addEventListener( 'wpcf7spam', scheduleCheck ); 192 document.addEventListener( 'wpcf7mailfailed', scheduleCheck ); 193 194 // WPForms + Gravity Forms (jQuery events â not caught by native addEventListener) 195 if ( typeof jQuery !== 'undefined' ) { 196 jQuery( document ).on( 'wpformsAjaxSubmitSuccess', scheduleCheck ); 197 jQuery( document ).on( 'wpformsAjaxSubmitError', scheduleCheck ); 198 jQuery( document ).on( 'gform_confirmation_loaded', scheduleCheck ); 199 } 200 201 // ------------------------------------------------------------------------- 202 // Universal: catch ANY form submission on the page (capture phase) 203 // This is the most reliable way to detect form submissions regardless 204 // of which form plugin is used. It covers AJAX, standard, and custom flows. 205 // ------------------------------------------------------------------------- 206 document.addEventListener( 'submit', function () { 207 // Use a longer delay for standard (non-AJAX) submissions 208 // because the server needs time to process the request. 209 setTimeout( checkForError, 1500 ); 210 }, true ); 211 212 // ------------------------------------------------------------------------- 213 // Generic AJAX interception 214 // Catches all AJAX calls to detect form submissions from plugins 215 // that don't fire specific events (e.g., Avada, Elementor). 216 // ------------------------------------------------------------------------- 217 218 // Intercept fetch() 219 if ( typeof window.fetch === 'function' ) { 220 var originalFetch = window.fetch; 221 window.fetch = function () { 222 // Capture URL before the async call â `arguments` inside .then() 223 // refers to the callback's own arguments, not the fetch arguments. 224 var fetchUrl = ( typeof arguments[0] === 'string' ) 225 ? arguments[0] 226 : ( arguments[0] && arguments[0].url ) || ''; 227 228 return originalFetch.apply( this, arguments ).then( function ( response ) { 229 if ( fetchUrl && fetchUrl.indexOf( 'admin-ajax.php' ) !== -1 ) { 230 scheduleCheck(); 231 } 232 return response; 233 } ); 234 }; 235 } 236 237 // Intercept XMLHttpRequest for jQuery.ajax based form plugins 238 var origOpen = XMLHttpRequest.prototype.open; 239 XMLHttpRequest.prototype.open = function ( method, url ) { 240 // Skip our own internal error-check requests to prevent infinite polling loops 241 if ( typeof url === 'string' && url.indexOf( 'admin-ajax.php' ) !== -1 && ! this._doiInternal ) { 242 this.addEventListener( 'load', scheduleCheck ); 243 } 244 return origOpen.apply( this, arguments ); 245 }; 246 247})();
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.