/**
 * Signup Framework Validation Library
 *
 * This package contains validation functions and cleaning functions.
 *
 * If you add a function to this file, then you MUST add a matching function to
 * functions.validation.php. Matching function MUST have the same name, take
 * the same input parameters, and return (as closely as possible) the same 
 * results as the function in this file.
 *
 * @package signupFramework
 * @subpackage validation
 */

/**
 * Validate and clean a North America telephone number.
 * Returns cleaned number or false if the number is invalid.
 *
 * @author Jacob Allred <jacob@jacoballred.com>
 * @param string $phone
 * @return mixed
 */
function validate_PhoneNumber(phone, onlyTollFree, required){
	if(onlyTollFree==null){
		onlyTollFree = false;
	}
	
	if(required==null){
		required = false;
	}
	
	// Remove non-numeric characters
	phone = validate_ReturnNumeric(phone);
	
	if(phone.length == 0 && required == false){
		return true;
	}
	
	// Remove first character if it is a 1 (country code) or 0 (invalid)
	while(phone.charAt(0) == '1' || phone.charAt(0) == '0'){
		phone = phone.substring(phone,1);
	}
	
	// Check for N11 codes
	if(phone.length >= 3){
		if(phone.substring(1,3) == '11'){
			return false;
		}
	}
	
	// Verify length and return value
	if(phone.length == 10){
		if(onlyTollFree == true){
			var ft = phone.substring(0,3);
			if(
				ft != '800' &&
				ft != '888' &&
				ft != '877' &&
				ft != '866'
			){
				return false;
			}
		}
		if(phone.charAt(3) == '1' || phone.charAt(3) == '0' ){
			return false;
		}		
		return phone;
	}else{
		return false;
	}
}

/**
 * Strip non-numeric characters.
 *
 * @author Jacob Allred <jacob@jacoballred.com>
 * @param string $string
 * @return string
 */
function validate_ReturnNumeric(string){
	re = /[^0-9]/g;
	string = string.replace(re, '');
	return string;
}

/**
 * Strip non-alphanumeric characters.
 *
 * @author Jacob Allred <jacob@jacoballred.com>
 * @param string $string
 * @return string
 */
function validate_ReturnAlphanumeric(string){
	re = /[^A-Za-z0-9]/g;
	string = string.replace(re, '');
	return string;
}

/**
 * Strip non-alpha characters.
 *
 * @author Jacob Allred <jacob@jacoballred.com>
 * @param string $string
 * @return string
 */
function validate_ReturnAlpha(string){
	re = /[^A-Za-z]/g;
	string = string.replace(re, '');
	return string;
}

/**
 * Strip non-alpha/space characters.
 *
 * @author Jacob Allred <jacob@jacoballred.com>
 * @param string $string
 * @return string
 */
function validate_ReturnAlphaSpace(string){
	re = /[^A-Za-z ]/g;
	string = string.replace(re, '');
	return string;
}

/**
 * Strip non-alphanumeric/space/hyphen characters.
 *
 * @author Jacob Allred <jacob@jacoballred.com>
 * @param string $string
 * @return string
 */
function validate_ReturnAlphanumericSpaceHyphen(string){
	re = /[^A-Za-z0-9 \-]/g;
	string = string.replace(re, '');
	return string;
}

/**
 * Strip non-alphanumeric/space characters.
 *
 * @author Jacob Allred <jacob@jacoballred.com>
 * @param string $string
 * @return string
 */
function validate_ReturnAlphanumericSpace(string){
	re = /[^A-Za-z0-9 ]/g;
	string = string.replace(re, '');
	return string;
}

/**
 * Validate ABA Routing number is formatted properly.
 * Does not strip invalid characters. Returns true or false.
 *
 * @author Jacob Allred <jacob@jacoballred.com>
 * @param string $aba_code
 * @param boolean $allowTest
 * @return boolean
 */
function validate_ABACode(aba_code, allowTest){
	var i, n;
	
	if(allowTest==null){
		allowTest = false;
	}
	
	// Check for test routing number
	if(aba_code == '123456780' && allowTest == false){
		return false;
	}
	
	// Check for invalid characters
	if(aba_code != validate_ReturnNumeric(aba_code)){
		return false;
	}
 
	// Check length
	if(aba_code.length != 9){
		return false;
	}
   
   // Now run through each digit and calculate the total.
   n = 0;
   for (i = 0; i < aba_code.length; i += 3) {
	n += parseInt(aba_code.charAt(i),     10) * 3
	  +  parseInt(aba_code.charAt(i + 1), 10) * 7
	  +  parseInt(aba_code.charAt(i + 2), 10);
   }
 
   // If the resulting sum is an even multiple of ten (but not zero),
   // the aba routing number is good.
   if (n != 0 && n % 10 == 0){
		return true;
   }else{
		return false;
   }
}

