1jQuery(document).ready(function($) { 2 console.log('ASM Ninja Forms Real-time Validation Loaded'); 3 4 // Real-time validation for Ninja Forms 5 let blockedWords = {}; 6 let formProtections = {}; 7 8 // No fallback protection - only use admin settings 9 const fallbackProtection = {}; 10 11 // Get form protection settings 12 function getFormProtection(formId) { 13 console.log('Getting protection for form ID:', formId); 14 15 return new Promise((resolve, reject) => { 16 $.ajax({ 17 url: nsf_ajax.ajax_url, 18 type: 'POST', 19 data: { 20 action: 'nfs_get_form_protection', // changed from nsf_get_form_protection 21 form_plugin: 'ninja-forms', 22 form_id: formId, 23 nonce: nsf_ajax.frontend_nonce 24 }, 25 success: function(response) { 26 console.log('AJAX response for Ninja Forms protection:', response); 27 if (response.success && response.data && response.data.field_protections) { 28 console.log('Using admin-configured protection settings:', response.data.field_protections); 29 resolve(response.data); 30 } else { 31 console.log('No admin protection found - no validation will be applied'); 32 resolve(null); 33 } 34 }, 35 error: function(xhr, status, error) { 36 console.error('AJAX error getting protection:', error); 37 console.log('No validation will be applied due to AJAX error'); 38 resolve(null); 39 } 40 }); 41 }); 42 } 43 44 // Check if text contains blocked words 45 function checkBlockedWords(text, protectionWords, protectionType) { 46 if (!text || !protectionWords) return { hasBlocked: false, blockedWord: null }; 47 48 const words = protectionWords.split(',').map(w => w.trim().toLowerCase()); 49 const textLower = text.toLowerCase(); 50 51 if (protectionType === 'blacklist') { 52 // Blacklist: Block if any blacklisted word is found 53 for (let word of words) { 54 if (word && textLower.includes(word)) { 55 return { hasBlocked: true, blockedWord: word }; 56 } 57 } 58 } else if (protectionType === 'whitelist') { 59 // Whitelist: Block if text contains words NOT in whitelist 60 const textWords = textLower.split(/\s+/).filter(w => w.length > 0); 61 for (let textWord of textWords) { 62 let isWhitelisted = false; 63 for (let whiteWord of words) { 64 if (whiteWord && textWord.includes(whiteWord)) { 65 isWhitelisted = true; 66 break; 67 } 68 } 69 if (!isWhitelisted) { 70 return { hasBlocked: true, blockedWord: textWord }; 71 } 72 } 73 } 74 75 return { hasBlocked: false, blockedWord: null }; 76 } 77 78 // Remove blocked words from text 79 function removeBlockedWords(text, protectionWords, protectionType) { 80 if (!text || !protectionWords) return text; 81 82 const words = protectionWords.split(',').map(w => w.trim()); 83 let cleanText = text; 84 85 if (protectionType === 'blacklist') { 86 // Remove blacklisted words 87 words.forEach(word => { 88 if (word) { 89 const regex = new RegExp(word.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'gi'); 90 cleanText = cleanText.replace(regex, ''); 91 } 92 }); 93 } else if (protectionType === 'whitelist') { 94 // For whitelist, remove words that are NOT in the whitelist 95 const textWords = cleanText.split(/\s+/); 96 const filteredWords = textWords.filter(textWord => { 97 if (!textWord.trim()) return true; // Keep empty strings/spaces 98 99 const textWordLower = textWord.toLowerCase(); 100 for (let whiteWord of words) { 101 if (whiteWord && textWordLower.includes(whiteWord.toLowerCase())) { 102 return true; // Keep whitelisted words 103 } 104 } 105 return false; // Remove non-whitelisted words 106 }); 107 cleanText = filteredWords.join(' '); 108 } 109 110 return cleanText.trim(); 111 } 112 113 // Show warning message 114 function showWarning(field, message, protectionType) { 115 const $field = $(field); 116 const $container = $field.closest('.nf-field-container'); 117 118 // Remove existing warnings 119 $container.find('.asm-warning').remove(); 120 121 // Add warning styling 122 $field.addClass('asm-blocked-field'); 123 124 // Create appropriate warning message based on protection type 125 let warningText = message; 126 if (protectionType === 'blacklist') { 127 warningText = 'This content contains prohibited words and has been removed.'; 128 } else if (protectionType === 'whitelist') { 129 warningText = 'Only specific words are allowed in this field. Unauthorized content has been removed.'; 130 } 131 132 // Add warning message 133 const $warning = $('<div class="asm-warning" style="color: #d63638; font-size: 12px; margin-top: 5px; display: flex; align-items: center;"><span class="dashicons dashicons-warning" style="margin-right: 5px; font-size: 14px;"></span>' + warningText + '</div>'); 134 $container.append($warning); 135 } 136 137 // Clear warning 138 function clearWarning(field) { 139 const $field = $(field); 140 const $container = $field.closest('.nf-field-container'); 141 142 $field.removeClass('asm-blocked-field'); 143 $container.find('.asm-warning').remove(); 144 } 145
146 // Initialize real-time validation for Ninja Forms 147 function initNinjaFormsValidation() { 148 console.log('Initializing Ninja Forms validation...'); 149 150 // Find all Ninja Forms - use exact selectors from HTML structure 151 const $forms = $('.nf-form-wrap, .ninja-forms-form-wrap, div[class*="nf-form"]'); 152 console.log('Found forms:', $forms.length); 153 154 $forms.each(function() { 155 const $form = $(this); 156 console.log('Processing form:', $form); 157 158 // Extract form ID from JavaScript variable (from screenshot) 159 let formId = null; 160 161 // Method 1: Look for FormDisplay variable in page scripts 162 if (window.nfForms && window.nfForms.length > 0) { 163 formId = window.nfForms[0].id; 164 console.log('Form ID from nfForms global:', formId); 165 } 166 167 // Method 2: Extract from form data attributes or classes 168 if (!formId) { 169 const formClass = $form.attr('class') || ''; 170 const formIdMatch = formClass.match(/form-(\d+)/); 171 if (formIdMatch) { 172 formId = formIdMatch[1]; 173 console.log('Form ID from class:', formId); 174 } 175 } 176 177 // Method 3: Look for form ID in hidden inputs within the form 178 if (!formId) { 179 $form.find('input[type="hidden"]').each(function() { 180 const name = $(this).attr('name'); 181 const value = $(this).val(); 182 console.log('Hidden input found:', name, '=', value); 183 184 if (name === 'form_id' || name === 'id' || (name && name.includes('form') && /^\d+$/.test(value))) { 185 formId = value; 186 console.log('Form ID from hidden input:', formId); 187 return false; 188 } 189 }); 190 } 191 192 // Method 4: Extract from script tags containing form data 193 if (!formId) { 194 $('script').each(function() { 195 const scriptContent = $(this).html(); 196 if (scriptContent && scriptContent.includes('form_id')) { 197 const formIdMatch = scriptContent.match(/"form_id"\s*:\s*"?(\d+)"?/); 198 if (formIdMatch) { 199 formId = formIdMatch[1]; 200 console.log('Form ID from script:', formId); 201 return false; 202 } 203 } 204 }); 205 } 206 207 // Method 5: Default fallback 208 if (!formId) { 209 console.log('No form ID found - using default form ID 1'); 210 formId = '1'; 211 } 212 213 console.log('Form ID found:', formId); 214 215 // Get protection settings for this form 216 getFormProtection(formId).then(function(protection) { 217 console.log('Protection settings received:', protection); 218 219 if (!protection || !protection.field_protections) { 220 console.log('No protection settings found - skipping validation for this form'); 221 return; 222 } 223 224 const fieldProtections = typeof protection.field_protections === 'string' 225 ? JSON.parse(protection.field_protections) 226 : protection.field_protections; 227 228 console.log('Parsed field protections:', fieldProtections); 229 230 // Check if field protections is empty 231 if (!fieldProtections || Object.keys(fieldProtections).length === 0) { 232 console.log('Field protections is empty - no validation will be applied'); 233 return; 234 } 235 236 // Add real-time validation to ALL input fields 237 $form.find('input[type="text"], input[type="email"], textarea').each(function() { 238 const $field = $(this); 239 const $container = $field.closest('.nf-field-container, .field-wrap'); 240 const $label = $container.find('label, .nf-field-label'); 241 let fieldLabel = $label.text().trim(); 242 243 // Try multiple ways to get field label 244 if (!fieldLabel) { 245 fieldLabel = $field.attr('placeholder') || $field.attr('name') || $field.attr('id'); 246 } 247 248 console.log('Found field with label:', fieldLabel); 249 console.log('Available protections:', Object.keys(fieldProtections)); 250 251 // Check if this field has protection - try exact match and partial match 252 let fieldProtection = fieldProtections[fieldLabel]; 253 254 // If no exact match, try partial matching 255 if (!fieldProtection) { 256 for (let protectionKey in fieldProtections) { 257 if (fieldLabel.toLowerCase().includes(protectionKey.toLowerCase()) || 258 protectionKey.toLowerCase().includes(fieldLabel.toLowerCase())) { 259 fieldProtection = fieldProtections[protectionKey]; 260 console.log('Found partial match:', protectionKey, 'for field:', fieldLabel); 261 break; 262 } 263 } 264 } 265 266 // Also try matching by field position/index 267 if (!fieldProtection) { 268 const fieldIndex = $form.find('input[type="text"], input[type="email"], textarea').index(this); 269 const protectionKeys = Object.keys(fieldProtections); 270 if (protectionKeys[fieldIndex]) { 271 fieldProtection = fieldProtections[protectionKeys[fieldIndex]]; 272 console.log('Found by index match:', protectionKeys[fieldIndex], 'for field at index:', fieldIndex); 273 } 274 } 275 276 if (fieldProtection) { 277 console.log('Adding validation to field:', fieldLabel, fieldProtection); 278 279 // Add input event listener 280 $field.on('input keyup paste', function() { 281 const currentValue = $(this).val(); 282 console.log('Field value changed:', fieldLabel, currentValue); 283 console.log('Protection type:', fieldProtection.type); 284 console.log('Protection words:', fieldProtection.words); 285 286 const result = checkBlockedWords(currentValue, fieldProtection.words, fieldProtection.type); 287 console.log('Protection check result:', result); 288 289 if (result.hasBlocked) { 290 console.log('Blocked content found, removing...'); 291 292 // Auto-remove blocked content 293 const cleanValue = removeBlockedWords(currentValue, fieldProtection.words, fieldProtection.type); 294 $(this).val(cleanValue); 295 296 // Show appropriate warning based on protection type 297 if (fieldProtection.type === 'blacklist') { 298 showWarning(this, 'Blocked word "' + result.blockedWord + '" removed automatically', 'blacklist'); 299 } else if (fieldProtection.type === 'whitelist') { 300 showWarning(this, 'Non-whitelisted word "' + result.blockedWord + '" removed automatically', 'whitelist'); 301 } 302 303 // Clear warning after 3 seconds 304 setTimeout(() => { 305 clearWarning(this); 306 }, 3000); 307 } else { 308 clearWarning(this); 309 } 310 }); 311 } 312 }); 313 }); 314 }); 315 } 316 317 // CSS for blocked fields 318 $('<style>') 319 .prop('type', 'text/css') 320 .html(` 321 .asm-blocked-field { 322 border: 2px solid #d63638 !important; 323 background-color: #fef7f7 !important; 324 } 325 .asm-warning { 326 animation: asm-fade-in 0.3s ease-in; 327 } 328 @keyframes asm-fade-in { 329 from { opacity: 0; transform: translateY(-10px); } 330 to { opacity: 1; transform: translateY(0); } 331 } 332 `) 333 .appendTo('head'); 334 335 // Initialize when DOM is ready 336 initNinjaFormsValidation(); 337 338 // Re-initialize when new forms are loaded (AJAX) 339 $(document).on('nfFormReady', function() { 340 setTimeout(initNinjaFormsValidation, 500); 341 }); 342});
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.