/***************************************************************************
@doc 	incValidation.js 
@author	Emily Boyd
@desc	Common validation functions
@comm 	$Revision: 8 $ 
@comm 	$Author: Emily.boyd $ 
@comm 	$Date: 3/07/01 5:45p $
****************************************************************************
|		VAL_validate(objField,strValidationType,strError,arrError)
|
|		VAL_getFieldValue(objField)
|
|		VAL_addError(objField,strReturn,arrError)
|
|		VAL_isInError(objField, arrError)
|
|		VAL_getErrors(arrError)
|
|		VAL_validateWithPattern(strValue,strPattern)
|
|		VAL_validateLength(strValue,intMinLength,intMaxLength)
|
|		VAL_validateDate(objField)
|
|		VAL_getMonthEnd(dteValue)
|
|		VAL_validateEmail(strValue)
|
|		VAL_validateNumber(strValue,blnWhole,intMinVal,intMaxVal,intMinDec,intMaxDec)
****************************************************************************/

/***************************************************************************
@func	VAL_validate()
@author	Emily Boyd
@desc	Performs various kinds of data validation by calling other functions,
		depending on the validation type passed to the function.
		
		USAGE OF VAL_validate
	
		This functions works by building an array of field objects which
		failed validation, and the error which should be displayed to the user
		for each failed validation. To display the errors and set focus to the
		first invalid field, VAL_getErrors is called. An example is below.
	
		// define variables
		var arrError = new Array();		// array containing objects/errors
		var blnError;					// whether any errors were found
		
		// validate the fields
		VAL_validate(form.Field1,'RealNumber','Field1 must be a real number.',arrError);
		VAL_validate(form.Field1,'Required','Field2 is a required field.',arrError);
		
		// check if errors were found
		if (arrError.length > 0) {
			blnError = true;
		} else {
			blnError = false;
		}
		
		// display the error
		VAL_getErrors(arrError);
		
@param	objField	object	field object to be validated
@param	strValidationType	string	validation type
@param	strError	string	error string which should be returned
@param	arrError	array	error array
***************************************************************************/
function VAL_validate(objField,strValidationType,strError,arrError) {
	var strReturn = '';		// return string
	var strValue = '';		// value string
	
	// determine the value of field to check
	strValue = VAL_getFieldValue(objField);
	
	// perform validation depending on validation type requested	
	switch(strValidationType) {
		case 'WholeNumber':
			if (!VAL_validateNumber(strValue,1,'','','','')) { strReturn = strError; }
			break;
		case 'PositiveWholeNumber':
			if (!VAL_validateNumber(strValue,1,'0','','','')) { strReturn = strError; }
			break;
		case 'NegativeWholeNumber':
			if (!VAL_validateNumber(strValue,1,'','0','','')) { strReturn = strError; }
			break;
		case 'PositiveWholeNumberNotZero':
			if (!VAL_validateNumber(strValue,1,'1','','','')) { strReturn = strError; }
			break;
		case 'Currency':
			if (!VAL_validateNumber(strValue,0,'','','0','2')) { strReturn = strError; }
			break;
		case 'PositiveCurrency':
			if (!VAL_validateNumber(strValue,0,'0','','0','2')) { strReturn = strError; }
			break;
		case 'RealNumber':
			if (!VAL_validateNumber(strValue,0,'','','','')) { strReturn = strError; }
			break;
		case 'PositiveRealNumber':
			if (!VAL_validateNumber(strValue,0,'0','','','')) { strReturn = strError; }
			break;
		case 'Postcode':
			if (!VAL_validateNumber(strValue,1,'100','9999','','') ||
				!VAL_validateWithPattern(strValue,'^[0-9]*$')) { strReturn = strError; }
			break;
		case 'Length20':
			if (!VAL_validateLength(strValue,'0','20')) { strReturn = strError; }
			break;
		case 'Length50':
			if (!VAL_validateLength(strValue,'0','50')) { strReturn = strError; }
			break;
		case 'Length100':
			if (!VAL_validateLength(strValue,'0','100')) { strReturn = strError; }
			break;
		case 'Length200':
			if (!VAL_validateLength(strValue,'0','200')) { strReturn = strError; }
			break;
		case 'Length256':
			if (!VAL_validateLength(strValue,'0','256')) { strReturn = strError; }
			break;
		case 'Length300':
			if (!VAL_validateLength(strValue,'0','300')) { strReturn = strError; }
			break;
		case 'Length400':
			if (!VAL_validateLength(strValue,'0','400')) { strReturn = strError; }
			break;
		case 'Length500':
			if (!VAL_validateLength(strValue,'0','500')) { strReturn = strError; }
			break;
		case 'Password6_12':
			if (!VAL_validateWithPattern(strValue,'^[a-zA-Z0-9!@#$%^&*()_]*$') ||
				!VAL_validateLength(strValue,'6','12')) { strReturn = strError; }
			break;
		case 'Password5_25':
			if (!VAL_validateWithPattern(strValue,'^[a-zA-Z0-9]*$') ||
				!VAL_validateLength(strValue,'5','25')) { strReturn = strError; }
			break;
		case 'Login5_25':
			if (!VAL_validateWithPattern(strValue,'^[a-zA-Z0-9]*$') ||
				!VAL_validateLength(strValue,'5','25')) { strReturn = strError; }
			break;	
		case "Phone": 
			if (!VAL_validateWithPattern(strValue,'^[\+]?[0-9]*[ ]?[\(]?[0-9]*[\)]?[ ]?[0-9 ]*$') || 
				!VAL_validateLength(strValue,'8','20')) { strReturn = strError; }
			break;
		case 'Email':
			if (VAL_validateEmail(strValue,'')) { strReturn = strError; }
			break;
		case 'Required':
			if (strValue.length == 0) { strReturn = strError; }
			break;
		case 'Date':
			DTE_updateDateSelection(objField.name, objField.form);
			if (!DTE_validateDate(VAL_getFieldValue(objField),0,0,0)) { strReturn = strError; }
			break;
		case 'DateFuture':
			DTE_updateDateSelection(objField.name, objField.form);
			if (!DTE_validateDate(VAL_getFieldValue(objField),1,0)) { strReturn = strError; }
			break;
		case 'DatePast':
			DTE_updateDateSelection(objField.name, objField.form);
			if (!DTE_validateDate(VAL_getFieldValue(objField),0,1)) { strReturn = strError; }
			break;
		case 'DateRequired':
			DTE_updateDateSelection(objField.name, objField.form);
			if (VAL_getFieldValue(objField).length == 0) { strReturn = strError; }
			break;
		case 'JPGorGIF':
			if (!VAL_validateWithPattern(strValue,'(.jpg|.gif|.JPG|.GIF){1}$')) { strReturn = strError; }
			break;
		case 'IsCSV':
			if (!VAL_validateWithPattern(strValue,'(.csv|.CSV){1}$')) { strReturn = strError; }
			break;	
		default:
			strReturn = 'INVALID VALIDATION TYPE: ' + strValidationType;
			break;
	}

	// add error to the array
	VAL_addError(objField,strReturn,arrError)
}

