
// this function only validates syntax.
// if it's not alert a message. otherwise send test e-mail to customer. if mail gets
// throught then e-mail is valid.
// why?
// 1. domain does not exist
// 2. no such e-mail - dns cannot find the address
// 3. customer's account with spam guard that bounces back the mail

// according to RFC 822 standard http://www.ietf.org/rfc/rfc0822.txt

// specials: <>[]()\",.;:@
// CTLs: ASCII control character \0 - \37 or 0. - 31.
// atom: (any char except specials, space ant CTLs)+
// word: "anything" | atom
// dtext: any char except []\CR (CR = ASCII \15 or 13.)
// quoted-char: \char. back slash may quote any char.
// comment: anything with in parenthesis. can apear anywhere in the address

// address: mailbox | group
// group: word+:[mailbox(, mailbox)*]opt;
// mailbox: addr-soec | word+ route-addr
// route-addr: <[route]opt addr-spec>
// route: @domain(,@domain)*:
// addr-spec: local-part@domain
// local-part: word(.word)*
// domain: sub-domain(.sub-domain)*
// sub-domain: domain-ref | domain-literal
// domain-ref: word
// domain literal: [(dtext |  quoted-char)*] // a name of a machine or ip address

// in this function the following grammer is validated
// address: local-part@domain
// local-part: word(.word)*
// domain: sub-domain(.sub-domain)* | [ip address]
// sub-domain: word
// leaving the few poor persons that have different e-mail addresses to find another service
function isValidEmail(str)
{
	if (!str)
		return -1
	// check syntax
	var reg = /\(.+\)/g // matches comments = anything within parnethesis
	str = str.replace(reg,'') // cut out comments
	reg = /^([^\(\)<>\[\],:;\\\.@\s\"]+|\".+\")(\.([^\(\)<>\[\],:;\\\.@\s\"]+|\".+\"))*@((\[[0-9]{1,3}(\.[0-9]{1,3}){3}\])|(([^\(\)<>\[\],:;\\\.@\s\"]+|\".+\")(\.([^\(\)<>\[\],:;\\\.@\s\"]+|\".+\"))*))$/
	if (!reg.test(str)) // wrong syntax
		return -2
	// validate domain
	var domain = str.split('@')[1]
	if (domain.charAt(0) == '[') {// domain is an ip
		var d = domain.substring(1,domain.length - 1).split('.')
		if (Number(d[0]) == 0 && Number(d[1]) == 0 && Number(d[3]) == 0 && Number(d[3]) == 0 ||
			Number(d[0]) >= 255 && Number(d[1]) >= 255 && Number(d[2]) >= 255 && Number(d[3]) >= 255)
			return -3
	}
	// else if (){} // check domain name - doesn't worth the effort
	return 0
}

// function called from input field on change/blur/propertychange(IE) events
// and deletes any non digit character
function digitsOnly(obj)
{
	var str = obj.value
	var reg = /\D/g
	if (reg.test(str))
		obj.value = str.replace(reg,'')
}

// same but called on keypress/keyup/keydown events
// function calling syntax is: onkeypress="return numbersOnlyOnKeyPress(event)"
function digitsOnlyOnKeyPress(event)
{
	var ch = event.keyCode? event.keyCode: event.which
	if ((ch < '0'.charCodeAt(0) || ch > '9'.charCodeAt(0)) && ch != 0 && ch != 8 && ch != 13)
		return false
	return true
}

// function called from input field on change/blur/propertychange(IE) events
// and keeps the input's value a legal float number
function floatOnly(obj)
{
	var p
	var str = obj.value, strTail
	var reg = /^\d+\.{0,1}\d*$/g
	if (!reg.test(str)) {
		str = str.replace(/[^\d\.]/g,'') // remove ilegal characters
		if ((p = obj.value.indexOf('.')) != -1 && p < str.length) { // remove all '.' except 1st one
			strTail = str.substring(p + 1,str.length)
			strTail = strTail.split('.').join('')
			str = str.substring(0,p + 1) + strTail
		}
		obj.value = str
	}
}

// same but called on keypress/keyup/keydown events
// function calling syntax is: onkeypress="return floatOnlyOnKeyPress(obj,event)"
function floatOnlyOnKeyPress(obj,event)
{
	// NOTE: '.'.charCodeAt(0) == 46
	var ch = event.keyCode? event.keyCode: event.which
	if ((ch < '0'.charCodeAt(0) || ch > '9'.charCodeAt(0)) && 
		(ch != '.'.charCodeAt(0) || obj.value.indexOf('.') != -1) && 
		ch != 0 && ch != 8 && ch != 13)
		return false
	return true
}

// function called from input field on change/blur/propertychange(IE) events
// and deletes any non alpha numeric letters
function alphaNumericOnly(obj)
{
	var str = obj.value
	var reg = /[^\w]/g
	if (reg.test(str))
		obj.value = str.replace(reg,'')
}

// same but called on keypress/keyup/keydown events
// function calling syntax is: onkeypress="return alphaNumericOnlyOnKeyPress(obj,event)"
function alphaNumericOnlyOnKeyPress(event)
{
	var ch = event.keyCode? event.keyCode: event.which
	var reg = /^\w$/
	return reg.test(String.fromCharCode(ch))
}

// function called from input field on change/blur/propertychange(IE) events
// and deletes any non english letters
function englishLettersOnly(obj)
{
	var str = obj.value;
	var reg = /[^a-zA-Z]/g
	if (reg.test(str))
		obj.value = str.replace(reg,'')
}

// validate any integer only field
function isInteger(str)
{
	var reg = /\D/ // look for first non-digit character
	return reg.test(str) != true
}

// validate any float only field
function isFloat(str)
{
	var reg = /^\d+(\.\d+){0,1}$/
	return reg.test(str)
}

// validate any numeric field
function isNumeric(str)
{
	if (str.length == 0)
		return false
	return !isNaN(str)
}

// all english letters
function isAlphaEnglish(str)
{
	var reg = /^[a-zA-Z]+$/
	return reg.test(str)
}

// any legal word ([a-zA-Z0-9_])
function isAlphaNumeric(str)
{
	var reg = /^\w+$/
	return reg.test(str)
}

// all english letters and digits
function isAlphaNumericEnglish(str)
{
	var reg = /^[a-zA-Z0-9]+$/
	return reg.test(str)
}

// all hebrew letters
function isAlphaHebrew(str)
{
	//var reg = /^[\u5256-\u5396]+$/ // for utf-8 charset
	var reg = /^[à-ú]+$/ // for ASCII charset
	return reg.test(str)
}

// all hebrew letters and digits
function isAlphaNumericHebrew(str)
{
	// var reg = /^[\u5256-\u5396\d]+$/ // for utf-8 charset
	var reg = /^[à-ú\d]+$/ // for ASCII charset
	return reg.test(str)
}

// validate name field
// 		parameters:
//			str - string
//			lang - language ('en' for english or 'he' for hebrew)
function isName(str,lang)
{
	// remove non alpha numeric legal charcters: '\'', '"', '_', '-', '.', ' '
	str = str.replace(/['"_\-\. ]/g,'') //'
	
	// check the remaining characters
	if (!lang) lang = 'en'
	switch(lang) {
	case 'en':	// english alpha numeric validation
		return isAlphaNumericEnglish(str)
	case 'he':	// hebrew alpha numeric validation
		return isAlphaNumericHebrew(str)
	default:	// english alpha numeric validation
		return isAlphaNumericEnglish(str)
	}
}

// validate phone number - with no area code
function isValidPhone(str)
{
	var reg = /^\d{7}$/
	return reg.test(str)
}

// validate mobile phone number - with no area code
function isValidMobile(str)
{
	var reg = /^\d{6}$/
	return reg.test(str)
}

// validate mobile zip code
function isValidZipCode(str)
{
	var reg = /^\d{5}$/
	return reg.test(str)
}

// validate id number
function isValidId(str)
{
	// check syntax - 6-9 digits
	var reg = /^\d{6,9}$/
	if (!reg.test(str))
		return false
	
	// validate using validation algorithm
	
	var curDigit
	var weight
	var product
	var result = 0
	
	for (i = 9 - str.length; i < 9; i++) {
		curDigit = Number(str.charAt(i - 9 + str.length)) // get current digit
		weight = i % 2 + 1 // calculate current weight
		product = curDigit * weight // multiply curDigit by current weight
		// sum the digits of each product (if product = 10x+y then product = x+y)
		result += Math.floor(product / 10) + product % 10
	}
	
	// the id number is legal if the result sum is 0 modulu 10
	return result % 10 == 0
}

// validate credit card number string (except Isracart number)
// a credit card number is a number of 11-19 digits.
// the validation algorithm is the same as in the israeli id validation
function isValidCreditCardNumber(str)
{
	// check syntax - 11-19 digits
	var reg = /^\d{11,19}$/
	if (!reg.test(str))
		return false
	
	// validate using validation algorithm
	
	var creditNum = Number(str)
	var i, lsd /* least significant digit */, weight, product
	var result = 0
	
	for (i = 0; i < str.length; i++) {
		lsd = creditNum % 10
		creditNum = (creditNum - lsd)/10
		weight = i % 2 + 1
		// get the product of the lsd and its corresponding weight
		product = lsd * weight
		// sum the digits of each result (if product = 10x+y then result += x+y)
		result += Math.floor(product/10) + product % 10
	}
	
	// the credit card number is legal if the result sum is 0 modulu 10
	return result % 10 == 0
}

// validate Isracart credit card number string
// an Isracrt credit card number is of 8-9 digits and its validation 
// is different than the standard
function isValidIsracartNumber(str)
{
	// check syntax - 11-19 digits
	var reg = /^\d{8,9}$/
	if (!reg.test(str))
		return false
	
	// validate using validation algorithm
	
	var creditNum = Number(str)
	var i, lsd /* least significant digit */, weight, product
	var result = 0
	
	for (i = 0; i < str.length; i++) {
		lsd = creditNum % 10
		creditNum = (creditNum - lsd)/10
		weight = i + 1
		// get the product of the lsd and its corresponding weight
		product = lsd * weight
		// sum the products
		result += product
	}
	
	// the credit card number is legal if the result sum is 0 modulu 11
	return result % 11 == 0
}

// check string's length
function checkLength(str,minLength,maxLength)
{
	if (arguments.length < 1)
		return false
	if (arguments.length == 1)
		return true
	minLength = minLength? Math.max(minLength,0): 0
	maxLength = maxLength? Math.min(maxLength,str.length): str.length
	return minLength <= str.length && str.length <= maxLength
}

// generic form validation
// =======================
/*
arguments: a reference to a form object
description:
	the function executes a generic form validation according to addon tag 
		attributes/properties. the function then return a boolean indicating whether validation 
		succeded. disabled form fields are not validated
usage:
	* one way to call the function is by using the form tag's attribute - onSubmit
		onSubmit="return genericFormValidation(this)"
	* the genericFormValidation function relies on the definition of addon attributes/properties
	  to the form tag/object and its fields. these attributes are listed here.
	* addon attributes/properties of the form tag/object:
	  1. validationFields - a comma separated string specifing the field names to be validated
	     by the order they should be validated. if not specified all non-disabled fields are
		 validated by their default order. recommended if order of validation does not match
		 the order of the html tags.
		 	example:
			<form validationFields="uname,pass" onSubmit="return genericFormValidation(this)">
				<input type="text" name="uname" value="">
				<input type="password" name="pass" value="">
				<input type="text" name="extra_field" value=""><!-- not validated -->
			</form>
		2. onValidationFail - a string to be evaluated when validation fails
		3. onValidationSuccess - a string to be evaluated when validation succeeds
	* addon attributes/properties of a form field tag/object:
		1. mandatory - if this addon attribute/property is defined and set to 'true' (string)
		   then validation will fail in one of the following cases:
		   a. the field is an input field of 'text' type with value == ''
		   b. the field is a single select field and its selected index is 0
		   c. the field is a multiple select field and its selected index is not set
		2. validationType - this addon attribute/property used to validate 'text' input 
	   	   fields only. one of the following strings could be assigned to it:
		   a. integer - only digits
		   b. numeric - any numeric string (integer, float, negative number)
		   c. alphanumeric - a string containg only digits, underscores and english letters
		   d. name - used with another addon attribute/property 'validationLang' specifies the
		  	  language used to validate the name (currently only 'en' for english and 'he' for 
			  hebrew) if not specified english language assumed. a name can contain any letter
			  form the specified language alphabet, digit and any of the following characters:
			  '-', '.' , '_' ,' ', ',', '\'', '"'
		   e. email - validate an e-mail address's syntax
		3. validationFunc - any string that evaluates to a boolean, for example: 
		   '/^\d{6}$/.test(this.value)' evaluated to true if field's value is a 6 digits string or 
		   'booleanFunction()' evaluates to what ever the booleanFunction() call returns. 
		   NOTE: it is possible to use the 'this' pointer in the validationFunc string.
		   NOTE: validationFunc could be applied to any enabled field
		4. validationAlert - if defined then alerted no matter why validation has failed
		5. validationAlert1 - unless 'validationAlert' defined, alerted when mandatory validation
		   fails. to be used only with fields where mandatory="true" defined
		6. validationAlert2 - unless 'validationAlert' defined, alerted when validation fails for
		   any other reason. to be used only with fields where mandatory="true" defined
		7. onValidationFail - a string to be evaluated when validation fails on the refered field
		8. noAlert - if used no alert prompted when validation fails on field
*/
function genericFormValidation(formObj)
{
	//genericFormValidation.DEBUG = true
	var formFields, fieldObj, fieldTempObj
	var isValid, isMandatory, isMandatoryFailure, alertString
	
	// get the names of the fields to be validated
	if (formObj.attributes.validationFields) // 'validationFields' is an addon form's tag attribute
		formFields = formObj.attributes.validationFields.split(',')
	else { // get all form's enabled fields
		formFields = new Array()
		for (var i = 0; i < formObj.elements.length; i++)
			//alert(i + ',' + formObj.elements[i].name)
			if (!formObj.elements[i].disabled)
				formFields[formFields.length] = formObj.elements[i].name
	}
	isValid = true
	
	// run through all fields soecified in the formFields array and validate them
	for (var i = 0; i < formFields.length; i++) {
		fieldObj = formObj.elements[i]
		//alert('formFields[i]: ' + formFields[i] + '\nfieldObj.name: ' + fieldObj.name)
		if (!fieldObj || fieldObj.disabled || fieldObj.validationSkip)
			continue
		
		//genericFormValidation.getExpandoAttributeValue(fieldObj, 'mandatory')
		isMandatory = (genericFormValidation.getExpandoAttributeValue(fieldObj, 'mandatory') == 'true')
		isMandatoryFailure = false
		
		if (genericFormValidation.DEBUG)
			alert('name=' + fieldObj.name + 
				'\ntype=' + fieldObj.type + 
				'\nisMandatory=' + isMandatory +
				'\nvalidation type=' + genericFormValidation.getExpandoAttributeValue(fieldObj, 'validationType') + 
				'\nvalidation Alert=' + genericFormValidation.getExpandoAttributeValue(fieldObj, 'validationAlert') + 
				'\nvalidation Alert1=' + genericFormValidation.getExpandoAttributeValue(fieldObj, 'validationAlert1') + 
				'\nvalidation Alert2=' + genericFormValidation.getExpandoAttributeValue(fieldObj, 'validationAlert2') + 
				'\nvalue=' + fieldObj.value)
		
		// validate text boxes
		if (fieldObj.type == 'text') {
			// mandatory validation
			if (isMandatory && fieldObj.value.length == 0) { 
				isValid = false
				isMandatoryFailure = true
			}
			
			// generic validation
			else if (fieldObj.value.length > 0 && genericFormValidation.getExpandoAttributeValue(fieldObj, 'validationType')) {
				switch(genericFormValidation.getExpandoAttributeValue(fieldObj, 'validationType')) {
				case 'integer': // digits only validation
					isValid = isInteger(fieldObj.value)
					break
				case 'numeric': // any numeric string validation
					isValid = isNumeric(fieldObj.value)
					break
				case 'alphanumeric': // alphanumeric string validation
					isValid = isAlphaNumeric(fieldObj.value)
					break
				case 'name':	// name validation
					isValid = isName(genericFormValidation.getExpandoAttributeValue(fieldObj, 'validationLang'))
					break
				case 'email':	// email validation
					isValid = (isValidEmail(fieldObj.value) == 0)
					break
				}
			}
		}
		
		// generic combo-boxes validation
		else if (fieldObj.type == 'select-one' && isMandatory) {
			if (fieldObj.selectedIndex == 0) { // no option selected
				isValid = false
				isMandatoryFailure = true
			}
		}
		
		// generic lists validation
		else if (fieldObj.type == 'select-multiple' && isMandatory) {
			for (var j = 0; j < fieldObj.options.length; j++)
				if (fieldObj.attributes.options[j].selected)
					break
			if (j == fieldObj.attributes.options.length) { // no option selected
				isValid = false
				isMandatoryFailure = true
			}
		}
		
		// generic password validation
		else if (fieldObj.type == 'password' && isMandatory && fieldObj.value.length == 0) {
			isValid = false
			isMandatoryFailure = true
		}
		
		// generic password validation
		else if (fieldObj.type == 'textarea' && isMandatory && fieldObj.value.length == 0) {
			isValid = false
			isMandatoryFailure = true
		}
		
		// user defined boolean function/string
		if (isValid && genericFormValidation.getExpandoAttributeValue(fieldObj, 'validationFunc')) {
			// use the ThisImpersonator object to evaluate the validationFunc string which
			// could contain 0 or more appearences of the 'this' keyword
			fieldTempObj = new ThisImpersonator(fieldObj)
			isValid = fieldTempObj.eval(genericFormValidation.getExpandoAttributeValue(fieldObj, 'validationFunc'))
		}
		
		// handle validation failure
		if (!isValid) {
			if (genericFormValidation.getExpandoAttributeValue(fieldObj, 'onValidationFail')) {
				// use the ThisImpersonator object to evaluate the onValidationFail string which
				// could contain 0 or more appearences of the 'this' keyword
				fieldTempObj = new ThisImpersonator(fieldObj)
				fieldTempObj.eval(genericFormValidation.getExpandoAttributeValue(fieldObj, 'onValidationFail'))
			}
			else {
				if (genericFormValidation.config.alertOnFail && typeof(genericFormValidation.getExpandoAttributeValue(fieldObj, 'noAlert')) != 'string') {
					// get the alert string
					if (genericFormValidation.getExpandoAttributeValue(fieldObj, 'validationAlert')) // any validation failure
						alertString = genericFormValidation.getExpandoAttributeValue(fieldObj, 'validationAlert')
					else if (isMandatory && isMandatoryFailure) { // mandatory validation failure
						if (genericFormValidation.getExpandoAttributeValue(fieldObj, 'validationAlert1'))
							alertString = genericFormValidation.getExpandoAttributeValue(fieldObj, 'validationAlert1')
						else
							alertString = genericFormValidation.config.MANDATORY_ALERT
					}
					else if (isMandatory) { // mandatory field, validation failure for other reason
						if (genericFormValidation.getExpandoAttributeValue(fieldObj, 'validationAlert2'))
							alertString = genericFormValidation.getExpandoAttributeValue(fieldObj, 'validationAlert2')
						else
							alertString = genericFormValidation.config.ILEGAL_ALERT
					}
					else // any other validation failure
						alertString = genericFormValidation.config.ILEGAL_ALERT
					
					alert(alertString)
				}
				
				if (genericFormValidation.config.focusOnFail)
					fieldObj.focus()
				else if (genericFormValidation.config.selectOnFail)
					fieldObj.select()
			}
			
			return false
		}
	}
	
	return true
}
genericFormValidation.config = new Object()
genericFormValidation.config.MANDATORY_ALERT = 'This field is mandatory.'
genericFormValidation.config.ILEGAL_ALERT = 'This field is ilegal.'
genericFormValidation.config.alertOnFail = true
genericFormValidation.config.focusOnFail = true
genericFormValidation.config.selectOnFail = false
genericFormValidation.DEBUG = false
genericFormValidation.getExpandoAttributeValue = function(oObj, sAttrName) {
	if (oObj.name != undefined)
	{
		if (!oObj || !oObj.attributes[sAttrName])
			return null;
		else
		{
			return oObj.attributes[sAttrName].value;
		}
	}
}

// this impersonator object 'assigns' a given object to the this pointer
// used to evaluate strings containing the 'this' keyword when the this pointer
// does not reference the required object.
function ThisImpersonator(obj)
{
	this.name = 'ThisImpersonatorObject'
	this.object = obj
	eval(this.name + ' = this.object')
	this.replace = ThisImpersonatorReplace
	this.eval = ThisImpersonatorEval
}

// replace all appearences of 'this' keyword with the object's name which references the 
// object property
function ThisImpersonatorReplace(str)
{
	return str.replace(/(\b)(this)(\b)/g,'$1' + this.name + '$3')
}

// evaluate a string containg 0 or more  appearences of 'this' keyword
function ThisImpersonatorEval(str)
{
	str = this.replace(str)
	return eval(str)
}