/**
 * Validate alpha only
 *
 * @author Jacob Allred <jacob@jacoballred.com>
 * @param string $string
 * @param int $maxLength
 * @param int $minimumLength
 * @return boolean
 */
function validate_Alpha(string, maxLength, minimumLength){
	var count;
	
	// Set defaults
	if(maxLength==null){maxLength=0;}
	if(minimumLength==null){minimumLength=0;}
	
	count = string.match(/[^A-Za-z]/g);
	if(count == null){        
		// Return false if too long or too short
		if((maxLength > 0 && string.length > maxLength) || (string.length < minimumLength)){
			return false;
		}

		return true;
	}else{
		return false;
	}
}

/**
 * Validate alpha/space only
 *
 * @author Jacob Allred <jacob@jacoballred.com>
 * @param string $string
 * @param int $maxLength
 * @param int $minimumLength
 * @return boolean
 */
function validate_AlphaSpace(string, maxLength, minimumLength){
	var count;
	
	// Set defaults
	if(maxLength==null){maxLength=0;}
	if(minimumLength==null){minimumLength=0;}
	
	count = string.match(/[^A-Za-z ]/g);
	if(count == null){        
		// Return false if too long or too short
		if((maxLength > 0 && string.length > maxLength) || (string.length < minimumLength)){
			return false;
		}

		return true;
	}else{
		return false;
	}
}

/**
 * Validate alphanumeric only
 *
 * @author Jacob Allred <jacob@jacoballred.com>
 * @param string $string
 * @param int $maxLength
 * @param int $minimumLength
 * @return boolean
 */
function validate_Alphanumeric(string, maxLength, minimumLength){
	var count;
	string = string+'';
	
	// Set defaults
	if(maxLength==null){maxLength=0;}
	if(minimumLength==null){minimumLength=0;}
	
	count = string.match(/[^A-Za-z0-9]/g);
	if(count == null){        
		// Return false if too long or too short
		if((maxLength > 0 && string.length > maxLength) || (string.length < minimumLength)){
			return false;
		}

		return true;
	}else{
		return false;
	}
}

/**
 * Validate alphanumeric/space/hyphen only.
 * This was developed for use with Intelliverse's API.
 *
 * @author Jacob Allred <jacob@jacoballred.com>
 * @param string $string
 * @param int $maxLength
 * @param int $minimumLength
 * @return boolean
 */
function validate_AlphanumericSpaceHyphen(string, maxLength, minimumLength){
	var count;
	
	// Set defaults
	if(maxLength==null){maxLength=0;}
	if(minimumLength==null){minimumLength=0;}
	
	count = string.match(/[^A-Za-z0-9 \-]/g);
	if(count == null){        
		// Return false if too long or too short
		if((maxLength > 0 && string.length > maxLength) || (string.length < minimumLength)){
			return false;
		}

		return true;
	}else{
		return false;
	}
}

/**
 * Validate alphanumeric/space only.
 * This was developed for use with Intelliverse's API.
 *
 * @author Jacob Allred <jacob@jacoballred.com>
 * @param string $string
 * @param int $maxLength
 * @param int $minimumLength
 * @return boolean
 */
function validate_AlphanumericSpace(string, maxLength, minimumLength){
	var count;
	
	// Set defaults
	if(maxLength==null){maxLength=0;}
	if(minimumLength==null){minimumLength=0;}
	
	count = string.match(/[^A-Za-z0-9 ]/g);
	if(count == null){        
		// Return false if too long or too short
		if((maxLength > 0 && string.length > maxLength) || (string.length < minimumLength)){
			return false;
		}

		return true;
	}else{
		return false;
	}
}

/**
 * Validate numeric only
 *
 * @author Jacob Allred <jacob@jacoballred.com>
 * @param string $string
 * @param int $maxLength
 * @param int $minimumLength
 * @return boolean
 */
function validate_Numeric(string, maxLength, minimumLength){
	if(string==null){
		return false;
	}else{
		string = string+'';
	}
	
	var count;
	
	// Set defaults
	if(maxLength==null){maxLength=0;}
	if(minimumLength==null){minimumLength=0;}
	
	count = string.match(/[^0-9]/g);
	if(count == null){
		// Return false if too long or too short
		if((maxLength > 0 && string.length > maxLength) || (string.length < minimumLength)){
			return false;
		}

		return true;
	}else{
		return false;
	}
}

/**
 * Validate length
 *
 * @author Jacob Allred <jacob@jacoballred.com>
 * @param string $string
 * @param int $maxLength
 * @param int $minimumLength
 * @return boolean
 */