/***************************************************************************
@func	VAL_getFieldValue()
@author	Emily Boyd
@desc		This function gets the value of a field by determing the type
		of the form element eg. text and then getting the value.
@param	objField	object	form element to get the value of
@return	string	value of field
***************************************************************************/
function VAL_getFieldValue(objField) {
	var strValue = '';		// value of field

	// have the field object, but not the field value
	// need to use the type property to determine how to get the value
	if (objField) {
		switch(objField.type) {
			case 'text':
				strValue = objField.value; // value of text field
				break;
			case 'file':
				strValue = objField.value; // value of text field
				break;
			case 'hidden':
				strValue = objField.value; // value of text field
				break;
			case 'textarea':
				strValue = objField.value; // value of text field
				break;
			case 'password':
				strValue = objField.value; // value of text field
				break;
			case 'select-one': 
				strValue = objField.options[objField.selectedIndex].value;
				break;
			// other form element types could be added here if needed eg.
			// checkbox, radio, select-multiple
			default:
				strValue = '';
		}
	
	// object was not found on form
	} else {
		strValue = '';
	}
	
	// return the value
	return strValue;
}

/***************************************************************************
@func	VAL_addError()
@author	Emily Boyd
@desc		This function adds object/error string to an array. It is called
		by validation functions such as VAL_validate to build up the array.
@param	arrError	array	a two-dimensional array; each element contains an
		array which holds the field object [0] and error string [1]
***************************************************************************/
function VAL_addError(objField,strReturn,arrError) {
	// return the object and error string in an array
	if (strReturn.length > 0) {
		var arrReturn = new Array(objField,strReturn);
		arrError.push(arrReturn);
	}
}

