1/** 2 * Shared input-mask validation helpers (Elementor Pro, Cool Form, Hello Plus, Atomic Form). 3 */ 4window.CFKEF = window.CFKEF || {}; 5 6window.CFKEF.MaskValidators = (function () { 7 'use strict'; 8 9 function isValidPhoneUSA(phoneStr) { 10 return /^\(\d{3}\) \d{3}-\d{4}$/.test(phoneStr); 11 } 12 13 function isValidPhone8(phoneStr) { 14 return /^\d{4}-\d{4}$/.test(phoneStr); 15 } 16 17 function isValidPhoneDDD8(phoneStr) { 18 return /^\(\d{2}\) \d{4}-\d{4}$/.test(phoneStr); 19 } 20 21 function isValidPhoneDDD9(phoneStr) { 22 return /^\(\d{2}\) 9\d{4}-\d{4}$/.test(phoneStr); 23 } 24 25 function isValidDateTime(value, format) { 26 var regexPattern; 27 var expectedParts; 28 29 switch (format) { 30 case 'DMY': 31 regexPattern = /^(\d{2})\/(\d{2})\/(\d{4})$/; 32 expectedParts = ['day', 'month', 'year']; 33 break; 34 case 'MDY': 35 regexPattern = /^(\d{2})\/(\d{2})\/(\d{4})$/; 36 expectedParts = ['month', 'day', 'year']; 37 break; 38 case 'HMS': 39 regexPattern = /^(\d{2}):(\d{2}):(\d{2})$/; 40 expectedParts = ['hour', 'minute', 'second']; 41 break; 42 case 'HM': 43 regexPattern = /^(\d{2}):(\d{2})$/; 44 expectedParts = ['hour', 'minute']; 45 break; 46 case 'DMY-HM': 47 regexPattern = /^(\d{2})\/(\d{2})\/(\d{4}) (\d{2}):(\d{2})$/; 48 expectedParts = ['day', 'month', 'year', 'hour', 'minute']; 49 break; 50 case 'MDY-HM': 51 regexPattern = /^(\d{2})\/(\d{2})\/(\d{4}) (\d{2}):(\d{2})$/; 52 expectedParts = ['month', 'day', 'year', 'hour', 'minute']; 53 break; 54 case 'MY': 55 regexPattern = /^(\d{2})\/(\d{4})$/; 56 expectedParts = ['month', 'year']; 57 break; 58 default: 59 return false; 60 } 61 62 var match = value.match(regexPattern); 63 if (!match) { 64 return false; 65 } 66 67 var parts = {}; 68 expectedParts.forEach(function (part, index) { 69 parts[part] = parseInt(match[index + 1], 10); 70 }); 71 72 if (parts.year && (parts.year < 1500 || parts.year > 3000)) { 73 return false; 74 } 75 if (parts.month && (parts.month < 1 || parts.month > 12)) { 76 return false; 77 } 78 if (parts.day) { 79 var daysInMonth = new Date(parts.year, parts.month, 0).getDate(); 80 if (parts.day < 1 || parts.day > daysInMonth) { 81 return false; 82 } 83 } 84 if (parts.hour && (parts.hour < 0 || parts.hour >= 24)) { 85 return false; 86 } 87 if (parts.minute && (parts.minute < 0 || parts.minute >= 60)) { 88 return false; 89 } 90 if (parts.second && (parts.second < 0 || parts.second >= 60)) { 91 return false; 92 } 93 94 return true; 95 } 96 97 function isValidExpiryDate(value, format) { 98 var regexPattern = format === 'MM/YY' ? /^(\d{2})\/(\d{2})$/ : /^(\d{2})\/(\d{4})$/; 99 var match = value.match(regexPattern); 100 if (!match) { 101 return false; 102 } 103 104 var month = parseInt(match[1], 10); 105 var year = parseInt(match[2], 10); 106 var currentYear = new Date().getFullYear(); 107 var currentMonth = new Date().getMonth() + 1; 108 109 if (format === 'MM/YY') { 110 year += 2000; 111 } 112 if (month < 1 || month > 12) { 113 return false; 114 } 115 if (year < currentYear || (year === currentYear && month < currentMonth)) { 116 return false; 117 } 118 return true; 119 } 120 121 function isValidCreditCard(cardNumber) { 122 var cleaned = String(cardNumber).replace(/\D/g, ''); 123 if (cleaned.length < 15 || cleaned.length > 16) { 124 return false; 125 } 126 var sum = 0; 127 var shouldDouble = false; 128 for (var i = cleaned.length - 1; i >= 0; i--) { 129 var digit = parseInt(cleaned.charAt(i), 10); 130 if (shouldDouble) { 131 digit *= 2; 132 if (digit > 9) { 133 digit -= 9; 134 } 135 } 136 sum += digit; 137 shouldDouble = !shouldDouble; 138 } 139 return sum % 10 === 0; 140 } 141 142 function isValidCNPJ(cnpj) { 143 cnpj = String(cnpj).toUpperCase().replace(/[.\-\/]/g, ''); 144 if (!/^[A-Z0-9]{12}\d{2}$/.test(cnpj)) { 145 return false; 146 } 147 if (/^(.)\1{13}$/.test(cnpj)) { 148 return false; 149 } 150 var calcCheckDigit = function (str, length) { 151 var weights = length === 12 152 ? [5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2] 153 : [6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2]; 154 var s = 0; 155 for (var i = 0; i < weights.length; i++) { 156 s += (str.charCodeAt(i) - 48) * weights[i]; 157 } 158 var remainder = s % 11; 159 return remainder < 2 ? 0 : 11 - remainder; 160 }; 161 var firstCheck = calcCheckDigit(cnpj, 12); 162 var secondCheck = calcCheckDigit(cnpj.slice(0, 12) + firstCheck, 13); 163 return firstCheck === parseInt(cnpj.charAt(12), 10) && secondCheck === parseInt(cnpj.charAt(13), 10); 164 } 165 166 function isValidCPF(cpf) {
167 cpf = String(cpf).replace(/\D/g, ''); 168 if (cpf.length !== 11) { 169 return false; 170 } 171 if (/^(\d)\1+$/.test(cpf)) { 172 return false; 173 } 174 var validateCPFDigit = function (str, length) { 175 var s = 0; 176 for (var i = 0; i < length; i++) { 177 s += parseInt(str.charAt(i), 10) * (length + 1 - i); 178 } 179 var result = (s * 10) % 11; 180 if (result === 10) { 181 result = 0; 182 } 183 return result === parseInt(str.charAt(length), 10); 184 }; 185 return validateCPFDigit(cpf, 9) && validateCPFDigit(cpf, 10); 186 } 187 188 function isValidCEP(cep) { 189 return /^\d{5}-\d{3}$/.test(cep); 190 } 191 192 function isValidIPv4(ip) { 193 var ipv4Pattern = /^(?:\d{1,3}\.){3}\d{1,3}$/; 194 if (!ipv4Pattern.test(ip)) { 195 return false; 196 } 197 return ip.split('.').every(function (octet) { 198 var num = parseInt(octet, 10); 199 return num >= 0 && num <= 255; 200 }); 201 } 202 203 return { 204 isValidPhoneUSA: isValidPhoneUSA, 205 isValidPhone8: isValidPhone8, 206 isValidPhoneDDD8: isValidPhoneDDD8, 207 isValidPhoneDDD9: isValidPhoneDDD9, 208 isValidDateTime: isValidDateTime, 209 isValidExpiryDate: isValidExpiryDate, 210 isValidDateDMY: function (v) { return isValidDateTime(v, 'DMY'); }, 211 isValidDateMDY: function (v) { return isValidDateTime(v, 'MDY'); }, 212 isValidTimeHMS: function (v) { return isValidDateTime(v, 'HMS'); }, 213 isValidTimeHM: function (v) { return isValidDateTime(v, 'HM'); }, 214 isValidDateDMYHM: function (v) { return isValidDateTime(v, 'DMY-HM'); }, 215 isValidDateMDYHM: function (v) { return isValidDateTime(v, 'MDY-HM'); }, 216 isValidDateMY: function (v) { return isValidDateTime(v, 'MY'); }, 217 isValidExpiryMMYY: function (v) { return isValidExpiryDate(v, 'MM/YY'); }, 218 isValidExpiryMMYYYY: function (v) { return isValidExpiryDate(v, 'MM/YYYY'); }, 219 isValidCreditCard: isValidCreditCard, 220 isValidCNPJ: isValidCNPJ, 221 isValidCPF: isValidCPF, 222 isValidCEP: isValidCEP, 223 isValidIPv4: isValidIPv4, 224 }; 225})(); 226 227CFKEF.getMaskValidationDefs = function () { 228 var MV = CFKEF.MaskValidators || {}; 229 return [ 230 { sel: '.mask-cnpj', errorClass: 'error-cnpj', validate: MV.isValidCNPJ, key: 'mask-cnpj' }, 231 { sel: '.mask-cpf', errorClass: 'error-cpf', validate: MV.isValidCPF, key: 'mask-cpf' }, 232 { sel: '.mask-cep', errorClass: 'error-cep', validate: MV.isValidCEP, key: 'mask-cep' }, 233 { sel: '.mask-phus', errorClass: 'error-phus', validate: MV.isValidPhoneUSA, key: 'mask-phus' }, 234 { sel: '.mask-ph8', errorClass: 'error-ph8', validate: MV.isValidPhone8, key: 'mask-ph8' }, 235 { sel: '.mask-ddd8', errorClass: 'error-ddd8', validate: MV.isValidPhoneDDD8, key: 'mask-ddd8' }, 236 { sel: '.mask-ddd9', errorClass: 'error-ddd9', validate: MV.isValidPhoneDDD9, key: 'mask-ddd9' }, 237 { sel: '.mask-dmy', errorClass: 'error-dmy', validate: MV.isValidDateDMY, key: 'mask-dmy' }, 238 { sel: '.mask-mdy', errorClass: 'error-mdy', validate: MV.isValidDateMDY, key: 'mask-mdy' }, 239 { sel: '.mask-hms', errorClass: 'error-hms', validate: MV.isValidTimeHMS, key: 'mask-hms' }, 240 { sel: '.mask-hm', errorClass: 'error-hm', validate: MV.isValidTimeHM, key: 'mask-hm' }, 241 { sel: '.mask-dmyhm', errorClass: 'error-dmyhm', validate: MV.isValidDateDMYHM, key: 'mask-dmyhm' }, 242 { sel: '.mask-mdyhm', errorClass: 'error-mdyhm', validate: MV.isValidDateMDYHM, key: 'mask-mdyhm' }, 243 { sel: '.mask-my', errorClass: 'error-my', validate: MV.isValidDateMY, key: 'mask-my' }, 244 { sel: '.mask-ccs', errorClass: 'error-ccs', validate: MV.isValidCreditCard, key: 'mask-ccs' }, 245 { sel: '.mask-cch', errorClass: 'error-cch', validate: MV.isValidCreditCard, key: 'mask-cch' }, 246 { sel: '.mask-ccmy', errorClass: 'error-ccmy', validate: MV.isValidExpiryMMYY, key: 'mask-ccmy' }, 247 { sel: '.mask-ccmyy', errorClass: 'error-ccmyy', validate: MV.isValidExpiryMMYYYY, key: 'mask-ccmyy' }, 248 { sel: '.mask-ipv4', errorClass: 'error-ipv4', validate: MV.isValidIPv4, key: 'mask-ipv4' }, 249 ]; 250}; 251 252/** 253 * Resolve the .mask-error node for an input. 254 * 255 * @param {jQuery} $ 256 * @param {jQuery} $input 257 * @param {string} errorClass 258 * @param {Object} [opts] 259 * @param {Function} [opts.resolveErrorElement] 260 * @return {jQuery} 261 */ 262CFKEF.resolveMaskErrorElement = function ($, $input, errorClass, opts) { 263 opts = opts || {}; 264 if ($input.hasClass('hide-fme-mask-input')) { 265 return $(); 266 } 267 if (typeof opts.resolveErrorElement === 'function') { 268 return opts.resolveErrorElement($input, errorClass); 269 } 270 if ($input.hasClass('cool-form__field')) { 271 return $input.closest('.cool-form__field-group').find('.' + errorClass); 272 } 273 if ($input.hasClass('ehp-form__field')) { 274 return $input.closest('.ehp-form__field-group').find('.' + errorClass); 275 } 276 return $input.closest('.elementor-field-group').find('.' + errorClass); 277}; 278 279/** 280 * Find mask validation def matching an input's classes. 281 * 282 * @param {jQuery} $input 283 * @return {Object|null} 284 */ 285CFKEF.findMaskDefForInput = function ($input) { 286 var defs = CFKEF.getMaskValidationDefs(); 287 for (var i = 0; i < defs.length; i++) { 288 var cls = defs[i].sel.replace(/^\./, ''); 289 if ($input.hasClass(cls)) { 290 return defs[i]; 291 } 292 } 293 return null; 294}; 295 296/** 297 * Bind blur/input validation for one mask selector. 298 * 299 * @param {jQuery} $ 300 * @param {string} selector 301 * @param {string} errorClass 302 * @param {Function} validationFunction 303 * @param {string} errorMessage 304 * @param {Object} [opts] 305 */ 306CFKEF.bindMaskFieldValidation = function ($, selector, errorClass, validationFunction, errorMessage, opts) { 307 opts = opts || {}; 308 309 $(document).on('blur', selector, function () { 310 var $input = $(this); 311 if (typeof opts.shouldHandle === 'function' && !opts.shouldHandle($input)) { 312 return; 313 } 314 if ($input.hasClass('hide-fme-mask-input')) { 315 return; 316 } 317 318 var val = $input.val(); 319 var $errorEl = CFKEF.resolveMaskErrorElement($, $input, errorClass, opts); 320 321 if (String(val).length === 1 && !/\d/.test(val)) { 322 $input.val(''); 323 $errorEl.hide().text(''); 324 return; 325 } 326 327 if (val !== '' && !validationFunction(val)) { 328 $errorEl.text(errorMessage).css('display', 'flex').hide().fadeIn(200); 329 } else { 330 $errorEl.fadeOut(100, function () { 331 $(this).css('display', 'none'); 332 }); 333 } 334 }); 335 336 $(document).on('input', selector, function () { 337 var $input = $(this); 338 if (typeof opts.shouldHandle === 'function' && !opts.shouldHandle($input)) { 339 return; 340 } 341 if (typeof opts.onInput === 'function') { 342 opts.onInput($input, errorClass, validationFunction); 343 } 344 345 var $errorEl = CFKEF.resolveMaskErrorElement($, $input, errorClass, opts); 346 if ($errorEl.is(':visible')) { 347 var val = $input.val(); 348 if (validationFunction(val)) { 349 $errorEl.fadeOut(100, function () { 350 $(this).css('display', 'none'); 351 }); 352 } 353 } 354 }); 355}; 356 357/** 358 * Bind blur/input validation for all shared mask defs. 359 * 360 * @param {jQuery} $ 361 * @param {Object} [opts] 362 */ 363CFKEF.bindAllMaskValidations = function ($, opts) { 364 opts = opts || {}; 365 var msgs = opts.errorMessages || (window.fmeData && window.fmeData.errorMessages) || {}; 366 var defs = CFKEF.getMaskValidationDefs(); 367
368 defs.forEach(function (def) { 369 if (typeof def.validate !== 'function') { 370 return; 371 } 372 CFKEF.bindMaskFieldValidation( 373 $, 374 def.sel, 375 def.errorClass, 376 def.validate, 377 msgs[def.key] || def.key, 378 opts 379 ); 380 }); 381}; 382 383/** 384 * Actively validate mask inputs in a form; show errors and optionally scroll. 385 * 386 * @param {jQuery} $ 387 * @param {jQuery} $form 388 * @param {Object} [opts] 389 * @return {boolean} true if submit must be blocked 390 */ 391CFKEF.maskValidationBlocksSubmit = function ($, $form, opts) { 392 opts = opts || {}; 393 var msgs = opts.errorMessages || (window.fmeData && window.fmeData.errorMessages) || {}; 394 var blocked = false; 395 var $scrollTarget = null; 396 var inputSelector = opts.inputSelector || 'input.fme-mask-input'; 397 398 $form.find(inputSelector).not('.hide-fme-mask-input').each(function () { 399 var $inp = $(this); 400 if (typeof opts.shouldValidate === 'function' && !opts.shouldValidate($inp)) { 401 return; 402 } 403 404 var val = String($inp.val() || '').trim(); 405 if (val.length === 1 && !/\d/.test(val)) { 406 $inp.val(''); 407 val = ''; 408 } 409 410 var def = CFKEF.findMaskDefForInput($inp); 411 if (!def || typeof def.validate !== 'function') { 412 return; 413 } 414 415 var $err = CFKEF.resolveMaskErrorElement($, $inp, def.errorClass, opts); 416 if (val !== '' && !def.validate(val)) { 417 $err.text(msgs[def.key] || def.key).css('display', 'flex').show(); 418 blocked = true; 419 if (!$scrollTarget || !$scrollTarget.length) { 420 $scrollTarget = $err; 421 } 422 } else { 423 $err.text('').hide().css('display', 'none'); 424 } 425 }); 426 427 if (blocked && $scrollTarget && $scrollTarget.length && opts.scroll !== false) { 428 var off = $scrollTarget.offset(); 429 if (off) { 430 $('html, body').animate({ scrollTop: off.top - (opts.scrollOffset || 200) }, opts.scrollDuration || 300); 431 } 432 } 433 434 return blocked; 435}; 436 437/** 438 * Shared Cool Form / Hello Plus (and similar) mask submit click guard. 439 * 440 * @param {jQuery} $ 441 * @param {string} selector 442 * @param {Object} [opts] 443 */ 444CFKEF.bindMaskSubmitGuard = function ($, selector, opts) { 445 opts = opts || {}; 446 var delay = opts.delay != null ? opts.delay : 400; 447 var preventDefaultAlways = !!opts.preventDefaultAlways; 448 var checkRequiredEmpty = !!opts.checkRequiredEmpty; 449 var addWaitingClass = !!opts.addWaitingClass; 450 var checkHtml5Validity = !!opts.checkHtml5Validity; 451 var scrollOffset = opts.scrollOffset || 200; 452 var scrollDuration = opts.scrollDuration || 400; 453 var errorDisplayFlex = !!opts.errorDisplayFlex; 454 var onBefore = typeof opts.onBefore === 'function' ? opts.onBefore : null; 455 var onAfterValid = typeof opts.onAfterValid === 'function' ? opts.onAfterValid : null; 456 var onBlocked = typeof opts.onBlocked === 'function' ? opts.onBlocked : null; 457 var shouldHandle = typeof opts.shouldHandle === 'function' ? opts.shouldHandle : null; 458 459 $(document).on('click', selector, function (e) { 460 var $submitBtn = $(this); 461 462 if (shouldHandle && !shouldHandle($submitBtn, e)) { 463 return; 464 } 465 466 if (preventDefaultAlways) { 467 e.preventDefault(); 468 } 469 470 var $form = $submitBtn.closest('form'); 471 472 if ($submitBtn.data('clicked')) { 473 e.preventDefault(); 474 return; 475 } 476 $submitBtn.data('clicked', true); 477 478 if (onBefore) { 479 onBefore($submitBtn, $form, e); 480 } 481 482 $form.find('input').trigger('blur'); 483 if (addWaitingClass && $form[0]) { 484 $form[0].classList.add('elementor-form-waiting'); 485 } 486 487 setTimeout(function () { 488 var hasError = false; 489 var $errors = $form.find('.mask-error').filter(function () { 490 var visible = errorDisplayFlex 491 ? $(this).css('display') === 'flex' 492 : $(this).is(':visible'); 493 return $(this).text().trim() !== '' && visible; 494 }); 495 496 if ($errors.length > 0) { 497 hasError = true; 498 $('html, body').animate( 499 { scrollTop: $errors.first().offset().top - scrollOffset }, 500 scrollDuration 501 ); 502 } 503 504 if (checkRequiredEmpty) { 505 var $emptyRequiredMasked = $form.find('input[required]').filter(function () { 506 if ($(this).hasClass('hide-fme-mask-input')) { 507 return false; 508 } 509 var val = String($(this).val() || '').trim(); 510 var isVisible = errorDisplayFlex 511 ? $(this).css('display') === 'flex' 512 : $(this).is(':visible'); 513 return isVisible && (val === '' || /^[\s_\-\(\)\.:/]+$/.test(val)); 514 }); 515 516 if ($emptyRequiredMasked.length > 0) { 517 hasError = true; 518 $('html, body').animate( 519 { scrollTop: $emptyRequiredMasked.first().offset().top - scrollOffset }, 520 scrollDuration 521 ); 522 $emptyRequiredMasked.first().focus(); 523 } 524 } 525
526 if (hasError || (checkHtml5Validity && $form[0] && !$form[0].checkValidity())) { 527 if (addWaitingClass && $form[0]) { 528 $form[0].classList.remove('elementor-form-waiting'); 529 } 530 $submitBtn.data('clicked', false); 531 e.preventDefault(); 532 if (onBlocked) { 533 onBlocked($submitBtn, $form, e); 534 } 535 return; 536 } 537 538 if (addWaitingClass && $form[0]) { 539 $form[0].classList.remove('elementor-form-waiting'); 540 } 541 542 if (onAfterValid) { 543 onAfterValid($submitBtn, $form, e); 544 $submitBtn.data('clicked', false); 545 return; 546 } 547 548 if ($form[0] && typeof $form[0].requestSubmit === 'function') { 549 $form[0].requestSubmit(); 550 } 551 $submitBtn.data('clicked', false); 552 }, delay); 553 }); 554}; 555 556/** 557 * Atomic-style form submit guard: validate masks then allow one re-entrant submit. 558 * 559 * @param {jQuery} $ 560 * @param {Object} [opts] 561 */ 562CFKEF.bindMaskAllowOnceSubmitGuard = function ($, opts) { 563 opts = opts || {}; 564 if (CFKEF._maskAllowOnceSubmitGuardBound) { 565 return; 566 } 567 CFKEF._maskAllowOnceSubmitGuardBound = true; 568 569 var formMatch = typeof opts.formMatch === 'function' ? opts.formMatch : function () { return true; }; 570 var hasMaskInputs = typeof opts.hasMaskInputs === 'function' 571 ? opts.hasMaskInputs 572 : function ($form) { 573 return $form.find('input.fme-mask-input').not('.hide-fme-mask-input').length > 0; 574 }; 575 var validate = typeof opts.validate === 'function' ? opts.validate : function ($form) { 576 return CFKEF.maskValidationBlocksSubmit($, $form, opts); 577 }; 578 var clickSelector = opts.clickSelector || 'button[type="submit"]'; 579 var allowOnce = false; 580 581 $(document).on('submit', 'form', function (e) { 582 var formEl = this; 583 var $form = $(formEl); 584 if (!formMatch($form)) { 585 return; 586 } 587 if (!hasMaskInputs($form)) { 588 return; 589 } 590 591 if (allowOnce) { 592 allowOnce = false; 593 return; 594 } 595 596 if (validate($form)) { 597 e.preventDefault(); 598 e.stopImmediatePropagation(); 599 return; 600 } 601 602 e.preventDefault(); 603 e.stopImmediatePropagation(); 604 if (typeof formEl.requestSubmit === 'function') { 605 allowOnce = true; 606 formEl.requestSubmit(); 607 } else { 608 formEl.submit(); 609 } 610 }); 611 612 $(document).on('click', clickSelector, function (e) { 613 var $btn = $(this); 614 var $form = $btn.closest('form'); 615 if (!$form.length || !formMatch($form) || !hasMaskInputs($form)) { 616 return; 617 } 618 if (validate($form)) { 619 e.preventDefault(); 620 e.stopPropagation(); 621 } 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.