<!--
// Functions for client-side validation of forms.
// Note (2002-06-12): updated to validate.js - a number of functions were moved to strings.js:
// isValidString, isIn, isUpperCaseLetterCode, isLowerCaseLetterCode, isLetterCode, isDigitCode,
// isAlphanumericCode, isUpperCaseLetter, isLowerCaseLetter, isLetter, isDigit, isAlphanumeric,
// isAllDigits, isAllLetters, isAllAlphanumeric

// requires: strings.js

// author: Shaun Moss (shaun@MarsEngineering.com)
// last updated: 2002-06-12


/////////////////////////////////////////////////////////////////////////////////////////
// check domain names, email addresses, and passwords.

function isValidMailbox(str)
{
	// 'Mailbox' in this context means the first part of an email address
	// (preceding the '@').

	// Checks that str only contains digits, letters, fullstops, hyphens or underscores.
	// NB This is probably not a perfect test, e.g. a mailbox name of "." or "-"
	// would be accepted by this function.

	// check str:
	if (!isValidString(str))
		return false;
	
	// check characters:
	var ch;
	for (var j = 0; j < str.length; j++)
	{
		ch = str.charAt(j);
		if (!(isAlphanumeric(ch) || isIn(ch, ".-_")))
			return false;
	}
	
	// looks ok:
	return true;
}


function isValidDomainWord(str)
{
	// By 'Domain Word' I mean a component of a domain name,
	// e.g. 'mars' or 'com' in 'mars.com'.

	// The following rules are used (see RFC952)
	// * Words can contain only letters, digits, or dashes.
	// * Words may not start or end with a dash.

	// check str:
	if (!isValidString(str))
		return false;

	// check characters:
	var ch;
	for (var j = 0; j < str.length; j++)
	{
		ch = str.charAt(j)
		if (j == 0 || j == str.length - 1)
		{
			// word can only start or end with an alphanumeric:
			if (!isAlphanumeric(ch))
				return false;
		}
		else
		{
			if (!(isAlphanumeric(ch) || ch == "-"))
				return false;
		}
	}

	// all ok:
	return true;
}


function isValidDomainName(str)
{
	// check str:
	if (!isValidString(str))
		return false;

	// check length:
	if (str.length < 4 || str.length > 63)
		return false;

	// break up domain name into words (word = domain component, eg: "www" or "com"):
	var words = str.split(".");

	// check number of words (must be at least 2):
	if (words.length < 2)
		return false;
	
	// check each word:
	for (var j = 0; j < words.length; j++)
		if (!isValidDomainWord(words[j]))
			return false;
	
	// all ok:
	return true;
}


function isValidEmailAddress(str)
{
	// check str:
	if (!isValidString(str))
		return false;
	str = trim(str);

	// check the '@':
	var atPos = str.indexOf("@");
	if ((atPos < 1) || (atPos > str.length-4))
		return false;

	// check the mailbox name:
	var mailbox = str.substring(0, atPos);
	if (!isValidMailbox(mailbox))
		return false;

	// check domain name:
	var domainName = str.substring(atPos + 1, str.length);
	if (!isValidDomainName(domainName))
		return false;

	// all ok:
	return true;
}


function isValidPassword(str, minLength, maxLength)
{
	// A valid password is defined as a string of minLength to maxLength chars,
	// containing only letters and/or numbers.
	// Can also be used to validate usernames.

	// check str:
	if (!isValidString(str))
		return false;

	// check length:
	if (str.length < minLength || str.length > maxLength)
		return false;

	// check characters (digits and letter permitted):
	var ch;
	for (var j = 0; j < str.length; j++)
	{
		ch = str.charAt(j)
		if (!isAlphanumeric(ch))
			return false;
	}

	// all ok:
	return true;
}


function isValidUsername(str, minLength, maxLength)
{
	// validates a username in the same way as a password:
	return isValidPassword(str, minLength, maxLength);
}


/////////////////////////////////////////////////////////////////////////////////////////
// check phone numbers, country codes, and postcodes:

function matchesPattern(str, pattern)
{
	// Returns true if str matches the pattern of letters, digits, and punctuation in pattern.
	// Upper case letters in str must match upper case letters in pattern.
	// Lower case letters must match lower case letters.
	// Digits must match digits.
	// Any other characters must exactly match.
	// e.g. matchesPattern("RG1 5NN", "ZZ9 9ZZ") returns true
	//
	// Note this function could possibly be done with regular expressions,
	// but I have found these to be unreliable and also hard to read.

	// check length
	if (str.length != pattern.length)
		return false;

	// check characters:
	var chStr, chPattern;
	for (var j = 0; j < str.length; j++)
	{
		chStr = str.charAt(j);
		chPattern = pattern.charAt(j);
		if (isUpperCaseLetter(chStr))
		{
			if (!isUpperCaseLetter(chPattern))
				return false;
		}
		else if (isLowerCaseLetter(chStr))
		{
			if (!isLowerCaseLetter(chPattern))
				return false;
		}
		else if (isDigit(chStr))
		{
			if (!isDigit(chPattern))
				return false;
		}
		else if (chStr != chPattern)
			return false;
	}

	// all ok:
	return true;
}


function isValidPhoneNumber(str)
{
	// This version is fairly general purpose.  The string length or format is not checked.
	// The characters permitted are digits, '+', '-', ' ', '(', ')'.

	// check str:
	if (!isValidString(str))
		return false;

	// check characters (digits, spaces, parentheses, and '-' or '+' permitted)
	var ch;
	var nDigits = 0;
	for (var j = 0; j < str.length; j++)
	{
		ch = str.charAt(j)
		if (isDigit(ch))
			nDigits++;
		else if (!isIn(ch, " ()-+"))
			return false;
	}

	// (vary constants in this bit if necessary)
	// check number of digits:
	if (nDigits < 5 || nDigits > 15)
		return false;

	// all ok:
	return true;
}


function isValidCountryCode(str)
{
	// Does a basic check that str is a valid country code, by checking that it's a string of 2 letters.
	// This function could potentially be improved by checking against a list of the actual country codes.

	// check str:
	if (!isValidString(str))
		return false;

	// check length:
	if (str.length != 2)
		return false;

	// check it's all letters:
	if (!isAllLetters(str))
		return false;

	// all ok:
	return true;
}


function isValidZipcode(str)
{
	// check str:
	if (!isValidString(str))
		return false;

	return matchesPattern(str, "99999");
}


function isValidUKPostcode(str)
{
	// check str:
	if (!isValidString(str))
		return false;
	// check length:
	if (str.length < 6 || str.length > 8)
		return false;
	// check for space:
	if (str.charAt(str.length - 4) != ' ')
		return false;
	// check inward code (last 3 chars):
	var inwardCode = str.substr(str.length - 3, 3);
	if (!matchesPattern(inwardCode, "Z99"))
		return false;
	// check outward code (first part, before space):
	var outwardCode = str.substr(0, str.length - 4);
	// check first char is a letter:
	if (!isUpperCaseLetter(outwardCode.charAt(0)))
		return false;
	// check all alphanumeric chars:
	if (!isAllAlphanumeric(outwardCode))
		return false;

	// all ok:
	return true;
}


function isValidDublinPostcode(str)
{
	// check str:
	if (!isValidString(str))
		return false;

	// check length:
	if (str.length > 2)
		return false;

	// check for 1..9
	if (str.length == 1 && str >= 1 && str <= 9)
		return true;
	// check for 10..24
	if (str.length == 2 && str >= 10 && str <= 24)
		return true;
	// check for 6W:
	if (str == "6W")
		return true;

	// postcode not ok:
	return false;
}