/***************************************************************************
@func	VAL_isInError(objField, arrError)
@author	Graham Pearson
@desc	This function checks to see whether a certain form or querystirng field
		has an error message associated with it in the error array and returns
		the error messages found (or an empty string)
@param	objField 	field to be checked
	arrError	array	a two-dimensional array; each element contains an
***************************************************************************/
function VAL_isInError(objField, arrError) {
	var blnReturn;		//the return value
	var i;			//loop counter for recusing through the array

	//assume no errors to begin with
	blnReturn = false;
		
	//if already an array, add an extra item
	for (i = 0; i < arrError.length; i++ ) {
		if (arrError[i][0].name == objField.name) {
			blnReturn = true;
		}
	}

	return blnReturn;
}

/***************************************************************************
@func	VAL_getErrors()
@author	Emily Boyd
@desc	Loops through an array containing field objects and associated
		errors; displays an error message to the user, and puts focus on
		first invalid element.
@param	arrError	array	a two-dimensional array; each element contains an
		array which holds the field object [0] and error string [1]
***************************************************************************/
function VAL_getErrors(arrError) {
	var strError = '';	// error string (for display to user)
	var objFocus;		// focus object
			
	// for each item in the array, add this error to error string
	for (i=0; i < arrError.length; i++) {
		var strItemError = arrError[i][1];
		strError = strError + '- ' + strItemError + '\n';
	}
			
	// if there were any errors, display here
	if (strError.length > 0) {
		// set focus to first invalid field (if found on form)
		objFocus = arrError[0][0];
		if (objFocus && objFocus.type != 'hidden') {
			objFocus.focus(); 
		}
		return 'The following errors were found:\n\n' + strError +
		'\nPlease correct the errors before continuing.';
	} else {
		return '';
	}
}

/***************************************************************************
@func	VAL_validateWithPattern()
@author	Emily Boyd
@desc	Performs validation on an input field by using regular expressions
		to match the input against a pattern. The search is global and
		case insensitive.
@param	objField	object	field object to be validated
@param	strPattern	string	the pattern for the input to be validated
		against eg. "[a-z]+" for a word of one or more letters
@param	strError	string	error string which should be returned
@param	arrError	array error array
***************************************************************************/
function VAL_validateWithPattern(strValue,strPattern) {
	var blnReturn = false;		// return value
	var objRegExp;			// regular expression object
	
	// set options for reg exp object
	objRegExp = new RegExp(strPattern, 'g', 'i');

	//if the length is zero we return true regardless - 'Required' validation should be used
	if (strValue.length == 0) {

		blnReturn = true;

	} else {

		// if it matches, then it passed validation
		if (objRegExp.test(strValue) ) {
			blnReturn = true;
		}
	}
	
	// return success flag
	return blnReturn;
}

/***************************************************************************
@func	VAL_validateLength()
@author	Emily Boyd
@desc	Performs data validation to make sure that string is not over
		a maximum length. If validation fails, adds to error array.
@param	objField	object	field object to be validated
@param	intMaxLength	integer	maximum length of string
@param	strError	string	error string which should be returned
@param	arrError	array error array
***************************************************************************/
function VAL_validateLength(strValue,intMinLength,intMaxLength) {
var blnValid = true;	
	
	// perform validation
	if (strValue.length != 0) {	// only validate if the length is not zero - use 'Required' to validate non-blank
		if (intMinLength.length > 0) {
			if (strValue.length < parseInt(intMinLength)) {
				blnValid = false;
			}
		}
		if (intMaxLength.length > 0) {
			if (strValue.length > parseInt(intMaxLength)) {
				blnValid = false;
			}
		}
	}
	
	return blnValid;
}