function validate_Length(string, maxLength, minimumLength){
	if(string==null){
		return false;
	}else{
		string = string+'';
	}
	
	// Set defaults
	if(maxLength==null){maxLength=0;}
	if(minimumLength==null){minimumLength=0;}
	
	// Return false if too long or too short
	if((maxLength > 0 && string.length > maxLength) || (string.length < minimumLength)){
		return false;
	}

	return true;
}

/**
 * Validate and clean a CA zip code.
 * Returns cleaned zip code or false if invalid.
 *
 * @author Jacob Allred <jacob@jacoballred.com>
 * @param string $zip
 * @param int $returnLength
 * @return mixed
 */
function validate_ZipCodeCA(zip){
	// Remove non-alphanumeric characters
	zip = validate_ReturnAlphanumeric(zip);
	
	// Verify length
	if(zip.length != 6){
		return false;
	}
	
	// Force uppercase
	zip = zip.toUpperCase();

	// Verify zip follows CA specifications
	if(zip.match(/^([a-ceghj-npr-tv-z]){1}[0-9]{1}[a-ceghj-npr-tv-z]{1}[0-9]{1}[a-ceghj-npr-tv-z]{1}[0-9]{1}$/i) != null){
		return zip;
	}else{
		return false;
	}
}

/**
 * Validate and clean a US zip code.
 * Returns cleaned zip code or false if invalid.
 *
 * @author Jacob Allred <jacob@jacoballred.com>
 * @param string $zip
 * @param int $returnLength
 * @return mixed
 */
function validate_ZipCodeUS(zip, returnLength){
	if(returnLength==null){returnLength=9;}
	
	// Remove non-numeric characters
	zip = validate_ReturnNumeric(zip);
	
	// Verify length
	if(zip.length != 5 && zip.length != 9){
		return false;
	}else{
		// Truncate to ZIP5 if needed, otherwise return full zip
		if(returnLength == 5){
			return zip.substring(0,5);
		}else{
			if(zip.length == 9){
				return zip.substring(0,5) + '-' + zip.substring(5);
			}else{
				return zip;
			}
		}
	}
}

/**
 * Verify two variables match.
 *
 * @author Jacob Allred <jacob@jacoballred.com>
 * @param mixed $string1
 * @param mixed $string2
 * @param boolean $strictMatch Match variable content and datatype
 * @return boolean
 */
function validate_Matching(string1, string2, strictMatch){
	if(strictMatch == null){strictMatch=false;}
	
	if(strictMatch == true){
		if(string1 === string2){
			return true;
		}else{
			return false;
		}
	}else{
		if(string1 == string2){
			return true;
		}else{
			return false;
		}
	}
}

/**
 * Validate email address using a regular expression.
 * We do not check to see if MX records exist because RFC 2821 says that when
 * no mail exchangers are listed, hostname itself should be used as the only
 * mail exchanger with a priority of 0.
 *
 * @author Jacob Allred <jacob@jacoballred.com>
 * @param string $email
 * @return boolean
 */
function validate_EmailAddress(email){
	// Regular expression to weed out obvious bad email addresses
	if(email.match(/^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,})$/i) != null){
		return true;
	}else{
		return false;
	}
}

/**
 * Validate credit card number and return card type.
 * Optionally you can validate if it is a specific type.
 *
 * @author Jacob Allred <jacob@jacoballred.com>
 * @param string $ccnumber
 * @param string $cardtype
 * @param string $allowTest
 * @return mixed
 */
function validate_CreditCard(ccnumber, cardtype, allowTest){
	if(allowTest==null){allowTest=false;}
	
	// Check for test cc number
	if(allowTest == false && ccnumber == '4111111111111111'){
		return false;
	}
	
	var cc = new Object;
	ccnumber = validate_ReturnNumeric(ccnumber);
	cc.ccnum = ccnumber;
	cc.valid = false;
	
	// Determine card type    
	var visaRegEx = /^4\d{3}-?\d{4}-?\d{4}-?\d{4}$/;
	var mastercardRegEx = /^5[1-5]\d{2}-?\d{4}-?\d{4}-?\d{4}$/;
	var discoverRegEx = /^6011-?\d{4}-?\d{4}-?\d{4}$/;
	var amexRegEx = /^3[4,7]\d{13}$/;

	if(visaRegEx.test(ccnumber)){
		cc.type = 'visa';
	}else if(mastercardRegEx.test(ccnumber)){
		cc.type = 'mastercard';
	}else if(discoverRegEx.test(ccnumber)){
		cc.type = 'discover';
	}else if(amexRegEx.test(ccnumber)){
		cc.type = 'amex';
	}else{
		return false;
	}

	// Match against specific card type if requested
	if(cardtype != null && cc.type != cardtype){
		return false;
	}
	
	// Validate card
	s = ccnumber;
	if((cc.type == 'visa' && (s.length == 13 || s.length == 16)) || (cc.type == 'mastercard' && s.length == 16) || (cc.type == 'amex' && s.length == 15) || (cc.type == 'discover' && s.length == 16)){
		var i, n, c, r, t;

		// First, reverse the string and remove any non-numeric characters.
		r = "";
		for (i = 0; i < s.length; i++) {
			c = parseInt(s.charAt(i), 10);
			if (c >= 0 && c <= 9)
				r = c + r;
		}

		// Check for a bad string.
		if (r.length <= 1){
			return cc;
		}

		// Now run through each single digit to create a new string. Even digits
		// are multiplied by two, odd digits are left alone.
		t = "";
		for (i = 0; i < r.length; i++) {
			c = parseInt(r.charAt(i), 10);
			if (i % 2 != 0)
				c *= 2;
			t = t + c;
		}

		// Finally, add up all the single digits in this string.
		n = 0;
		for (i = 0; i < t.length; i++) {
			c = parseInt(t.charAt(i), 10);
			n = n + c;
		}

		// If the resulting sum is an even multiple of ten (but not zero), the
		// card number is good.
		if (n != 0 && n % 10 == 0){
			cc.valid = true;
			return cc;
		}else{
			return cc;
		}
	}else{
		return cc;
	}
	
}

