From c2b57e5ac35b689c8852e0a48852009ab5ebd59c Mon Sep 17 00:00:00 2001 From: Fabrizio Balliano Date: Mon, 21 Oct 2024 11:41:42 +0100 Subject: [PATCH] Rewrote js/prototype/validation.js in modern vanilla js removing prototypejs, moved to js/validation.js --- .github/codeql-config.yml | 2 +- app/code/core/Mage/Core/Model/App.php | 2 +- .../default/default/layout/dataflow.xml | 4 +- .../adminhtml/default/default/layout/main.xml | 2 +- .../default/template/forgotpassword.phtml | 2 +- .../default/default/template/login.phtml | 2 +- .../template/resetforgottenpassword.phtml | 2 +- .../frontend/base/default/layout/page.xml | 4 +- .../frontend/rwd/default/layout/page.xml | 4 +- .../default/default/template/page.phtml | 2 +- public/js/prototype/validation.js | 951 ------------------ public/js/validation.js | 903 +++++++++++++++++ public/js/varien/form.js | 15 +- 13 files changed, 923 insertions(+), 972 deletions(-) delete mode 100644 public/js/prototype/validation.js create mode 100644 public/js/validation.js diff --git a/.github/codeql-config.yml b/.github/codeql-config.yml index 15e4b0032..a793127f3 100644 --- a/.github/codeql-config.yml +++ b/.github/codeql-config.yml @@ -2,6 +2,6 @@ paths-ignore: - 'js/prototype/prototype.js' - 'js/mage/adminhtml/wysiwyg/tiny_mce/setup.js' - - 'js/prototype/validation.js' + - 'js/validation.js' - 'js/extjs/ext-tree.js' - '**/*.test.js' diff --git a/app/code/core/Mage/Core/Model/App.php b/app/code/core/Mage/Core/Model/App.php index 89b810253..a1de29614 100644 --- a/app/code/core/Mage/Core/Model/App.php +++ b/app/code/core/Mage/Core/Model/App.php @@ -54,7 +54,7 @@ class Mage_Core_Model_App * The absolute minimum of password length for all types of passwords * * With changing this value also need to change: - * 1. in `js/prototype/validation.js` declarations `var minLength = 7;` in two places; + * 1. in `js/validation.js` declarations `var minLength = 7;` in two places; * 2. in `app/code/core/Mage/Customer/etc/system.xml` * comments for fields `min_password_length` and `min_admin_password_length` * `Please enter a number 7 or greater in this field.`; diff --git a/app/design/adminhtml/default/default/layout/dataflow.xml b/app/design/adminhtml/default/default/layout/dataflow.xml index 91d137743..4c4b66e6c 100644 --- a/app/design/adminhtml/default/default/layout/dataflow.xml +++ b/app/design/adminhtml/default/default/layout/dataflow.xml @@ -18,7 +18,7 @@ - + @@ -32,7 +32,7 @@ - + diff --git a/app/design/adminhtml/default/default/layout/main.xml b/app/design/adminhtml/default/default/layout/main.xml index 9e8ea1dca..ef4baae18 100644 --- a/app/design/adminhtml/default/default/layout/main.xml +++ b/app/design/adminhtml/default/default/layout/main.xml @@ -43,7 +43,7 @@ Default layout, loads most of the pages Maho Admin - + diff --git a/app/design/adminhtml/default/default/template/forgotpassword.phtml b/app/design/adminhtml/default/default/template/forgotpassword.phtml index ee7d76ee7..990134df4 100644 --- a/app/design/adminhtml/default/default/template/forgotpassword.phtml +++ b/app/design/adminhtml/default/default/template/forgotpassword.phtml @@ -20,7 +20,7 @@ - + diff --git a/app/design/adminhtml/default/default/template/login.phtml b/app/design/adminhtml/default/default/template/login.phtml index dddf1c0ac..c646df50d 100644 --- a/app/design/adminhtml/default/default/template/login.phtml +++ b/app/design/adminhtml/default/default/template/login.phtml @@ -20,7 +20,7 @@ - + diff --git a/app/design/adminhtml/default/default/template/resetforgottenpassword.phtml b/app/design/adminhtml/default/default/template/resetforgottenpassword.phtml index 9cf7ed349..ed59c1f8f 100644 --- a/app/design/adminhtml/default/default/template/resetforgottenpassword.phtml +++ b/app/design/adminhtml/default/default/template/resetforgottenpassword.phtml @@ -20,7 +20,7 @@ - + diff --git a/app/design/frontend/base/default/layout/page.xml b/app/design/frontend/base/default/layout/page.xml index fda05b24c..82c7d8852 100644 --- a/app/design/frontend/base/default/layout/page.xml +++ b/app/design/frontend/base/default/layout/page.xml @@ -22,7 +22,7 @@ Default layout, loads most of the pages - + @@ -99,7 +99,7 @@ Default layout, loads most of the pages - + css/styles.css diff --git a/app/design/frontend/rwd/default/layout/page.xml b/app/design/frontend/rwd/default/layout/page.xml index 21cc6a243..f607897f0 100644 --- a/app/design/frontend/rwd/default/layout/page.xml +++ b/app/design/frontend/rwd/default/layout/page.xml @@ -22,7 +22,7 @@ - + @@ -131,7 +131,7 @@ - + css/styles.css diff --git a/app/design/install/default/default/template/page.phtml b/app/design/install/default/default/template/page.phtml index 92f5e7e93..a74d8b411 100644 --- a/app/design/install/default/default/template/page.phtml +++ b/app/design/install/default/default/template/page.phtml @@ -21,7 +21,7 @@ <?= Mage::helper('install')->__('Maho Installation Wizard') ?> - + diff --git a/public/js/prototype/validation.js b/public/js/prototype/validation.js deleted file mode 100644 index 532094023..000000000 --- a/public/js/prototype/validation.js +++ /dev/null @@ -1,951 +0,0 @@ -/* -* Really easy field validation with Prototype -* http://tetlaw.id.au/view/javascript/really-easy-field-validation -* Andrew Tetlaw -* Version 1.5.4.1 (2007-01-05) -* -* Copyright (c) 2007 Andrew Tetlaw -* Permission is hereby granted, free of charge, to any person -* obtaining a copy of this software and associated documentation -* files (the "Software"), to deal in the Software without -* restriction, including without limitation the rights to use, copy, -* modify, merge, publish, distribute, sublicense, and/or sell copies -* of the Software, and to permit persons to whom the Software is -* furnished to do so, subject to the following conditions: -* -* The above copyright notice and this permission notice shall be -* included in all copies or substantial portions of the Software. -* -* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS -* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN -* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -* SOFTWARE. -* -*/ -var Validator = Class.create(); - -Validator.prototype = { - initialize : function(className, error, test, options) { - if(typeof test == 'function'){ - this.options = $H(options); - this._test = test; - } else { - this.options = $H(test); - this._test = function(){return true}; - } - this.error = error || 'Validation failed.'; - this.className = className; - }, - test : function(v, elm) { - return (this._test(v,elm) && this.options.all(function(p){ - return Validator.methods[p.key] ? Validator.methods[p.key](v,elm,p.value) : true; - })); - } -} -Validator.methods = { - pattern : function(v,elm,opt) {return Validation.get('IsEmpty').test(v) || opt.test(v)}, - minLength : function(v,elm,opt) {return v.length >= opt}, - maxLength : function(v,elm,opt) {return v.length <= opt}, - min : function(v,elm,opt) {return v >= parseFloat(opt)}, - max : function(v,elm,opt) {return v <= parseFloat(opt)}, - notOneOf : function(v,elm,opt) {return $A(opt).all(function(value) { - return v != value; - })}, - oneOf : function(v,elm,opt) {return $A(opt).any(function(value) { - return v == value; - })}, - is : function(v,elm,opt) {return v == opt}, - isNot : function(v,elm,opt) {return v != opt}, - equalToField : function(v,elm,opt) {return v == $F(opt)}, - notEqualToField : function(v,elm,opt) {return v != $F(opt)}, - include : function(v,elm,opt) {return $A(opt).all(function(value) { - return Validation.get(value).test(v,elm); - })} -} - -var Validation = Class.create(); -Validation.defaultOptions = { - onSubmit : true, - stopOnFirst : false, - immediate : false, - focusOnError : true, - useTitles : false, - addClassNameToContainer: false, - containerClassName: '.input-box', - onFormValidate : function(result, form) {}, - onElementValidate : function(result, elm) {} -}; - -Validation.prototype = { - initialize : function(form, options){ - this.form = $(form); - if (!this.form) { - return; - } - this.options = Object.extend({ - onSubmit : Validation.defaultOptions.onSubmit, - stopOnFirst : Validation.defaultOptions.stopOnFirst, - immediate : Validation.defaultOptions.immediate, - focusOnError : Validation.defaultOptions.focusOnError, - useTitles : Validation.defaultOptions.useTitles, - onFormValidate : Validation.defaultOptions.onFormValidate, - onElementValidate : Validation.defaultOptions.onElementValidate - }, options || {}); - if(this.options.onSubmit) Event.observe(this.form,'submit',this.onSubmit.bind(this),false); - if(this.options.immediate) { - Form.getElements(this.form).each(function(input) { // Thanks Mike! - if (input.tagName.toLowerCase() == 'select') { - Event.observe(input, 'blur', this.onChange.bindAsEventListener(this)); - } - if (input.type.toLowerCase() == 'radio' || input.type.toLowerCase() == 'checkbox') { - Event.observe(input, 'click', this.onChange.bindAsEventListener(this)); - } else { - Event.observe(input, 'change', this.onChange.bindAsEventListener(this)); - } - }, this); - } - }, - onChange : function (ev) { - Validation.isOnChange = true; - Validation.validate(Event.element(ev),{ - useTitle : this.options.useTitles, - onElementValidate : this.options.onElementValidate - }); - Validation.isOnChange = false; - }, - onSubmit : function(ev){ - if(!this.validate()) Event.stop(ev); - }, - validate : function() { - var result = false; - var useTitles = this.options.useTitles; - var callback = this.options.onElementValidate; - try { - if(this.options.stopOnFirst) { - result = Form.getElements(this.form).all(function(elm) { - if (elm.hasClassName('local-validation') && !this.isElementInForm(elm, this.form)) { - return true; - } - return Validation.validate(elm,{useTitle : useTitles, onElementValidate : callback}); - }, this); - } else { - result = Form.getElements(this.form).collect(function(elm) { - if (elm.hasClassName('local-validation') && !this.isElementInForm(elm, this.form)) { - return true; - } - return Validation.validate(elm,{useTitle : useTitles, onElementValidate : callback}); - }, this).all(); - } - } catch (e) { - } - if(!result && this.options.focusOnError) { - try{ - Form.getElements(this.form).findAll(function(elm){return $(elm).hasClassName('validation-failed')}).first().focus() - } - catch(e){ - } - } - this.options.onFormValidate(result, this.form); - return result; - }, - reset : function() { - Form.getElements(this.form).each(Validation.reset); - }, - isElementInForm : function(elm, form) { - var domForm = elm.up('form'); - if (domForm == form) { - return true; - } - return false; - } -} - -Object.extend(Validation, { - validate : function(elm, options){ - options = Object.extend({ - useTitle : false, - onElementValidate : function(result, elm) {} - }, options || {}); - elm = $(elm); - - var cn = $w(elm.className); - return result = cn.all(function(value) { - var test = Validation.test(value,elm,options.useTitle); - options.onElementValidate(test, elm); - return test; - }); - }, - insertAdvice : function(elm, advice){ - var container = $(elm).up('.field-row'); - if(container){ - Element.insert(container, {after: advice}); - } else if (elm.up('td.value')) { - elm.up('td.value').insert({bottom: advice}); - } else if (elm.advaiceContainer && $(elm.advaiceContainer)) { - $(elm.advaiceContainer).update(advice); - } - else { - switch (elm.type.toLowerCase()) { - case 'checkbox': - case 'radio': - var p = elm.parentNode; - if(p) { - Element.insert(p, {'bottom': advice}); - } else { - Element.insert(elm, {'after': advice}); - } - break; - default: - Element.insert(elm, {'after': advice}); - } - } - }, - showAdvice : function(elm, advice, adviceName){ - if(!elm.advices){ - elm.advices = new Hash(); - } - else{ - elm.advices.each(function(pair){ - if (!advice || pair.value.id != advice.id) { - // hide non-current advice after delay - this.hideAdvice(elm, pair.value); - } - }.bind(this)); - } - elm.advices.set(adviceName, advice); - if(typeof Effect == 'undefined') { - advice.style.display = 'block'; - } else { - if(!advice._adviceAbsolutize) { - advice.show(); - } else { - Position.absolutize(advice); - advice.show(); - advice.setStyle({ - 'top':advice._adviceTop, - 'left': advice._adviceLeft, - 'width': advice._adviceWidth, - 'z-index': 1000 - }); - advice.addClassName('advice-absolute'); - } - } - }, - hideAdvice : function(elm, advice){ - if (advice != null) { - advice.hide(); - } - }, - updateCallback : function(elm, status) { - if (typeof elm.callbackFunction != 'undefined') { - eval(elm.callbackFunction+'(\''+elm.id+'\',\''+status+'\')'); - } - }, - ajaxError : function(elm, errorMsg) { - var name = 'validate-ajax'; - var advice = Validation.getAdvice(name, elm); - if (advice == null) { - advice = this.createAdvice(name, elm, false, errorMsg); - } - this.showAdvice(elm, advice, 'validate-ajax'); - this.updateCallback(elm, 'failed'); - - elm.addClassName('validation-failed'); - elm.addClassName('validate-ajax'); - if (Validation.defaultOptions.addClassNameToContainer && Validation.defaultOptions.containerClassName != '') { - var container = elm.up(Validation.defaultOptions.containerClassName); - if (container && this.allowContainerClassName(elm)) { - container.removeClassName('validation-passed'); - container.addClassName('validation-error'); - } - } - }, - allowContainerClassName: function (elm) { - if (elm.type == 'radio' || elm.type == 'checkbox') { - return elm.hasClassName('change-container-classname'); - } - - return true; - }, - test : function(name, elm, useTitle) { - var v = Validation.get(name); - var prop = '__advice'+name.camelize(); - try { - if(Validation.isVisible(elm) && !v.test($F(elm), elm)) { - //if(!elm[prop]) { - var advice = Validation.getAdvice(name, elm); - if (advice == null) { - advice = this.createAdvice(name, elm, useTitle); - } - this.showAdvice(elm, advice, name); - this.updateCallback(elm, 'failed'); - //} - elm[prop] = 1; - if (!elm.advaiceContainer) { - elm.removeClassName('validation-passed'); - elm.addClassName('validation-failed'); - } - - if (Validation.defaultOptions.addClassNameToContainer && Validation.defaultOptions.containerClassName != '') { - var container = elm.up(Validation.defaultOptions.containerClassName); - if (container && this.allowContainerClassName(elm)) { - container.removeClassName('validation-passed'); - container.addClassName('validation-error'); - } - } - return false; - } else { - var advice = Validation.getAdvice(name, elm); - this.hideAdvice(elm, advice); - this.updateCallback(elm, 'passed'); - elm[prop] = ''; - elm.removeClassName('validation-failed'); - elm.addClassName('validation-passed'); - if (Validation.defaultOptions.addClassNameToContainer && Validation.defaultOptions.containerClassName != '') { - var container = elm.up(Validation.defaultOptions.containerClassName); - if (container && !container.down('.validation-failed') && this.allowContainerClassName(elm)) { - if (!Validation.get('IsEmpty').test(elm.value) || !this.isVisible(elm)) { - container.addClassName('validation-passed'); - } else { - container.removeClassName('validation-passed'); - } - container.removeClassName('validation-error'); - } - } - return true; - } - } catch(e) { - throw(e) - } - }, - isVisible : function(elm) { - while(elm.tagName != 'BODY') { - if(!$(elm).visible()) return false; - elm = elm.parentNode; - } - return true; - }, - getAdvice : function(name, elm) { - return $('advice-' + name + '-' + Validation.getElmID(elm)) || $('advice-' + Validation.getElmID(elm)); - }, - createAdvice : function(name, elm, useTitle, customError) { - var v = Validation.get(name); - var errorMsg = useTitle ? ((elm && elm.title) ? elm.title : v.error) : v.error; - if (customError) { - errorMsg = customError; - } - try { - if (Translator){ - errorMsg = Translator.translate(errorMsg); - } - } - catch(e){} - - advice = '' - - - Validation.insertAdvice(elm, advice); - advice = Validation.getAdvice(name, elm); - if($(elm).hasClassName('absolute-advice')) { - var dimensions = $(elm).getDimensions(); - var originalPosition = Position.cumulativeOffset(elm); - - advice._adviceTop = (originalPosition[1] + dimensions.height) + 'px'; - advice._adviceLeft = (originalPosition[0]) + 'px'; - advice._adviceWidth = (dimensions.width) + 'px'; - advice._adviceAbsolutize = true; - } - return advice; - }, - getElmID : function(elm) { - return elm.id ? elm.id : elm.name; - }, - reset : function(elm) { - elm = $(elm); - var cn = $w(elm.className); - cn.each(function(value) { - var prop = '__advice'+value.camelize(); - if(elm[prop]) { - var advice = Validation.getAdvice(value, elm); - if (advice) { - advice.hide(); - } - elm[prop] = ''; - } - elm.removeClassName('validation-failed'); - elm.removeClassName('validation-passed'); - if (Validation.defaultOptions.addClassNameToContainer && Validation.defaultOptions.containerClassName != '') { - var container = elm.up(Validation.defaultOptions.containerClassName); - if (container) { - container.removeClassName('validation-passed'); - container.removeClassName('validation-error'); - } - } - }); - }, - add : function(className, error, test, options) { - var nv = {}; - nv[className] = new Validator(className, error, test, options); - Object.extend(Validation.methods, nv); - }, - addAllThese : function(validators) { - var nv = {}; - $A(validators).each(function(value) { - nv[value[0]] = new Validator(value[0], value[1], value[2], (value.length > 3 ? value[3] : {})); - }); - Object.extend(Validation.methods, nv); - }, - get : function(name) { - return Validation.methods[name] ? Validation.methods[name] : Validation.methods['_LikeNoIDIEverSaw_']; - }, - methods : { - '_LikeNoIDIEverSaw_' : new Validator('_LikeNoIDIEverSaw_','',{}) - } -}); - -Validation.add('IsEmpty', '', function(v) { - return (v == '' || (v == null) || (v.length == 0) || /^\s+$/.test(v)); -}); - -Validation.addAllThese([ - ['validate-no-html-tags', 'HTML tags are not allowed', function(v) { - return !/<(\/)?\w+/.test(v); - }], - ['validate-select', 'Please select an option.', function(v) { - return ((v != "none") && (v != null) && (v.length != 0)); - }], - ['required-entry', 'This is a required field.', function(v) { - return !Validation.get('IsEmpty').test(v); - }], - ['validate-number', 'Please enter a valid number in this field.', function(v) { - return Validation.get('IsEmpty').test(v) - || (!isNaN(parseNumber(v)) && /^\s*-?\d*(\.\d*)?\s*$/.test(v)); - }], - ['validate-number-range', 'The value is not within the specified range.', function(v, elm) { - if (Validation.get('IsEmpty').test(v)) { - return true; - } - - var numValue = parseNumber(v); - if (isNaN(numValue)) { - return false; - } - - var reRange = /^number-range-(-?[\d.,]+)?-(-?[\d.,]+)?$/, - result = true; - - $w(elm.className).each(function(name) { - var m = reRange.exec(name); - if (m) { - result = result - && (m[1] == null || m[1] == '' || numValue >= parseNumber(m[1])) - && (m[2] == null || m[2] == '' || numValue <= parseNumber(m[2])); - } - }); - - return result; - }], - ['validate-digits', 'Please use numbers only in this field. Please avoid spaces or other characters such as dots or commas.', function(v) { - return Validation.get('IsEmpty').test(v) || !/[^\d]/.test(v); - }], - ['validate-digits-range', 'The value is not within the specified range.', function(v, elm) { - if (Validation.get('IsEmpty').test(v)) { - return true; - } - - var numValue = parseNumber(v); - if (isNaN(numValue)) { - return false; - } - - var reRange = /^digits-range-(-?\d+)?-(-?\d+)?$/, - result = true; - - $w(elm.className).each(function(name) { - var m = reRange.exec(name); - if (m) { - result = result - && (m[1] == null || m[1] == '' || numValue >= parseNumber(m[1])) - && (m[2] == null || m[2] == '' || numValue <= parseNumber(m[2])); - } - }); - - return result; - }], - ['validate-hex-color', 'Please enter a valid hexadecimal color. For example ff0000.', function (v) { - return Validation.get('IsEmpty').test(v) || /^[a-f0-9]{6}$/i.test(v) - }], - ['validate-hex-color-hash', 'Please enter a valid hexadecimal color with hash. For example #ff0000.', function (v) { - return Validation.get('IsEmpty').test(v) || /^#[a-f0-9]{6}$/i.test(v) - }], - ['validate-alpha', 'Please use letters only (a-z or A-Z) in this field.', function (v) { - return Validation.get('IsEmpty').test(v) || /^[a-zA-Z]+$/.test(v) - }], - ['validate-code', 'Please use only letters (a-z), numbers (0-9) or underscore(_) in this field, first character should be a letter.', function (v) { - return Validation.get('IsEmpty').test(v) || /^[a-z]+[a-z0-9_]+$/.test(v) - }], - ['validate-code-event', 'Please do not use "event" for an attribute code.', function (v) { - return Validation.get('IsEmpty').test(v) || !/^(event)$/.test(v) - }], - ['validate-alphanum', 'Please use only letters (a-z or A-Z) or numbers (0-9) only in this field. No spaces or other characters are allowed.', function(v) { - return Validation.get('IsEmpty').test(v) || /^[a-zA-Z0-9]+$/.test(v) - }], - ['validate-alphanum-with-spaces', 'Please use only letters (a-z or A-Z), numbers (0-9) or spaces only in this field.', function(v) { - return Validation.get('IsEmpty').test(v) || /^[a-zA-Z0-9 ]+$/.test(v) - }], - ['validate-street', 'Please use only letters (a-z or A-Z) or numbers (0-9) or spaces and # only in this field.', function(v) { - return Validation.get('IsEmpty').test(v) || /^[ \w]{3,}([A-Za-z]\.)?([ \w]*\#\d+)?(\r\n| )[ \w]{3,}/.test(v) - }], - ['validate-phoneStrict', 'Please enter a valid phone number. For example (123) 456-7890 or 123-456-7890.', function(v) { - return Validation.get('IsEmpty').test(v) || /^(\()?\d{3}(\))?(-|\s)?\d{3}(-|\s)\d{4}$/.test(v); - }], - ['validate-phoneLax', 'Please enter a valid phone number. For example (123) 456-7890 or 123-456-7890.', function(v) { - return Validation.get('IsEmpty').test(v) || /^((\d[-. ]?)?((\(\d{3}\))|\d{3}))?[-. ]?\d{3}[-. ]?\d{4}$/.test(v); - }], - ['validate-fax', 'Please enter a valid fax number. For example (123) 456-7890 or 123-456-7890.', function(v) { - return Validation.get('IsEmpty').test(v) || /^(\()?\d{3}(\))?(-|\s)?\d{3}(-|\s)\d{4}$/.test(v); - }], - ['validate-date', 'Please enter a valid date.', function(v) { - var test = new Date(v); - return Validation.get('IsEmpty').test(v) || !isNaN(test); - }], - ['validate-date-range', 'The From Date value should be less than or equal to the To Date value.', function(v, elm) { - var m = /\bdate-range-(\w+)-(\w+)\b/.exec(elm.className); - if (!m || m[2] == 'to' || Validation.get('IsEmpty').test(v)) { - return true; - } - - var currentYear = new Date().getFullYear() + ''; - var normalizedTime = function(v) { - v = v.split(/[.\/]/); - if (v[2] && v[2].length < 4) { - v[2] = currentYear.substr(0, v[2].length) + v[2]; - } - return new Date(v.join('/')).getTime(); - }; - - var dependentElements = Element.select(elm.form, '.validate-date-range.date-range-' + m[1] + '-to'); - return !dependentElements.length || Validation.get('IsEmpty').test(dependentElements[0].value) - || normalizedTime(v) <= normalizedTime(dependentElements[0].value); - }], - ['validate-email', 'Please enter a valid email address. For example johndoe@domain.com.', function (v) { - //return Validation.get('IsEmpty').test(v) || /\w{1,}[@][\w\-]{1,}([.]([\w\-]{1,})){1,3}$/.test(v) - //return Validation.get('IsEmpty').test(v) || /^[\!\#$%\*/?|\^\{\}`~&\'\+\-=_a-z0-9][\!\#$%\*/?|\^\{\}`~&\'\+\-=_a-z0-9\.]{1,30}[\!\#$%\*/?|\^\{\}`~&\'\+\-=_a-z0-9]@([a-z0-9_-]{1,30}\.){1,5}[a-z]{2,4}$/i.test(v) - return Validation.get('IsEmpty').test(v) || /^([a-z0-9,!\#\$%&'\*\+\/=\?\^_`\{\|\}~-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z0-9,!\#\$%&'\*\+\/=\?\^_`\{\|\}~-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*@([a-z0-9-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z0-9-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*\.(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]){2,})$/i.test(v) - }], - ['validate-emailSender', 'Please use only visible characters and spaces.', function (v) { - return Validation.get('IsEmpty').test(v) || /^[\S ]+$/.test(v) - }], - ['validate-password', 'Please enter more characters or clean leading or trailing spaces.', function(v, elm) { - var pass=v.strip(); /*strip leading and trailing spaces*/ - var reMin = new RegExp(/^min-pass-length-[0-9]+$/); - var minLength = 7; - $w(elm.className).each(function(name, index) { - if (name.match(reMin)) { - minLength = name.split('-')[3]; - } - }); - return (!(v.length > 0 && v.length < minLength) && v.length == pass.length); - }], - ['validate-admin-password', 'Please enter more characters. Password should contain both numeric and alphabetic characters.', function(v, elm) { - var pass=v.strip(); - if (0 == pass.length) { - return true; - } - if (!(/[a-z]/i.test(v)) || !(/[0-9]/.test(v))) { - return false; - } - var reMin = new RegExp(/^min-admin-pass-length-[0-9]+$/); - var minLength = 7; - $w(elm.className).each(function(name, index) { - if (name.match(reMin)) { - minLength = name.split('-')[4]; - } - }); - return !(pass.length < minLength); - }], - ['validate-cpassword', 'Please make sure your passwords match.', function(v) { - var conf = $('confirmation') ? $('confirmation') : $$('.validate-cpassword')[0]; - var pass = false; - if ($('password')) { - pass = $('password'); - } - var passwordElements = $$('.validate-password'); - for (var i = 0; i < passwordElements.size(); i++) { - var passwordElement = passwordElements[i]; - if (passwordElement.up('form').id == conf.up('form').id) { - pass = passwordElement; - } - } - if ($$('.validate-admin-password').size()) { - pass = $$('.validate-admin-password')[0]; - } - return (pass.value == conf.value); - }], - ['validate-both-passwords', 'Please make sure your passwords match.', function(v, input) { - var dependentInput = $(input.form[input.name == 'password' ? 'confirmation' : 'password']), - isEqualValues = input.value == dependentInput.value; - - if (isEqualValues && dependentInput.hasClassName('validation-failed')) { - Validation.test(this.className, dependentInput); - } - - return dependentInput.value == '' || isEqualValues; - }], - ['validate-url', 'Please enter a valid URL. Protocol is required (http://, https:// or ftp://)', function (v) { - v = (v || '').replace(/^\s+/, '').replace(/\s+$/, ''); - return Validation.get('IsEmpty').test(v) || /^(https?|ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i.test(v) - }], - ['validate-clean-url', 'Please enter a valid URL. For example http://www.example.com or www.example.com', function (v) { - return Validation.get('IsEmpty').test(v) || /^(http|https|ftp):\/\/(([A-Z0-9][A-Z0-9_-]*)(\.[A-Z0-9][A-Z0-9_-]*)+.(com|org|net|dk|at|us|tv|info|uk|co.uk|biz|se)$)(:(\d+))?\/?/i.test(v) || /^(www)((\.[A-Z0-9][A-Z0-9_-]*)+.(com|org|net|dk|at|us|tv|info|uk|co.uk|biz|se)$)(:(\d+))?\/?/i.test(v) - }], - ['validate-identifier', 'Please enter a valid URL Key. For example "example-page", "example-page.html" or "anotherlevel/example-page".', function (v) { - return Validation.get('IsEmpty').test(v) || /^[a-z0-9][a-z0-9_\/-]+(\.[a-z0-9_-]+)?$/.test(v) - }], - ['validate-xml-identifier', 'Please enter a valid XML-identifier. For example something_1, block5, id-4.', function (v) { - return Validation.get('IsEmpty').test(v) || /^[A-Z][A-Z0-9_\/-]*$/i.test(v) - }], - ['validate-ssn', 'Please enter a valid social security number. For example 123-45-6789.', function(v) { - return Validation.get('IsEmpty').test(v) || /^\d{3}-?\d{2}-?\d{4}$/.test(v); - }], - ['validate-zip', 'Please enter a valid zip code. For example 90602 or 90602-1234.', function(v) { - return Validation.get('IsEmpty').test(v) || /(^\d{5}$)|(^\d{5}-\d{4}$)/.test(v); - }], - ['validate-zip-international', 'Please enter a valid zip code.', function(v) { - //return Validation.get('IsEmpty').test(v) || /(^[A-z0-9]{2,10}([\s]{0,1}|[\-]{0,1})[A-z0-9]{2,10}$)/.test(v); - return true; - }], - ['validate-date-au', 'Please use this date format: dd/mm/yyyy. For example 17/03/2006 for the 17th of March, 2006.', function(v) { - if(Validation.get('IsEmpty').test(v)) return true; - var regex = /^(\d{2})\/(\d{2})\/(\d{4})$/; - if(!regex.test(v)) return false; - var d = new Date(v.replace(regex, '$2/$1/$3')); - return ( parseInt(RegExp.$2, 10) == (1+d.getMonth()) ) && - (parseInt(RegExp.$1, 10) == d.getDate()) && - (parseInt(RegExp.$3, 10) == d.getFullYear() ); - }], - ['validate-currency-dollar', 'Please enter a valid $ amount. For example $100.00.', function(v) { - // [$]1[##][,###]+[.##] - // [$]1###+[.##] - // [$]0.## - // [$].## - return Validation.get('IsEmpty').test(v) || /^\$?\-?([1-9]{1}[0-9]{0,2}(\,[0-9]{3})*(\.[0-9]{0,2})?|[1-9]{1}\d*(\.[0-9]{0,2})?|0(\.[0-9]{0,2})?|(\.[0-9]{1,2})?)$/.test(v) - }], - ['validate-one-required', 'Please select one of the above options.', function (v,elm) { - var p = elm.parentNode; - var options = p.getElementsByTagName('INPUT'); - return $A(options).any(function(elm) { - return $F(elm); - }); - }], - ['validate-one-required-by-name', 'Please select one of the options.', function (v,elm) { - var inputs = $$('input[name="' + elm.name.replace(/([\\"])/g, '\\$1') + '"]'); - - var error = 1; - for(var i=0;i= 0; - }], - ['validate-zero-or-greater', 'Please enter a number 0 or greater in this field.', function(v) { - return Validation.get('validate-not-negative-number').test(v); - }], - ['validate-greater-than-zero', 'Please enter a number greater than 0 in this field.', function(v) { - if (Validation.get('IsEmpty').test(v)) { - return true; - } - v = parseNumber(v); - return !isNaN(v) && v > 0; - }], - - ['validate-special-price', 'The Special Price is active only when lower than the Actual Price.', function(v) { - var priceInput = $('price'); - var priceType = $('price_type'); - var priceValue = parseFloat(v); - - // Passed on non-related validators conditions (to not change order of validation) - if( - !priceInput - || !$F(priceInput) - || Validation.get('IsEmpty').test(v) - || !Validation.get('validate-number').test(v) - ) { - return true; - } - if(priceType) { - return (priceType && priceValue <= 99.99); - } - return priceValue < parseFloat($F(priceInput)); - }], - ['validate-state', 'Please select State/Province.', function(v) { - return (v!=0 || v == ''); - }], - ['validate-new-password', 'Please enter more characters or clean leading or trailing spaces.', function(v, elm) { - if (!Validation.get('validate-password').test(v, elm)) return false; - if (Validation.get('IsEmpty').test(v) && v != '') return false; - return true; - }], - ['validate-cc-number', 'Please enter a valid credit card number.', function(v, elm) { - // remove non-numerics - var ccTypeContainer = $(elm.id.substr(0,elm.id.indexOf('_cc_number')) + '_cc_type'); - if (ccTypeContainer && typeof Validation.creditCartTypes.get(ccTypeContainer.value) != 'undefined' - && Validation.creditCartTypes.get(ccTypeContainer.value)[2] == false) { - if (!Validation.get('IsEmpty').test(v) && Validation.get('validate-digits').test(v)) { - return true; - } else { - return false; - } - } - return validateCreditCard(v); - }], - ['validate-cc-type', 'Credit card number does not match credit card type.', function(v, elm) { - // remove credit card number delimiters such as "-" and space - elm.value = removeDelimiters(elm.value); - v = removeDelimiters(v); - - var ccTypeContainer = $(elm.id.substr(0,elm.id.indexOf('_cc_number')) + '_cc_type'); - if (!ccTypeContainer) { - return true; - } - var ccType = ccTypeContainer.value; - - if (typeof Validation.creditCartTypes.get(ccType) == 'undefined') { - return false; - } - - // Other card type or switch or solo card - if (Validation.creditCartTypes.get(ccType)[0]==false) { - return true; - } - - var validationFailure = false; - Validation.creditCartTypes.each(function (pair) { - if (pair.key == ccType) { - if (pair.value[0] && !v.match(pair.value[0])) { - validationFailure = true; - } - throw $break; - } - }); - - if (validationFailure) { - return false; - } - - if (ccTypeContainer.hasClassName('validation-failed') && Validation.isOnChange) { - Validation.validate(ccTypeContainer); - } - - return true; - }], - ['validate-cc-type-select', 'Card type does not match credit card number.', function(v, elm) { - var ccNumberContainer = $(elm.id.substr(0,elm.id.indexOf('_cc_type')) + '_cc_number'); - if (Validation.isOnChange && Validation.get('IsEmpty').test(ccNumberContainer.value)) { - return true; - } - if (Validation.get('validate-cc-type').test(ccNumberContainer.value, ccNumberContainer)) { - Validation.validate(ccNumberContainer); - } - return Validation.get('validate-cc-type').test(ccNumberContainer.value, ccNumberContainer); - }], - ['validate-cc-exp', 'Incorrect credit card expiration date.', function(v, elm) { - var ccExpMonth = v; - var ccExpYear = $(elm.id.substr(0,elm.id.indexOf('_expiration')) + '_expiration_yr').value; - var currentTime = new Date(); - var currentMonth = currentTime.getMonth() + 1; - var currentYear = currentTime.getFullYear(); - if (ccExpMonth < currentMonth && ccExpYear == currentYear) { - return false; - } - return true; - }], - ['validate-cc-cvn', 'Please enter a valid credit card verification number.', function(v, elm) { - var ccTypeContainer = $(elm.id.substr(0,elm.id.indexOf('_cc_cid')) + '_cc_type'); - if (!ccTypeContainer) { - return true; - } - var ccType = ccTypeContainer.value; - - if (typeof Validation.creditCartTypes.get(ccType) == 'undefined') { - return false; - } - - var re = Validation.creditCartTypes.get(ccType)[1]; - - if (v.match(re)) { - return true; - } - - return false; - }], - ['validate-ajax', '', function(v, elm) { return true; }], - ['validate-data', 'Please use only letters (a-z or A-Z), numbers (0-9) or underscore(_) in this field, first character should be a letter.', function (v) { - if(v != '' && v) { - return /^[A-Za-z]+[A-Za-z0-9_]+$/.test(v); - } - return true; - }], - ['validate-css-length', 'Please input a valid CSS-length. For example 100px or 77pt or 20em or .5ex or 50%.', function (v) { - if (v != '' && v) { - return /^[0-9\.]+(px|pt|em|ex|%)?$/.test(v) && (!(/\..*\./.test(v))) && !(/\.$/.test(v)); - } - return true; - }], - ['validate-length', 'Text length does not satisfy specified text range.', function (v, elm) { - var reMax = new RegExp(/^maximum-length-[0-9]+$/); - var reMin = new RegExp(/^minimum-length-[0-9]+$/); - var result = true; - $w(elm.className).each(function(name, index) { - if (name.match(reMax) && result) { - var length = name.split('-')[2]; - result = (v.length <= length); - } - if (name.match(reMin) && result && !Validation.get('IsEmpty').test(v)) { - var length = name.split('-')[2]; - result = (v.length >= length); - } - }); - return result; - }], - ['validate-percents', 'Please enter a number lower than 100.', {max:100}], - ['required-file', 'Please select a file', function(v, elm) { - var result = !Validation.get('IsEmpty').test(v); - if (result === false) { - ovId = elm.id + '_value'; - if ($(ovId)) { - result = !Validation.get('IsEmpty').test($(ovId).value); - } - } - return result; - }], - ['validate-cc-ukss', 'Please enter issue number or start date for switch/solo card type.', function(v,elm) { - var endposition; - - if (elm.id.match(/(.)+_cc_issue$/)) { - endposition = elm.id.indexOf('_cc_issue'); - } else if (elm.id.match(/(.)+_start_month$/)) { - endposition = elm.id.indexOf('_start_month'); - } else { - endposition = elm.id.indexOf('_start_year'); - } - - var prefix = elm.id.substr(0,endposition); - - var ccTypeContainer = $(prefix + '_cc_type'); - - if (!ccTypeContainer) { - return true; - } - var ccType = ccTypeContainer.value; - - if(['SS','SM','SO'].indexOf(ccType) == -1){ - return true; - } - - $(prefix + '_cc_issue').advaiceContainer - = $(prefix + '_start_month').advaiceContainer - = $(prefix + '_start_year').advaiceContainer - = $(prefix + '_cc_type_ss_div').down('ul li.adv-container'); - - var ccIssue = $(prefix + '_cc_issue').value; - var ccSMonth = $(prefix + '_start_month').value; - var ccSYear = $(prefix + '_start_year').value; - - var ccStartDatePresent = (ccSMonth && ccSYear) ? true : false; - - if (!ccStartDatePresent && !ccIssue){ - return false; - } - return true; - }] -]); - -function removeDelimiters (v) { - v = v.replace(/\s/g, ''); - v = v.replace(/\-/g, ''); - return v; -} - -function parseNumber(v) -{ - if (typeof v != 'string') { - return parseFloat(v); - } - - var isDot = v.indexOf('.'); - var isComa = v.indexOf(','); - - if (isDot != -1 && isComa != -1) { - if (isComa > isDot) { - v = v.replace('.', '').replace(',', '.'); - } - else { - v = v.replace(',', ''); - } - } - else if (isComa != -1) { - v = v.replace(',', '.'); - } - - return parseFloat(v); -} - -function validateCreditCard(input) { - const number = input.toString(); - const digits = number.replace(/\D/g, '').split('').map(Number); - let sum = 0; - let isSecond = false; - for (let i = digits.length - 1; i >= 0; i--) { - let digit = digits[i]; - if (isSecond) { - digit *= 2; - if (digit > 9) { - digit -= 9; - } - } - sum += digit; - isSecond = !isSecond; - } - return sum % 10 === 0; -} - -/** - * Hash with credit card types which can be simply extended in payment modules - * 0 - regexp for card number - * 1 - regexp for cvn - * 2 - check or not credit card number trough Luhn algorithm by - * function validateCreditCard which you can find above in this file - */ -Validation.creditCartTypes = $H({ - 'SO': [new RegExp('^(6334[5-9]([0-9]{11}|[0-9]{13,14}))|(6767([0-9]{12}|[0-9]{14,15}))$'), new RegExp('^([0-9]{3}|[0-9]{4})?$'), true], - 'VI': [new RegExp('^4[0-9]{12}([0-9]{3})?$'), new RegExp('^[0-9]{3}$'), true], - 'MC': [new RegExp('^(5[1-5][0-9]{14}|2(22[1-9][0-9]{12}|2[3-9][0-9]{13}|[3-6][0-9]{14}|7[0-1][0-9]{13}|720[0-9]{12}))$'), new RegExp('^[0-9]{3}$'), true], - 'AE': [new RegExp('^3[47][0-9]{13}$'), new RegExp('^[0-9]{4}$'), true], - 'DI': [new RegExp('^(30[0-5][0-9]{13}|3095[0-9]{12}|35(2[8-9][0-9]{12}|[3-8][0-9]{13})|36[0-9]{12}|3[8-9][0-9]{14}|6011(0[0-9]{11}|[2-4][0-9]{11}|74[0-9]{10}|7[7-9][0-9]{10}|8[6-9][0-9]{10}|9[0-9]{11})|62(2(12[6-9][0-9]{10}|1[3-9][0-9]{11}|[2-8][0-9]{12}|9[0-1][0-9]{11}|92[0-5][0-9]{10})|[4-6][0-9]{13}|8[2-8][0-9]{12})|6(4[4-9][0-9]{13}|5[0-9]{14}))$'), new RegExp('^[0-9]{3}$'), true], - 'JCB': [new RegExp('^(30[0-5][0-9]{13}|3095[0-9]{12}|35(2[8-9][0-9]{12}|[3-8][0-9]{13})|36[0-9]{12}|3[8-9][0-9]{14}|6011(0[0-9]{11}|[2-4][0-9]{11}|74[0-9]{10}|7[7-9][0-9]{10}|8[6-9][0-9]{10}|9[0-9]{11})|62(2(12[6-9][0-9]{10}|1[3-9][0-9]{11}|[2-8][0-9]{12}|9[0-1][0-9]{11}|92[0-5][0-9]{10})|[4-6][0-9]{13}|8[2-8][0-9]{12})|6(4[4-9][0-9]{13}|5[0-9]{14}))$'), new RegExp('^[0-9]{3,4}$'), true], - 'DICL': [new RegExp('^(30[0-5][0-9]{13}|3095[0-9]{12}|35(2[8-9][0-9]{12}|[3-8][0-9]{13})|36[0-9]{12}|3[8-9][0-9]{14}|6011(0[0-9]{11}|[2-4][0-9]{11}|74[0-9]{10}|7[7-9][0-9]{10}|8[6-9][0-9]{10}|9[0-9]{11})|62(2(12[6-9][0-9]{10}|1[3-9][0-9]{11}|[2-8][0-9]{12}|9[0-1][0-9]{11}|92[0-5][0-9]{10})|[4-6][0-9]{13}|8[2-8][0-9]{12})|6(4[4-9][0-9]{13}|5[0-9]{14}))$'), new RegExp('^[0-9]{3}$'), true], - 'SM': [new RegExp('(^(5[0678])[0-9]{11,18}$)|(^(6[^05])[0-9]{11,18}$)|(^(601)[^1][0-9]{9,16}$)|(^(6011)[0-9]{9,11}$)|(^(6011)[0-9]{13,16}$)|(^(65)[0-9]{11,13}$)|(^(65)[0-9]{15,18}$)|(^(49030)[2-9]([0-9]{10}$|[0-9]{12,13}$))|(^(49033)[5-9]([0-9]{10}$|[0-9]{12,13}$))|(^(49110)[1-2]([0-9]{10}$|[0-9]{12,13}$))|(^(49117)[4-9]([0-9]{10}$|[0-9]{12,13}$))|(^(49118)[0-2]([0-9]{10}$|[0-9]{12,13}$))|(^(4936)([0-9]{12}$|[0-9]{14,15}$))'), new RegExp('^([0-9]{3}|[0-9]{4})?$'), true], - 'OT': [false, new RegExp('^([0-9]{3}|[0-9]{4})?$'), false] -}); diff --git a/public/js/validation.js b/public/js/validation.js new file mode 100644 index 000000000..7216dfe35 --- /dev/null +++ b/public/js/validation.js @@ -0,0 +1,903 @@ +/* + * @copyright Copyright (c) 2007 Andrew Tetlaw + * @copyright Copyright (c) 2024 Maho (https://mahocommerce.com) + * @license https://opensource.org/license/mit.php + */ +class Validator { + constructor(className, error, test, options = {}) { + if (typeof test === 'function') { + this.options = new Map(Object.entries(options)); + this._test = test; + } else { + this.options = new Map(Object.entries(test)); + this._test = () => true; + } + this.error = error || 'Validation failed.'; + this.className = className; + } + + test(v, elm) { + return this._test(v, elm) && Array.from(this.options.entries()).every(([key, value]) => + Validator.methods[key] ? Validator.methods[key](v, elm, value) : true + ); + } + + static methods = { + pattern: (v, elm, opt) => Validator.get('IsEmpty').test(v) || opt.test(v), + minLength: (v, elm, opt) => v.length >= opt, + maxLength: (v, elm, opt) => v.length <= opt, + min: (v, elm, opt) => v >= parseFloat(opt), + max: (v, elm, opt) => v <= parseFloat(opt), + notOneOf: (v, elm, opt) => opt.every(value => v != value), + oneOf: (v, elm, opt) => opt.some(value => v == value), + is: (v, elm, opt) => v == opt, + isNot: (v, elm, opt) => v != opt, + equalToField: (v, elm, opt) => v == document.getElementById(opt)?.value, + notEqualToField: (v, elm, opt) => v != document.getElementById(opt)?.value, + include: (v, elm, opt) => opt.every(value => Validator.get(value).test(v, elm)) + }; + + static get(validatorName) { + return { + test: () => true + }; + } +} + +class Validation { + static defaultOptions = { + onSubmit: true, + stopOnFirst: false, + immediate: false, + focusOnError: true, + useTitles: false, + addClassNameToContainer: false, + containerClassName: '.input-box', + onFormValidate: (result, form) => {}, + onElementValidate: (result, elm) => {} + }; + + static methods = { + '_LikeNoIDIEverSaw_': new Validator('_LikeNoIDIEverSaw_', '', {}) + }; + + constructor(form, options = {}) { + this.form = form; + this.options = { ...Validation.defaultOptions, ...options }; + + if (this.options.onSubmit) { + this.form.addEventListener('submit', this.onSubmit.bind(this)); + } + + if (this.options.immediate) { + const elements = [...this.form.getElementsByTagName('input'), ...this.form.getElementsByTagName('select')]; + elements.forEach(input => { + if (input.tagName.toLowerCase() === 'select') { + input.addEventListener('blur', this.onChange.bind(this)); + } else if (['radio', 'checkbox'].includes(input.type.toLowerCase())) { + input.addEventListener('click', this.onChange.bind(this)); + } else { + input.addEventListener('change', this.onChange.bind(this)); + } + }); + } + } + + onChange(event) { + Validation.isOnChange = true; + Validation.validate(event.target, { + useTitle: this.options.useTitles, + onElementValidate: this.options.onElementValidate + }); + Validation.isOnChange = false; + } + + onSubmit(event) { + if (!this.validate()) event.preventDefault(); + } + + validate() { + let result = false; + const { useTitles, onElementValidate } = this.options; + + try { + const elements = [...this.form.elements]; + if (this.options.stopOnFirst) { + result = elements.every(elm => { + if (elm.classList.contains('local-validation') && !this.isElementInForm(elm, this.form)) { + return true; + } + return Validation.validate(elm, { useTitle: useTitles, onElementValidate }); + }); + } else { + result = elements.map(elm => { + if (elm.classList.contains('local-validation') && !this.isElementInForm(elm, this.form)) { + return true; + } + return Validation.validate(elm, { useTitle: useTitles, onElementValidate }); + }).every(Boolean); + } + } catch (e) { + console.error(e); + } + + if (!result && this.options.focusOnError) { + try { + this.form.querySelector('.validation-failed')?.focus(); + } catch (e) { + console.error(e); + } + } + + this.options.onFormValidate(result, this.form); + return result; + } + + reset() { + [...this.form.elements].forEach(Validation.reset); + } + + isElementInForm(elm, form) { + return elm.closest('form') === form; + } + + static validate(elm, options = {}) { + options = { + useTitle: false, + onElementValidate: (result, elm) => {}, + ...options + }; + + const cn = elm.className.split(' '); + return cn.every(value => { + const test = Validation.test(value, elm, options.useTitle); + options.onElementValidate(test, elm); + return test; + }); + } + + static insertAdvice(elm, advice) { + const container = elm.closest('.field-row'); + if (container) { + container.insertAdjacentHTML('afterend', advice); + } else if (elm.closest('td.value')) { + elm.closest('td.value').insertAdjacentHTML('beforeend', advice); + } else if (elm.advaiceContainer && document.getElementById(elm.advaiceContainer)) { + document.getElementById(elm.advaiceContainer).innerHTML = advice; + } else { + switch (elm.type.toLowerCase()) { + case 'checkbox': + case 'radio': + const p = elm.parentNode; + if (p) { + p.insertAdjacentHTML('beforeend', advice); + } else { + elm.insertAdjacentHTML('afterend', advice); + } + break; + default: + elm.insertAdjacentHTML('afterend', advice); + } + } + } + + static showAdvice(elm, advice, adviceName) { + if (!elm.advices) { + elm.advices = new Map(); + } else { + elm.advices.forEach((value, key) => { + if (!advice || value.id !== advice.id) { + Validation.hideAdvice(elm, value); + } + }); + } + elm.advices.set(adviceName, advice); + + if (typeof Effect === 'undefined') { + advice.style.display = 'block'; + } else { + if (!advice._adviceAbsolutize) { + advice.style.display = 'block'; + } else { + advice.style.position = 'absolute'; + advice.style.display = 'block'; + advice.style.top = advice._adviceTop; + advice.style.left = advice._adviceLeft; + advice.style.width = advice._adviceWidth; + advice.style.zIndex = '1000'; + advice.classList.add('advice-absolute'); + } + } + } + + static hideAdvice(elm, advice) { + if (advice != null) { + advice.style.display = 'none'; + } + } + + static updateCallback(elm, status) { + if (typeof elm.callbackFunction !== 'undefined') { + new Function(elm.callbackFunction + "('" + elm.id + "','" + status + "')")(); + } + } + + static ajaxError(elm, errorMsg) { + const name = 'validate-ajax'; + let advice = Validation.getAdvice(name, elm); + if (advice == null) { + advice = Validation.createAdvice(name, elm, false, errorMsg); + } + Validation.showAdvice(elm, advice, 'validate-ajax'); + Validation.updateCallback(elm, 'failed'); + + elm.classList.add('validation-failed', 'validate-ajax'); + if (Validation.defaultOptions.addClassNameToContainer && Validation.defaultOptions.containerClassName != '') { + const container = elm.closest(Validation.defaultOptions.containerClassName); + if (container && Validation.allowContainerClassName(elm)) { + container.classList.remove('validation-passed'); + container.classList.add('validation-error'); + } + } + } + + static allowContainerClassName(elm) { + if (elm.type == 'radio' || elm.type == 'checkbox') { + return elm.classList.contains('change-container-classname'); + } + return true; + } + + static test(name, elm, useTitle) { + const v = Validation.get(name); + const prop = '__advice' + name.replace(/-([a-z])/g, g => g[1].toUpperCase()); + try { + if (Validation.isVisible(elm) && !v.test(elm.value, elm)) { + let advice = Validation.getAdvice(name, elm); + if (advice == null) { + advice = Validation.createAdvice(name, elm, useTitle); + } + Validation.showAdvice(elm, advice, name); + Validation.updateCallback(elm, 'failed'); + + elm[prop] = 1; + if (!elm.advaiceContainer) { + elm.classList.remove('validation-passed'); + elm.classList.add('validation-failed'); + } + + if (Validation.defaultOptions.addClassNameToContainer && Validation.defaultOptions.containerClassName != '') { + const container = elm.closest(Validation.defaultOptions.containerClassName); + if (container && Validation.allowContainerClassName(elm)) { + container.classList.remove('validation-passed'); + container.classList.add('validation-error'); + } + } + return false; + } else { + const advice = Validation.getAdvice(name, elm); + Validation.hideAdvice(elm, advice); + Validation.updateCallback(elm, 'passed'); + elm[prop] = ''; + elm.classList.remove('validation-failed'); + elm.classList.add('validation-passed'); + if (Validation.defaultOptions.addClassNameToContainer && Validation.defaultOptions.containerClassName != '') { + const container = elm.closest(Validation.defaultOptions.containerClassName); + if (container && !container.querySelector('.validation-failed') && Validation.allowContainerClassName(elm)) { + if (!Validation.get('IsEmpty').test(elm.value) || !Validation.isVisible(elm)) { + container.classList.add('validation-passed'); + } else { + container.classList.remove('validation-passed'); + } + container.classList.remove('validation-error'); + } + } + return true; + } + } catch(e) { + throw(e); + } + } + + static isVisible(elm) { + while(elm.tagName != 'BODY') { + if(getComputedStyle(elm).display === 'none') return false; + elm = elm.parentNode; + } + return true; + } + + static getAdvice(name, elm) { + return document.getElementById('advice-' + name + '-' + Validation.getElmID(elm)) || document.getElementById('advice-' + Validation.getElmID(elm)); + } + + static createAdvice(name, elm, useTitle, customError) { + const v = Validation.get(name); + let errorMsg = useTitle ? ((elm && elm.title) ? elm.title : v.error) : v.error; + if (customError) { + errorMsg = customError; + } + try { + if (typeof Translator !== 'undefined'){ + errorMsg = Translator.translate(errorMsg); + } + } + catch(e){} + + const advice = ``; + + Validation.insertAdvice(elm, advice); + const adviceEl = Validation.getAdvice(name, elm); + if(elm.classList.contains('absolute-advice')) { + const dimensions = elm.getBoundingClientRect(); + const originalPosition = Validation.cumulativeOffset(elm); + + adviceEl._adviceTop = (originalPosition[1] + dimensions.height) + 'px'; + adviceEl._adviceLeft = (originalPosition[0]) + 'px'; + adviceEl._adviceWidth = (dimensions.width) + 'px'; + adviceEl._adviceAbsolutize = true; + } + return adviceEl; + } + + static getElmID(elm) { + return elm.id ? elm.id : elm.name; + } + + static reset(elm) { + const cn = elm.className.split(' '); + cn.forEach(value => { + const prop = '__advice' + value.replace(/-([a-z])/g, g => g[1].toUpperCase()); + if(elm[prop]) { + const advice = Validation.getAdvice(value, elm); + if (advice) { + advice.style.display = 'none'; + } + elm[prop] = ''; + } + elm.classList.remove('validation-failed', 'validation-passed'); + if (Validation.defaultOptions.addClassNameToContainer && Validation.defaultOptions.containerClassName != '') { + const container = elm.closest(Validation.defaultOptions.containerClassName); + if (container) { + container.classList.remove('validation-passed', 'validation-error'); + } + } + }); + } + + static add(className, error, test, options) { + Validation.methods[className] = new Validator(className, error, test, options); + } + + static addAllThese(validators) { + validators.forEach(value => { + Validation.methods[value[0]] = new Validator(value[0], value[1], value[2], (value.length > 3 ? value[3] : {})); + }); + } + + static get(name) { + return Validation.methods[name] ? Validation.methods[name] : Validation.methods['_LikeNoIDIEverSaw_']; + } + + static cumulativeOffset(element) { + let top = 0, left = 0; + do { + top += element.offsetTop || 0; + left += element.offsetLeft || 0; + element = element.offsetParent; + } while(element); + + return [left, top]; + } +} + +Validation.add('IsEmpty', '', function(v) { + return (v == '' || v == null || v.length == 0 || /^\s+$/.test(v)); +}); + +Validation.addAllThese([ + ['validate-no-html-tags', 'HTML tags are not allowed', v => !/<(\/)?\w+/.test(v)], + ['validate-select', 'Please select an option.', v => ((v != "none") && (v != null) && (v.length != 0))], + ['required-entry', 'This is a required field.', v => !Validation.get('IsEmpty').test(v)], + ['validate-number', 'Please enter a valid number in this field.', v => { + return Validation.get('IsEmpty').test(v) || (!isNaN(parseNumber(v)) && /^\s*-?\d*(\.\d*)?\s*$/.test(v)); + }], + ['validate-number-range', 'The value is not within the specified range.', (v, elm) => { + if (Validation.get('IsEmpty').test(v)) { + return true; + } + + const numValue = parseNumber(v); + if (isNaN(numValue)) { + return false; + } + + const reRange = /^number-range-(-?[\d.,]+)?-(-?[\d.,]+)?$/; + let result = true; + + elm.className.split(' ').forEach(name => { + const m = reRange.exec(name); + if (m) { + result = result + && (m[1] == null || m[1] == '' || numValue >= parseNumber(m[1])) + && (m[2] == null || m[2] == '' || numValue <= parseNumber(m[2])); + } + }); + + return result; + }], + ['validate-digits', 'Please use numbers only in this field. Please avoid spaces or other characters such as dots or commas.', v => { + return Validation.get('IsEmpty').test(v) || !/[^\d]/.test(v); + }], + ['validate-digits-range', 'The value is not within the specified range.', (v, elm) => { + if (Validation.get('IsEmpty').test(v)) { + return true; + } + + const numValue = parseNumber(v); + if (isNaN(numValue)) { + return false; + } + + const reRange = /^digits-range-(-?\d+)?-(-?\d+)?$/; + let result = true; + + elm.className.split(' ').forEach(name => { + const m = reRange.exec(name); + if (m) { + result = result + && (m[1] == null || m[1] == '' || numValue >= parseNumber(m[1])) + && (m[2] == null || m[2] == '' || numValue <= parseNumber(m[2])); + } + }); + + return result; + }], + ['validate-hex-color', 'Please enter a valid hexadecimal color. For example ff0000.', v => { + return Validation.get('IsEmpty').test(v) || /^[a-f0-9]{6}$/i.test(v); + }], + ['validate-hex-color-hash', 'Please enter a valid hexadecimal color with hash. For example #ff0000.', v => { + return Validation.get('IsEmpty').test(v) || /^#[a-f0-9]{6}$/i.test(v); + }], + ['validate-alpha', 'Please use letters only (a-z or A-Z) in this field.', v => { + return Validation.get('IsEmpty').test(v) || /^[a-zA-Z]+$/.test(v); + }], + ['validate-code', 'Please use only letters (a-z), numbers (0-9) or underscore(_) in this field, first character should be a letter.', v => { + return Validation.get('IsEmpty').test(v) || /^[a-z]+[a-z0-9_]+$/.test(v); + }], + ['validate-code-event', 'Please do not use "event" for an attribute code.', v => { + return Validation.get('IsEmpty').test(v) || !/^(event)$/.test(v); + }], + ['validate-alphanum', 'Please use only letters (a-z or A-Z) or numbers (0-9) only in this field. No spaces or other characters are allowed.', v => { + return Validation.get('IsEmpty').test(v) || /^[a-zA-Z0-9]+$/.test(v); + }], + ['validate-alphanum-with-spaces', 'Please use only letters (a-z or A-Z), numbers (0-9) or spaces only in this field.', v => { + return Validation.get('IsEmpty').test(v) || /^[a-zA-Z0-9 ]+$/.test(v); + }], + ['validate-street', 'Please use only letters (a-z or A-Z) or numbers (0-9) or spaces and # only in this field.', v => { + return Validation.get('IsEmpty').test(v) || /^[ \w]{3,}([A-Za-z]\.)?([ \w]*\#\d+)?(\r\n| )[ \w]{3,}/.test(v); + }], + ['validate-phoneStrict', 'Please enter a valid phone number. For example (123) 456-7890 or 123-456-7890.', v => { + return Validation.get('IsEmpty').test(v) || /^(\()?\d{3}(\))?(-|\s)?\d{3}(-|\s)\d{4}$/.test(v); + }], + ['validate-phoneLax', 'Please enter a valid phone number. For example (123) 456-7890 or 123-456-7890.', v => { + return Validation.get('IsEmpty').test(v) || /^((\d[-. ]?)?((\(\d{3}\))|\d{3}))?[-. ]?\d{3}[-. ]?\d{4}$/.test(v); + }], + ['validate-fax', 'Please enter a valid fax number. For example (123) 456-7890 or 123-456-7890.', v => { + return Validation.get('IsEmpty').test(v) || /^(\()?\d{3}(\))?(-|\s)?\d{3}(-|\s)\d{4}$/.test(v); + }], + ['validate-date', 'Please enter a valid date.', v => { + const test = new Date(v); + return Validation.get('IsEmpty').test(v) || !isNaN(test); + }], + ['validate-date-range', 'The From Date value should be less than or equal to the To Date value.', (v, elm) => { + const m = /\bdate-range-(\w+)-(\w+)\b/.exec(elm.className); + if (!m || m[2] == 'to' || Validation.get('IsEmpty').test(v)) { + return true; + } + + const currentYear = new Date().getFullYear() + ''; + const normalizedTime = function(v) { + v = v.split(/[.\/]/); + if (v[2] && v[2].length < 4) { + v[2] = currentYear.substr(0, v[2].length) + v[2]; + } + return new Date(v.join('/')).getTime(); + }; + + const dependentElements = elm.form.querySelectorAll('.validate-date-range.date-range-' + m[1] + '-to'); + return !dependentElements.length || Validation.get('IsEmpty').test(dependentElements[0].value) + || normalizedTime(v) <= normalizedTime(dependentElements[0].value); + }], + ['validate-email', 'Please enter a valid email address. For example johndoe@domain.com.', v => { + return Validation.get('IsEmpty').test(v) || /^([a-z0-9,!\#\$%&'\*\+\/=\?\^_`\{\|\}~-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z0-9,!\#\$%&'\*\+\/=\?\^_`\{\|\}~-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*@([a-z0-9-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z0-9-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*\.(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]){2,})$/i.test(v); + }], + ['validate-emailSender', 'Please use only visible characters and spaces.', v => { + return Validation.get('IsEmpty').test(v) || /^[\S ]+$/.test(v); + }], + ['validate-password', 'Please enter more characters or clean leading or trailing spaces.', (v, elm) => { + const pass = v.trim(); + const reMin = new RegExp(/^min-pass-length-[0-9]+$/); + let minLength = 7; + elm.className.split(' ').forEach(name => { + if (name.match(reMin)) { + minLength = parseInt(name.split('-')[3]); + } + }); + return (!(v.length > 0 && v.length < minLength) && v.length == pass.length); + }], + ['validate-admin-password', 'Please enter more characters. Password should contain both numeric and alphabetic characters.', (v, elm) => { + const pass = v.trim(); + if (0 == pass.length) { + return true; + } + if (!(/[a-z]/i.test(v)) || !(/[0-9]/.test(v))) { + return false; + } + const reMin = new RegExp(/^min-admin-pass-length-[0-9]+$/); + let minLength = 7; + elm.className.split(' ').forEach(name => { + if (name.match(reMin)) { + minLength = parseInt(name.split('-')[4]); + } + }); + return !(pass.length < minLength); + }], + ['validate-cpassword', 'Please make sure your passwords match.', v => { + const conf = document.getElementById('confirmation') || document.querySelector('.validate-cpassword'); + let pass = false; + if (document.getElementById('password')) { + pass = document.getElementById('password'); + } + const passwordElements = document.querySelectorAll('.validate-password'); + for (let i = 0; i < passwordElements.length; i++) { + const passwordElement = passwordElements[i]; + if (passwordElement.closest('form').id == conf.closest('form').id) { + pass = passwordElement; + } + } + if (document.querySelector('.validate-admin-password')) { + pass = document.querySelector('.validate-admin-password'); + } + return (pass.value == conf.value); + }], + ['validate-both-passwords', 'Please make sure your passwords match.', (v, input) => { + const dependentInput = input.form[input.name == 'password' ? 'confirmation' : 'password']; + const isEqualValues = input.value == dependentInput.value; + + if (isEqualValues && dependentInput.classList.contains('validation-failed')) { + Validation.test(input.className, dependentInput); + } + + return dependentInput.value == '' || isEqualValues; + }], + ['validate-url', 'Please enter a valid URL. Protocol is required (http://, https:// or ftp://)', v => { + v = (v || '').trim(); + return Validation.get('IsEmpty').test(v) || /^(https?|ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i.test(v); + }], + ['validate-clean-url', 'Please enter a valid URL. For example http://www.example.com or www.example.com', v => { + return Validation.get('IsEmpty').test(v) || /^(http|https|ftp):\/\/(([A-Z0-9][A-Z0-9_-]*)(\.[A-Z0-9][A-Z0-9_-]*)+.(com|org|net|dk|at|us|tv|info|uk|co.uk|biz|se)$)(:(\d+))?\/?/i.test(v) || /^(www)((\.[A-Z0-9][A-Z0-9_-]*)+.(com|org|net|dk|at|us|tv|info|uk|co.uk|biz|se)$)(:(\d+))?\/?/i.test(v); + }], + ['validate-identifier', 'Please enter a valid URL Key. For example "example-page", "example-page.html" or "anotherlevel/example-page".', v => { + return Validation.get('IsEmpty').test(v) || /^[a-z0-9][a-z0-9_\/-]+(\.[a-z0-9_-]+)?$/.test(v); + }], + ['validate-xml-identifier', 'Please enter a valid XML-identifier. For example something_1, block5, id-4.', v => { + return Validation.get('IsEmpty').test(v) || /^[A-Z][A-Z0-9_\/-]*$/i.test(v); + }], + ['validate-ssn', 'Please enter a valid social security number. For example 123-45-6789.', v => { + return Validation.get('IsEmpty').test(v) || /^\d{3}-?\d{2}-?\d{4}$/.test(v); + }], + ['validate-zip', 'Please enter a valid zip code. For example 90602 or 90602-1234.', v => { + return Validation.get('IsEmpty').test(v) || /(^\d{5}$)|(^\d{5}-\d{4}$)/.test(v); + }], + ['validate-zip-international', 'Please enter a valid zip code.', v => { + return true; + }], + ['validate-date-au', 'Please use this date format: dd/mm/yyyy. For example 17/03/2006 for the 17th of March, 2006.', v => { + if (Validation.get('IsEmpty').test(v)) return true; + const regex = /^(\d{2})\/(\d{2})\/(\d{4})$/; + if (!regex.test(v)) return false; + const [, day, month, year] = v.match(regex); + const d = new Date(year, month - 1, day); + return (parseInt(month, 10) == (1 + d.getMonth())) && + (parseInt(day, 10) == d.getDate()) && + (parseInt(year, 10) == d.getFullYear()); + }], + ['validate-currency-dollar', 'Please enter a valid $ amount. For example $100.00.', v => { + return Validation.get('IsEmpty').test(v) || /^\$?\-?([1-9]{1}[0-9]{0,2}(\,[0-9]{3})*(\.[0-9]{0,2})?|[1-9]{1}\d*(\.[0-9]{0,2})?|0(\.[0-9]{0,2})?|(\.[0-9]{1,2})?)$/.test(v); + }], + ['validate-one-required', 'Please select one of the above options.', (v, elm) => { + const options = elm.parentNode.querySelectorAll('INPUT'); + return Array.from(options).some(elm => elm.value); + }], + ['validate-one-required-by-name', 'Please select one of the options.', (v, elm) => { + const inputs = document.querySelectorAll(`input[name="${elm.name.replace(/([\\"])/g, '\\$1')}"]`); + let error = 1; + for (let i = 0; i < inputs.length; i++) { + if ((inputs[i].type == 'checkbox' || inputs[i].type == 'radio') && inputs[i].checked == true) { + error = 0; + } + if (Validation.isOnChange && (inputs[i].type == 'checkbox' || inputs[i].type == 'radio')) { + Validation.reset(inputs[i]); + } + } + return error === 0; + }], + ['validate-not-negative-number', 'Please enter a number 0 or greater in this field.', v => { + if (Validation.get('IsEmpty').test(v)) { + return true; + } + v = parseNumber(v); + return !isNaN(v) && v >= 0; + }], + ['validate-zero-or-greater', 'Please enter a number 0 or greater in this field.', v => { + return Validation.get('validate-not-negative-number').test(v); + }], + ['validate-greater-than-zero', 'Please enter a number greater than 0 in this field.', v => { + if (Validation.get('IsEmpty').test(v)) { + return true; + } + v = parseNumber(v); + return !isNaN(v) && v > 0; + }], + ['validate-special-price', 'The Special Price is active only when lower than the Actual Price.', v => { + const priceInput = document.getElementById('price'); + const priceType = document.getElementById('price_type'); + const priceValue = parseFloat(v); + + if (!priceInput || !priceInput.value || Validation.get('IsEmpty').test(v) || !Validation.get('validate-number').test(v)) { + return true; + } + if (priceType) { + return (priceType && priceValue <= 99.99); + } + return priceValue < parseFloat(priceInput.value); + }], + ['validate-state', 'Please select State/Province.', v => { + return (v != 0 || v == ''); + }], + ['validate-new-password', 'Please enter more characters or clean leading or trailing spaces.', (v, elm) => { + if (!Validation.get('validate-password').test(v, elm)) return false; + if (Validation.get('IsEmpty').test(v) && v != '') return false; + return true; + }], + ['validate-cc-number', 'Please enter a valid credit card number.', (v, elm) => { + const ccTypeContainer = document.getElementById(elm.id.substr(0, elm.id.indexOf('_cc_number')) + '_cc_type'); + if (ccTypeContainer && typeof Validation.creditCartTypes.get(ccTypeContainer.value) != 'undefined' + && Validation.creditCartTypes.get(ccTypeContainer.value)[2] == false) { + if (!Validation.get('IsEmpty').test(v) && Validation.get('validate-digits').test(v)) { + return true; + } else { + return false; + } + } + return validateCreditCard(v); + }], + ['validate-cc-type', 'Credit card number does not match credit card type.', (v, elm) => { + elm.value = removeDelimiters(elm.value); + v = removeDelimiters(v); + + const ccTypeContainer = document.getElementById(elm.id.substr(0, elm.id.indexOf('_cc_number')) + '_cc_type'); + if (!ccTypeContainer) { + return true; + } + const ccType = ccTypeContainer.value; + + if (typeof Validation.creditCartTypes.get(ccType) == 'undefined') { + return false; + } + + if (Validation.creditCartTypes.get(ccType)[0] == false) { + return true; + } + + let validationFailure = false; + Validation.creditCartTypes.forEach((value, key) => { + if (key == ccType) { + if (value[0] && !v.match(value[0])) { + validationFailure = true; + } + return; + } + }); + + if (validationFailure) { + return false; + } + + if (ccTypeContainer.classList.contains('validation-failed') && Validation.isOnChange) { + Validation.validate(ccTypeContainer); + } + + return true; + }], + ['validate-cc-type-select', 'Card type does not match credit card number.', (v, elm) => { + const ccNumberContainer = document.getElementById(elm.id.substr(0, elm.id.indexOf('_cc_type')) + '_cc_number'); + if (Validation.isOnChange && Validation.get('IsEmpty').test(ccNumberContainer.value)) { + return true; + } + if (Validation.get('validate-cc-type').test(ccNumberContainer.value, ccNumberContainer)) { + Validation.validate(ccNumberContainer); + } + return Validation.get('validate-cc-type').test(ccNumberContainer.value, ccNumberContainer); + }], + ['validate-cc-exp', 'Incorrect credit card expiration date.', (v, elm) => { + const ccExpMonth = v; + const ccExpYear = document.getElementById(elm.id.substr(0, elm.id.indexOf('_expiration')) + '_expiration_yr').value; + const currentTime = new Date(); + const currentMonth = currentTime.getMonth() + 1; + const currentYear = currentTime.getFullYear(); + if (ccExpMonth < currentMonth && ccExpYear == currentYear) { + return false; + } + return true; + }], + ['validate-cc-cvn', 'Please enter a valid credit card verification number.', (v, elm) => { + const ccTypeContainer = document.getElementById(elm.id.substr(0, elm.id.indexOf('_cc_cid')) + '_cc_type'); + if (!ccTypeContainer) { + return true; + } + const ccType = ccTypeContainer.value; + + if (typeof Validation.creditCartTypes.get(ccType) == 'undefined') { + return false; + } + + const re = Validation.creditCartTypes.get(ccType)[1]; + + if (v.match(re)) { + return true; + } + + return false; + }], + ['validate-ajax', '', v => true], + ['validate-data', 'Please use only letters (a-z or A-Z), numbers (0-9) or underscore(_) in this field, first character should be a letter.', v => { + if (v != '' && v) { + return /^[A-Za-z]+[A-Za-z0-9_]+$/.test(v); + } + return true; + }], + ['validate-css-length', 'Please input a valid CSS-length. For example 100px or 77pt or 20em or .5ex or 50%.', v => { + if (v != '' && v) { + return /^[0-9\.]+(px|pt|em|ex|%)?$/.test(v) && (!(/\..*\./.test(v))) && !(/\.$/.test(v)); + } + return true; + }], + ['validate-length', 'Text length does not satisfy specified text range.', (v, elm) => { + const reMax = new RegExp(/^maximum-length-[0-9]+$/); + const reMin = new RegExp(/^minimum-length-[0-9]+$/); + let result = true; + elm.className.split(' ').forEach(name => { + if (name.match(reMax) && result) { + const length = parseInt(name.split('-')[2]); + result = (v.length <= length); + } + if (name.match(reMin) && result && !Validation.get('IsEmpty').test(v)) { + const length = parseInt(name.split('-')[2]); + result = (v.length >= length); + } + }); + return result; + }], + ['validate-percents', 'Please enter a number lower than 100.', { max: 100 }], + ['required-file', 'Please select a file', (v, elm) => { + let result = !Validation.get('IsEmpty').test(v); + if (result === false) { + const ovId = elm.id + '_value'; + const ovElm = document.getElementById(ovId); + if (ovElm) { + result = !Validation.get('IsEmpty').test(ovElm.value); + } + } + return result; + }], + ['validate-cc-ukss', 'Please enter issue number or start date for switch/solo card type.', (v, elm) => { + let endposition; + + if (elm.id.match(/(.)+_cc_issue$/)) { + endposition = elm.id.indexOf('_cc_issue'); + } else if (elm.id.match(/(.)+_start_month$/)) { + endposition = elm.id.indexOf('_start_month'); + } else { + endposition = elm.id.indexOf('_start_year'); + } + + const prefix = elm.id.substr(0, endposition); + + const ccTypeContainer = document.getElementById(prefix + '_cc_type'); + + if (!ccTypeContainer) { + return true; + } + const ccType = ccTypeContainer.value; + + if (['SS', 'SM', 'SO'].indexOf(ccType) == -1) { + return true; + } + + document.getElementById(prefix + '_cc_issue').advaiceContainer + = document.getElementById(prefix + '_start_month').advaiceContainer + = document.getElementById(prefix + '_start_year').advaiceContainer + = document.getElementById(prefix + '_cc_type_ss_div').querySelector('ul li.adv-container'); + + const ccIssue = document.getElementById(prefix + '_cc_issue').value; + const ccSMonth = document.getElementById(prefix + '_start_month').value; + const ccSYear = document.getElementById(prefix + '_start_year').value; + + const ccStartDatePresent = (ccSMonth && ccSYear) ? true : false; + + if (!ccStartDatePresent && !ccIssue) { + return false; + } + return true; + }] +]); + +function removeDelimiters (v) { + v = v.replace(/\s/g, ''); + v = v.replace(/\-/g, ''); + return v; +} + +function parseNumber(v) +{ + if (typeof v != 'string') { + return parseFloat(v); + } + + var isDot = v.indexOf('.'); + var isComa = v.indexOf(','); + + if (isDot != -1 && isComa != -1) { + if (isComa > isDot) { + v = v.replace('.', '').replace(',', '.'); + } + else { + v = v.replace(',', ''); + } + } + else if (isComa != -1) { + v = v.replace(',', '.'); + } + + return parseFloat(v); +} + +function validateCreditCard(input) { + const number = input.toString(); + const digits = number.replace(/\D/g, '').split('').map(Number); + let sum = 0; + let isSecond = false; + for (let i = digits.length - 1; i >= 0; i--) { + let digit = digits[i]; + if (isSecond) { + digit *= 2; + if (digit > 9) { + digit -= 9; + } + } + sum += digit; + isSecond = !isSecond; + } + return sum % 10 === 0; +} + +/** + * Hash with credit card types which can be simply extended in payment modules + * 0 - regexp for card number + * 1 - regexp for cvn + * 2 - check or not credit card number trough Luhn algorithm by + * function validateCreditCard which you can find above in this file + */ +Validation.creditCartTypes = $H({ + 'SO': [new RegExp('^(6334[5-9]([0-9]{11}|[0-9]{13,14}))|(6767([0-9]{12}|[0-9]{14,15}))$'), new RegExp('^([0-9]{3}|[0-9]{4})?$'), true], + 'VI': [new RegExp('^4[0-9]{12}([0-9]{3})?$'), new RegExp('^[0-9]{3}$'), true], + 'MC': [new RegExp('^(5[1-5][0-9]{14}|2(22[1-9][0-9]{12}|2[3-9][0-9]{13}|[3-6][0-9]{14}|7[0-1][0-9]{13}|720[0-9]{12}))$'), new RegExp('^[0-9]{3}$'), true], + 'AE': [new RegExp('^3[47][0-9]{13}$'), new RegExp('^[0-9]{4}$'), true], + 'DI': [new RegExp('^(30[0-5][0-9]{13}|3095[0-9]{12}|35(2[8-9][0-9]{12}|[3-8][0-9]{13})|36[0-9]{12}|3[8-9][0-9]{14}|6011(0[0-9]{11}|[2-4][0-9]{11}|74[0-9]{10}|7[7-9][0-9]{10}|8[6-9][0-9]{10}|9[0-9]{11})|62(2(12[6-9][0-9]{10}|1[3-9][0-9]{11}|[2-8][0-9]{12}|9[0-1][0-9]{11}|92[0-5][0-9]{10})|[4-6][0-9]{13}|8[2-8][0-9]{12})|6(4[4-9][0-9]{13}|5[0-9]{14}))$'), new RegExp('^[0-9]{3}$'), true], + 'JCB': [new RegExp('^(30[0-5][0-9]{13}|3095[0-9]{12}|35(2[8-9][0-9]{12}|[3-8][0-9]{13})|36[0-9]{12}|3[8-9][0-9]{14}|6011(0[0-9]{11}|[2-4][0-9]{11}|74[0-9]{10}|7[7-9][0-9]{10}|8[6-9][0-9]{10}|9[0-9]{11})|62(2(12[6-9][0-9]{10}|1[3-9][0-9]{11}|[2-8][0-9]{12}|9[0-1][0-9]{11}|92[0-5][0-9]{10})|[4-6][0-9]{13}|8[2-8][0-9]{12})|6(4[4-9][0-9]{13}|5[0-9]{14}))$'), new RegExp('^[0-9]{3,4}$'), true], + 'DICL': [new RegExp('^(30[0-5][0-9]{13}|3095[0-9]{12}|35(2[8-9][0-9]{12}|[3-8][0-9]{13})|36[0-9]{12}|3[8-9][0-9]{14}|6011(0[0-9]{11}|[2-4][0-9]{11}|74[0-9]{10}|7[7-9][0-9]{10}|8[6-9][0-9]{10}|9[0-9]{11})|62(2(12[6-9][0-9]{10}|1[3-9][0-9]{11}|[2-8][0-9]{12}|9[0-1][0-9]{11}|92[0-5][0-9]{10})|[4-6][0-9]{13}|8[2-8][0-9]{12})|6(4[4-9][0-9]{13}|5[0-9]{14}))$'), new RegExp('^[0-9]{3}$'), true], + 'SM': [new RegExp('(^(5[0678])[0-9]{11,18}$)|(^(6[^05])[0-9]{11,18}$)|(^(601)[^1][0-9]{9,16}$)|(^(6011)[0-9]{9,11}$)|(^(6011)[0-9]{13,16}$)|(^(65)[0-9]{11,13}$)|(^(65)[0-9]{15,18}$)|(^(49030)[2-9]([0-9]{10}$|[0-9]{12,13}$))|(^(49033)[5-9]([0-9]{10}$|[0-9]{12,13}$))|(^(49110)[1-2]([0-9]{10}$|[0-9]{12,13}$))|(^(49117)[4-9]([0-9]{10}$|[0-9]{12,13}$))|(^(49118)[0-2]([0-9]{10}$|[0-9]{12,13}$))|(^(4936)([0-9]{12}$|[0-9]{14,15}$))'), new RegExp('^([0-9]{3}|[0-9]{4})?$'), true], + 'OT': [false, new RegExp('^([0-9]{3}|[0-9]{4})?$'), false] +}); diff --git a/public/js/varien/form.js b/public/js/varien/form.js index 4e24c3cfc..c9cae8bff 100644 --- a/public/js/varien/form.js +++ b/public/js/varien/form.js @@ -1,11 +1,12 @@ /** * Maho * - * @category Varien - * @package js - * @copyright Copyright (c) 2006-2020 Magento, Inc. (https://magento.com) - * @copyright Copyright (c) 2022-2023 The OpenMage Contributors (https://openmage.org) - * @license https://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) + * @category Varien + * @package js + * @copyright Copyright (c) 2006-2020 Magento, Inc. (https://magento.com) + * @copyright Copyright (c) 2022-2023 The OpenMage Contributors (https://openmage.org) + * @copyright Copyright (c) 2024 Maho (https://mahocommerce.com) + * @license https://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) */ VarienForm = Class.create(); @@ -18,9 +19,7 @@ VarienForm.prototype = { this.cache = $A(); this.currLoader = false; this.currDataIndex = false; - if (typeof Validation === 'function') { - this.validator = new Validation(this.form); - } + this.validator = new Validation(this.form); this.elementFocus = this.elementOnFocus.bindAsEventListener(this); this.elementBlur = this.elementOnBlur.bindAsEventListener(this); this.childLoader = this.onChangeChildLoad.bindAsEventListener(this);