/***************************************************************************
@func	VAL_validateEmail()
@author	Emily Boyd
@desc	Validates an email address (on the server)
		This function is called by VAL_validateEmail
		** ANY CHANGES TO THIS FUNCTION SHOULD ALSO BE MADE TO
		SERVER-SIDE VALIDATION (INCVALIDATION.ASP) **
@param	strValue	string	the email address being checked
@return	string	blank (for valid) or an error message
***************************************************************************/
function VAL_validateEmail(strValue) {
	// Version 1.1:  Sandeep V. Tamhankar (stamhankar@hotmail.com)
	// This script and many more are available free online at
	// The JavaScript Source! http://javascript.internet.com
	// http://javascript.internet.com/forms/check-email.html

	// only check if email address is supplied
	if (strValue.length > 0) {

		// The following pattern is used to check if the entered e-mail address
		// fits the user@domain format. It also is used to separate the username
		// from the domain.
		var emailPat = /^(.+)@(.+)$/
		
		// The following string represents the pattern for matching all special
		// characters. We don't want to allow special characters in the address. 
		// These characters include ( ) < > @ , ; : ' \ " . [ ]
		var specialChars = "\\(\\)<>@,;:'\\\\\\\"\\.\\[\\]"
		
		// The following string represents the range of characters allowed in a 
		// username or domainname. It really states which chars aren't allowed.
		var validChars = "\[^\\s" + specialChars + "\]"
		
		// The following pattern represents the range of characters allowed as
		// the first character in a valid username or domain. I just made it
		// the same as above, but if you want to add a different constraint,
		// you would change it here.
		var firstChars = validChars
		
		// The following pattern applies if the "user" is a quoted string (in
		// which case, there are no rules about which characters are allowed
		// and which aren't; anything goes). E.g. "jiminy cricket"@disney.com
		// is a legal e-mail address.
		var quotedUser = "(\"[^\"]*\")"
		
		// The following pattern applies for domains that are IP addresses,
		// rather than symbolic names.  E.g. joe@[123.124.233.4] is a legal
		// e-mail address. NOTE: The square brackets are required.
		var ipDomainPat = /^\[(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})\]$/
		
		// The following string represents at atom (basically a series of
		// non-special characters.)
		var atom = "(" + firstChars + validChars + "*" + ")"
		
		// The following string represents one word in the typical username.
		// For example, in john.doe@somewhere.com, john and doe are words.
		// Basically, a word is either an atom or quoted string.
		var word = "(" + atom + "|" + quotedUser + ")"
		
		// The following pattern describes the structure of the user
		var userPat = new RegExp("^" + word + "(\\." + word + ")*$")
		
		// The following pattern describes the structure of a normal symbolic
		// domain, as opposed to ipDomainPat, shown above.
		var domainPat = new RegExp("^" + atom + "(\\." + atom +")*$")

		// Finally, let's start trying to figure out if the supplied address is
		// valid.

		// Begin with the course pattern to simply break up user@domain into
		// different pieces that are easy to analyze.
		var matchArray=strValue.match(emailPat)
		if (matchArray==null) {
			// Too many/few @'s or something; basically, this address doesn't
			// even fit the general mould of a valid e-mail address.
			return 'Email is invalid (check @ and .\'s).';
		}
		var user=matchArray[1]
		var domain=matchArray[2]

		// See if "user" is valid 
		if (user.match(userPat)==null) {
		    // User is not valid
		    return 'Email is invalid (the username doesn\'t seem to be valid).';
		}
		
		// If the e-mail address is at an IP address (as opposed to a symbolic
		// host name) make sure the IP address is valid.
		var IPArray=domain.match(ipDomainPat)
		if (IPArray!=null) {
			// This is an IP address
			for (var i=1;i<=4;i++) {
				if (IPArray[i]>255) {
					return 'Email is invalid (destination IP address is invalid).';
				}
		    }
		    // If we get here, all ip numbers were valid
		    return '';
		}

		// Domain is symbolic name
		var domainArray=domain.match(domainPat)
		if (domainArray==null) {
			return 'Email is invalid (the domain name doesn\'t seem to be valid).';
		}
		
		// Domain name seems valid, but now make sure that it ends in a
		// three-letter word (like com, edu, gov) or a two-letter word,
		// representing country (uk, nl).
		// If there's a country code at the end of the address, the full domain
		// must include a hostname and category (e.g. host.co.uk or host.pub.nl)
		// If it ends in a .com or something, make sure there's a hostname.

		// Now we need to break up the domain to get a count of how many atoms
		// it consists of.
		var atomPat=new RegExp(atom,"g")
		var domArr=domain.match(atomPat)
		var len=domArr.length
		if (domArr[domArr.length-1].length<2 || 
		    domArr[domArr.length-1].length>3) {
		   // The address must end in a two letter or three letter word.
		   return 'Email is invalid (the address must end in a three-letter domain, or two letter country).';
		}
		
		// If it just ends in .com, .gov, etc., make sure there's a host name.
		//   This case can never actually happen because earlier checks take
		//   care of this implicitly, but we'll do it anyway.
		if (domArr[domArr.length-1].length==3 && len<2) {
		   return 'Email is invalid (this address is missing a hostname).';
		}
	}
	
	// If we've gotten this far, everything's valid!
	return '';
}