/**
 * Validate a date is in the future
 *
 * @author Jacob Allred <jacob@jacoballred.com>
 * @param int $year
 * @param mixed $month Accepts 1, Jan, january...
 * @param int $day
 * @param boolean $inclusive True to treat today's date as being in the future
 * @return boolean
 */
function validate_FutureDate(year, month, day, inclusive){
	if(inclusive == null){inclusive = true;}

	var d = new Date();
	
	// Make sure year is a number and that year is at least the current year
	if(year == null){
		year = d.getFullYear();
	}else if(validate_Numeric(year) == false || year < d.getFullYear()){
		return false;
	}
	year = parseInt(year);
	
	// Make sure month is a number
	var months = new Array(
		'january','february','march','april','may','june','july','august','september','october','november','december'
	);
	var monthsShort = new Array(
		'jan','feb','mar','apr','may','jun','jul','aug','sep','oct','nov','dec'
	);
	if(month == null){
		var monthNumber = d.getMonth();
	}else if(validate_Numeric(month) == true && month >= 0 && month <= 11){
		monthNumber = parseInt(month)-1;
	}else{
		// If it is a string, we need to see if it is a valid month name
		month = month.toLowerCase();
		month = month.replace(/^\s+|\s+$/g, '');
		var arLen = months.length;
		for(var i=0; i<arLen; ++i){
			if(months[i] == month){
				var monthNumber = i;
				break;
			}
		}
		
		if(monthNumber == null){
			var arLen = monthsShort.length;
			for(var i=0; i<arLen; ++i){
				if(monthsShort[i] == month){
					var monthNumber = i;
					break;
				}
			}		
		}
		
		if(monthNumber == null){
			return false;
		}
	}
	
	// If day is null, then we are only matching month and year
	if(day == null){
		var today = new Date();
		today.setFullYear(d.getFullYear(),d.getMonth(),1);
		var input = new Date();
		input.setFullYear(year, monthNumber, 1);
	}else{
		// Make sure day is a number and is valid for the month we are in
		if(validate_Numeric(day) == false){
			return false;
		}else{
			day = parseInt(day);
			var dt = new Date();
			dt.setFullYear(year, monthNumber, day);
			if(day != dt.getDate() || monthNumber != dt.getMonth()){
				return false;
			}
		}
		
		var today = new Date();
		today.setFullYear(d.getFullYear(),d.getMonth(),d.getDate());
		var input = new Date();
		input.setFullYear(year, monthNumber, day);
	}
	
	// See if input is greater than or equal to today
	if((inclusive == true && input.getTime() >= today.getTime()) || (inclusive == false && input.getTime() > today.getTime())){
		return true;
	}else{
		return false;
	}
}

/**
 * Validate that a username is in a good format
 *
 * @author Jacob Allred <jacob@jacoballred.com>
 * @param string $username
 * @return boolean
 */
function validate_Username(username){
	if(username == null){ return false; }
	
	// Business rule: usernames must be 5 to 25 characters, alphanumeric
	// WebBalanced: 1 <= $username <= 25
	// Intelliverse: 5 <= $username <= 30
	if(validate_Alphanumeric(username,25,5) == true){
		return true;
	}else{
		return false;
	}
}

/**
 * Validate that a password is in a good format
 *
 * @author Jacob Allred <jacob@jacoballred.com>
 * @param string $password
 * @return boolean
 */
function validate_Password(password){
	if(password == null){ return false; }
	
	// Business rule: passwords must be 6 to 15 characters, alphanumeric
	if(validate_Alphanumeric(password,15,6) == true){
		return true;
	}else{
		return false;
	}
}