function isValidPostcode(str, countryCode)
{
	// check str:
	if (!isValidString(str))
		return false;

	// check country code:
	if (!isValidCountryCode(countryCode))
		return false;

	// check postcode pattern:
	countryCode = countryCode.toUpperCase();
	if (countryCode == "AU" || countryCode == "NZ")
		return matchesPattern(str, "9999");
	else if (countryCode == "US")
		return isValidZipcode(str);
	else if (countryCode == "CA")
		return matchesPattern(str, "Z9Z 9Z9");
	else if (countryCode == "MX")
		return matchesPattern(str, "99999");
	else if (countryCode == "BR")
		return matchesPattern(str, "99999-999");
	else if (countryCode == "UK")
		return isValidUKPostcode(str);
	else if (countryCode == "NL")
		return matchesPattern(str, "9999ZZ") || matchesPattern(str, "9999 ZZ");
	else
		return false;
}


/////////////////////////////////////////////////////////////////////////////////////////
// check credit card numbers:

// original source of these functions:
// author: Chris Nott (chris@dithered.com)
// http://www.dithered.com/javascript/form_validation/index.html


// returns str with any spaces removed:
function removeSpaces(str)
{
	var result = "";
	var ch;
	for (var j = 0; j < str.length; j++)
	{
		ch = str.charAt(j);
		if (ch != ' ')
			result += ch;
	}
	return result;
}


// Returns a checksum digit for a number using mod 10
function calcMod10(number)
{
	// convert number to a string and check that it contains only digits
	// return -1 for illegal input
	number = "" + number;
	number = removeSpaces(number);
	if (!isAllDigits(number))
		return -1;
	
	// calculate checksum using mod10
	var checksum = 0;
	var isOdd;
	for (var i = number.length - 1; i >= 0; i--)
	{
		isOdd = ((number.length - i) % 2 != 0) ? true : false;
		digit = number.charAt(i);
		
		if (isOdd)
			checksum += parseInt(digit);
		else
		{
			var evenDigit = parseInt(digit) * 2;
			if (evenDigit >= 10)
				checksum += 1 + (evenDigit - 10);
			else
				checksum += evenDigit;
		}
	}
	return (checksum % 10);
}


// Check that a credit card number is valid based using the LUHN formula (mod10 is 0)
function isValidCreditCardNumber(ccn, cctype)
{
	// NB The cctype (credit card type) parameter is optional.

	// check ccn:
	if (!isValidString(ccn))
		return false;

	// check length:
	ccn = removeSpaces(ccn);
	if (ccn.length < 13 || ccn.length > 16)
		return false;

	// check that ccn is all digits:
	if (!isAllDigits(ccn))
		return false;

	// check mod10:
	if (calcMod10(ccn) != 0)
		return false;

	// check digits match credit card type (if provided)
	if (isValidString(cctype))
	{
		cctype = cctype.toLowerCase();
		var first2digits = ccn.substring(0, 2);
		var first4digits = ccn.substring(0, 4);

		if (cctype == 'visa' && ccn.substring(0, 1) == "4" &&
			(ccn.length == 16 || ccn.length == 13 ))
			return true;
		else if (cctype == 'mastercard' && ccn.length == 16 &&
			(first2digits == '51' || first2digits == '52' || first2digits == '53' ||
			first2digits == '54' || first2digits == '55'))
			return true;
		else if (cctype == 'american express' && ccn.length == 15 && 
			(first2digits == '34' || first2digits == '37'))
			return true;
		else if (cctype == 'diners club' && ccn.length == 14 && 
			(first2digits == '30' || first2digits == '36' || first2digits == '38'))
			return true;
		else if (cctype == 'discover' && ccn.length == 16 && first4digits == '6011')
			return true;
		else if (cctype == 'enroute' && ccn.length == 15 && 
			(first4digits == '2014' || first4digits == '2149'))
			return true;
		else if (cctype == 'jcb' && ccn.length == 16 &&
			(first4digits == '3088' || first4digits == '3096' || first4digits == '3112' ||
			first4digits == '3158' || first4digits == '3337' || first4digits == '3528'))
			return true;
		else
			return false;
	}

	// all ok:
	return true;
}
//-->