/***************************************************************************
@func	VAL_validateNumber()
@author	Emily Boyd
@desc		This function is called by other functions such as
		VAL_validate and VAL_validateDecimalPlaces.
		It validates a number based on the parameters passed in. Can validate
		whether a number is required, whole, positive, min/max values,
		and min/max number of decimal places.
@param	strValue	string	the number to be tested
@param	blnReq	boolean	must have provided a number ie. not blank (true|false)
@param	blnWhole	boolean	must be whole number (true|false)
@param	blnPos	boolean	must be positive number (true|false)
@param	intMinVal	integer	minimum value for number (if no check is required,
		pass in empty string)
@param	intMaxVal	integer	maximum value for number (if no check is required,
		pass in empty string)
@param	intMinDec	integer	minimum number of decimal places that number
		must have (only used if blnWhole is false; if no check is required,
		pass in empty string)
@param	intMaxDec	integer	maximum number of decimal places that number
		must have (only used if blnWhole is false; if no check is required,
		pass in empty string)
@return	true if valid, false if not
***************************************************************************/
function VAL_validateNumber(strValue,blnWhole,intMinVal,intMaxVal,intMinDec,intMaxDec) {
	var objRegExp;			// regular expression object
	var strPattern = '';	// regular expression pattern
	var blnValid = false;	// whether or not number is valid (default to false)
	var strPos = '';		// used to build part of pattern for checking sign
	var strWhole = '';		// used to build part of pattern for checking whole number

	// if first character of number is a decimal point, add a leading zero
	var strValue = new String(strValue);
	if (strValue.substr(0,1) == '.') {
		strValue = '0' + strValue;
	}
	
	// strip out any commas, as these will make the number return as invalid
	strValue = strValue.replace(/,/g,'');
	
	// the following section builds up the parts of the regular expression
	// pattern, based on the parameters passed into the function (see function
	// header for more details)
	
	// if number has been provided
	if (strValue.length > 0) {
		
		// if number doesn't have to be positive
		// this part of pattern looks for zero or one negative sign
		strPos = '-?';
		
		// if number must be whole
		if (blnWhole == 1) {
			// this part of pattern looks for at least one integer
			// and optionally a decimal point followed by zero(s)
			// which takes care of numbers such as 3.0, which should
			// be considered as "whole"
			strWhole = '[0-9]+\\.?[0]*';
		
		// else if number doesn't have to be whole,
		// and number of decimal places has not been specified
		} else if (blnWhole == 0 && intMinDec.length == 0 && intMaxDec.length == 0) {
			// this part of pattern looks for at least one integer,
			// then optionally a decimal point and then other integers
			strWhole = '[0-9]+\\.?[0-9]*';
		
		// else if number doesn't have to be whole,
		// and number of decimal places has been specified
		} else {
			// this part of pattern looks for at least one integer,
			// then a decimal point and then other integers (must have
			// at least intMinDec decimal places, and no more than intMaxDec
			// decimal places). if intMinDec is 0, then it is possible to have no
			// decimal point, otherwise one must be present
			if (intMinDec == 0) {
				strWhole = '[0-9]+\\.?[0-9]{' + intMinDec + ',' + intMaxDec + '}';
			} else {
				strWhole = '[0-9]+\\.[0-9]{' + intMinDec + ',' + intMaxDec + '}';
			}
		}
		
		// pattern is built by putting parts of pattern together
		strPattern = '^' + strPos + strWhole + '$';
		
		// set options for reg exp object
		objRegExp = new RegExp('^'+strPattern+'$', 'g', 'i');
		
		// test the value with the reg exp pattern
		if (objRegExp.test(strValue)) {
			blnValid = true;
			
			// passed validation so far, need to check min/max values
			if (intMinVal.length > 0) {
				if (parseFloat(strValue) < parseFloat(intMinVal)) {
					blnValid = false;
				}
			}
			if (intMaxVal.length > 0) {
				if (parseFloat(strValue) > parseFloat(intMaxVal)) {
					blnValid = false;
				}
			}
		}
	
	// otherwise valid as number not provided and not required
	} else {
		blnValid = true;
	}
	
	// return validity
	return blnValid;
}

