/*---------------------------------------------------------------------------------

Generic Form Validation Script

Author: 	Tom Walter
Date:		29/05/01
Language: 	Javascript 1.2
			(Note: won't work in earlier js versions because they don't support regular expressions.)
Tested on:	IE5.5, IE6.0, NS4.5, NS6, NS7


Usage------------------------------------------------------------------------------

Can be used in partnership with the class_formValidate.asp file which validates server side also (this
is useful because the client side script can be easily bypassed by hackers trying to send data
that will break databases etc.) In the case that you do this, remove the snippet at the end of the
formValidate function in this file so that it doesn't strip the validate data from the field names.

For the field's name= value enter the following properties like this:

"real_name;Display Name;required/optional;datatype;min;max"

real_name:	The name of the field, this is what will be passed through to the CGI script

Display Name:	The display name of the field, used in the error popup

required/
optional:	Whether the field is required or optional.

Datatype:	Used for validation. currently supported values are:
			'email' - checks for name@company.com format
			'card' - checks for a valid credit card.
			'text' - checks for alpha chars and whitespace only
			'numeric' - checks for numbers only
			'number' - checks for a number (int or float) in the range min-max (see below)
			'alphanumeric' - checks for letters and numbers
			'alphanumericspace' - checks for letters and numbers and spaces!
			'file' - matches any valid filename (windows)
			'imagefile' - checks for a valid filename with an extension of .jpg, .jpeg or .gif
			'worddoc' - ditto for .doc
			'pdffile' - ditto for .pdf
			'zipfile' - ditto for .zip
			'nohtml' - checks that the input does not contain html.
			'postcode' - checks for valid australian postcode. you must include the optional postcodes.js
			'notype' - don't check the datatype. Use this for checkboxes, radio buttons, select boxes etc.
			'date' - a valid australian date

			These data types are only necessary when using the ASP validator, as it can't auto-detect the field type
			'select'
			'multi-select'
			'checkbox'
			'radiobuttons'

Min:		An int to specify an minimum length for the field. To make a field an exact length,
			make the max of the field equal to this value.
			If the datatype is 'number', checks that the number is greater than this.
			If it is a multi-select box, checks that at least this many selections are made.

Max:		An int to specify an maximum length for the field. A non-positive value means no max length.
			If the datatype is 'number', checks that the number is less than this.
			If it is a multi-select box, checks that less than this many selections are made.


Notes:
- If a select box is marked as required, it checks to see that the selected option has a value
other than "null". So if you are including options that are simply titles, separators or prompts
like 'Select an option...', make sure you explicitely set value="null" for those options.
- For multiple select boxes, you can use the min/max values to make sure a particular number
of options are selected. As for single selects, options are not considered selected if their value="null"



---------------------------------------------------------------------------------*/




function isCreditCard(cardNumber) {
  /* Validates a credit card checksum */

  if (cardNumber.length > 19) return false; /* Encoding only works on cards with less than 19 digits */

  sum = 0; mul = 1; l = cardNumber.length;
  for (j = 0; j < l; j++) {
    digit = cardNumber.substring(l-j-1,l-j);
    tproduct = parseInt(digit ,10)*mul;
    if (tproduct >= 10) sum += (tproduct % 10) + 1;
    else sum += tproduct;
    if (mul == 1) mul++;
    else mul--;
  }

  return ((sum % 10) == 0);
}



var g_bError = false;
var g_iFirstError = -1;
var g_sError = 'Sorry, there were problems with your form:\n\n';
var g_bPostCodeValidatorIncluded;

function addError(sMessage, iPosition) {
	g_sError += '<tr><td>' + sMessage + '</tr></td>';
	if (!g_bError) {
		g_iFirstError = iPosition;
		g_bError = true;
	}
}



function validateForm(oForm) {

	g_bError = false;
	g_iFirstError = -1;
	g_sError = '<table><tr><th>Sorry, there were problems with your form:</th></tr>';

	var aValidatedRadios = new Array();


	/* regular expressions */
	var reValidEmail = /^.+\@.+\..+$/;
	var reWhitespace = /^\s+$/;
	var reInteger = /^\d+$/;
	var reNumber = /^[\d.-]+$/;
	var reAlphaOrSpace = /^[a-zA-Z\s]+$/;
	var reAlphanumeric = /^[a-zA-Z0-9]+$/;
	var reAlphanumericSpace = /^[a-zA-Z0-9\s]+$/;
	var reImagefile = /^[^\*\?"<>\|]+\.(jpg|jpeg|gif)$/i;
	var reFile = /^[^\*\?\"<>\|]+$/;
	var reWordfile = /^[^\*\?\"<>\|]+\.doc$/i;
	var rePdfFile = /^[^\*\?\"<>\|]+\.pdf$/i;
	var reWordOrPdf = /^[^\*\?\"<>\|]+\.(doc|pdf)$/i;
	var reZipFile = /^[^\*\?\"<>\|]+\.zip$/i;
	var reHTML = /^.*(<.+>).*$/;


	/* iterate through the form */
	for (i=0; i < oForm.elements.length; i++) {

		var bElemReq = false;
		var sElemType = '';
		var iElemMin = 0;
		var iElemMax = 0;
		var sElemName = '';
		var sElemDisplayName = '';

		oElem = oForm.elements[i];

		if ((oElem.type != 'button') && (oElem.type != 'submit') && (oElem.type != 'reset') && (oElem.type != 'hidden')) {

			/* parse the field's name to find out the required properties */
			aProperties = oElem.getAttribute('validityCheck').split(';');

			sElemName = aProperties[0];
			sElemDisplayName = (aProperties[1] != null && aProperties[1] != '') ? aProperties[1] : sElemName;
			bElemReq = (aProperties[2] == 'required') ? true : false;
			sElemType = (aProperties[3] != null && aProperties[3] != '') ? aProperties[3] : "notype";
			iElemMin = (aProperties[4] != null && aProperties[4] != '') ? Number(aProperties[4]) : 0;
			iElemMax = (aProperties[5] != null && aProperties[5] != '') ? Number(aProperties[5]) : -1;


			/* test what type of form element we are working with */
			switch(oElem.type) {

				case 'checkbox':
					if (bElemReq && !oElem.checked) {
						addError( sElemDisplayName + ' must be checked.', i);
					}
					break;

				case 'radio':
					if (bElemReq) {
						/* make sure we haven't validated this radio button set already */
						var bRadioValidated = false;
						for (j=0; j < aValidatedRadios.length; j++) {
							if (aValidatedRadios[j] == oElem.name) {
								bRadioValidated = true;
								break;
							}
						}
						if (!bRadioValidated) {
							var bRadioChecked = false;
							var aRadioValues = oForm.elements[oElem.name]; /* get all radio buttons with the same name */
							for (j=0; j < aRadioValues.length; j++) {
								if (aRadioValues[j].checked) {
									bRadioChecked = true;
									break;
								}
							}
							if (!bRadioChecked) {
								addError( sElemDisplayName + ' needs an option selected.', i);
							}
							aValidatedRadios.push(oElem.name); /* add this set of radios to the list of validated radios */
							/*aValidatedRadios = aValidatedRadios + [oElem.name];*/
						}
					}
					break;

				case 'select-one':
					if (bElemReq) {
						var bSelection = false;
						if (oElem.selectedIndex != -1) {
							if (oElem.options[oElem.selectedIndex].text != "") {
								if (oElem.options[oElem.selectedIndex].value != "null") {
									bSelection = true;
								}
							}
						}
						if (!bSelection) {
							addError( sElemDisplayName + ' needs an option selected.', i);
						}
					}
					break;

				case 'select-multiple':
					var aSelections = new Array();
					for (j=0; j < oElem.options.length; j++) {
						if (oElem.options[j].selected) {
							if (oElem.options[j].text != "") {
								if (oElem.options[j].value != "null") {
									aSelections.push(oElem.options[j]);
								}
							}
						}
					}
					if (aSelections.length > 0) {
						if (aSelections.length < iElemMin) {
							addError( sElemDisplayName + ' needs at least ' + iElemMin + ' options selected, you selected ' + aSelections.length, i);
						}
						if (iElemMax != -1) {
							if (aSelections.length > iElemMax) {
								addError( sElemDisplayName + ' can have a maximum of ' + iElemMax + ' options selected, you selected ' + aSelections.length + '.', i);
							}
						}
					} else if (bElemReq) {
						addError( sElemDisplayName + ' needs at least ' + iElemMin + ' options selected.', i);
					}
					break;

				default:
					/* it must be a textfield of some sort */

					/* test if the field has something in it */
					if ((oElem.value.length > 0) && !reWhitespace.test(oElem.value))  {

						/* test if the field is less than the minimum length (if there is one) */
						if ((sElemType != "number") && (oElem.value.length < iElemMin)) {
							addError( sElemDisplayName + ' must be at least ' + iElemMin + ' characters long. You entered ' + oElem.value.length + '.', i);
						}


						/* test if the field is more than the maximum length (if there is one) */
						if ((sElemType != "number") && (iElemMax > 0) && (oElem.value.length > iElemMax)) {
							addError( sElemDisplayName + ' must be less than ' + iElemMax + ' characters. You entered ' + oElem.value.length + '.', i);
						}


						/* test if the input matches the required datatype for the field */
						switch (sElemType) {

							case "email":
								if (!reValidEmail.test(oElem.value)) {
									addError( sElemDisplayName + ' should be in the format \'name@company.com\'.', i);
								}
								break;

							case "card":
								if (!reInteger.test(oElem.value)) {
									addError(sElemDisplayName + ' should contain numbers only, without any spaces or dashes.', i);
								} else if (!isCreditCard(oElem.value)) {
									addError( sElemDisplayName + ' is not a valid credit card number.', i);
								}
								break;

							case "text":
								if (!reAlphaOrSpace.test(oElem.value)) {
									addError( sElemDisplayName + ' should contain letters only.', i);
								}
								break;

							case "numeric":
								if (!reInteger.test(oElem.value)) {
									addError( sElemDisplayName + ' should contain numerals only.', i);
								}
								break;

							case "number":
								if (!reNumber.test(oElem.value)) {
									addError( sElemDisplayName + ' should contain a number.', i);
								} else {
									var iNum = parseFloat(oElem.value);
									if (isNaN(iNum)) {
										addError( sElemDisplayName + ' should contain a number.', i);
									} else if ((iNum < iElemMin) || ((iNum > iElemMax) && (iElemMax > -1))) {
										addError( sElemDisplayName + ' should contain a number in the range ' + iElemMin + '-' + iElemMax + '.', i);
									}
								}
								break;

							case "alphanumeric":
								if (!reAlphanumeric.test(oElem.value)) {
									addError( sElemDisplayName + ' should contain letters and numbers only.', i);
								}
								break;

							case "alphanumericspace":
								if (!reAlphanumericSpace.test(oElem.value)) {
									addError( sElemDisplayName + ' should contain letters, numbers and spaces only.', i);
								}
								break;

							case "imagefile":
								if (!reImagefile.test(oElem.value)) {
									addError( sElemDisplayName + ' should be a valid filename in JPG or GIF format (*.jpg, *.jpeg, or *.gif).', i);
								}
								break;

							case "file":
								if (!reFile.test(oElem.value)) {
									addError( sElemDisplayName + ' should be a valid filename (it cannot contain / * ? " < > or |).', i);
								}
								break;

							case "worddoc":
								if (!reWordfile.test(oElem.value)) {
									addError( sElemDisplayName + ' should be a valid filename in MS Word format (*.doc).', i);
								}
								break;

							case "pdffile":
								if (!rePdfFile.test(oElem.value)) {
									addError( sElemDisplayName + ' should be a valid filename in PDF format (*.pdf).', i);
								}
								break;

							case "zipfile":
								if (!reZipFile.test(oElem.value)) {
									addError( sElemDisplayName + ' should be a valid filename in ZIP format (*.zip).', i);
								}
								break;

							case "nohtml":
								if (reHTML.test(oElem.value)) {
									addError( sElemDisplayName + ' can not contain HTML code.', i);
								}
								break;

							case "postcode":
								if (!reInteger.test(oElem.value)) {
									addError(sElemDisplayName + ' should contain numerals only.', i);
								} else if (g_bPostCodeValidatorIncluded) {
									if (!isValidPostCode(oElem.value)) {
										addError( sElemDisplayName + ' is not a valid Australian postcode.', i);
									}
								}
								break;

							case "date":

								var oDate;
								var aDateParts;
								if ((oElem.value.indexOf('/') > -1) || (oElem.value.indexOf('-') > -1)) {
									if (oElem.value.indexOf('/') > -1) {
										aDateParts = oElem.value.split('/');
									} else if (oElem.value.indexOf('-') > -1) {
										aDateParts = oElem.value.split('-');
									}
									if (aDateParts.length == 3) {
										aDateParts[1] = parseInt(aDateParts[1], 10) - 1;
										oDate = new Date(aDateParts[2], aDateParts[1], aDateParts[0]);
									}
								} else {
									oDate = new Date(Date.parse(oElem.value));
								}

								/* can only check date validity if they entered in dd/mm/yyyy format */
								if ((aDateParts != null) && (aDateParts.length == 3)) {
									if (!((parseInt(aDateParts[0], 10) == oDate.getDate()) && (parseInt(aDateParts[1], 10) == oDate.getMonth()) && (parseInt(aDateParts[2], 10) == oDate.getFullYear()))) {
										addError( sElemDisplayName + ' must be a valid date.', i);
									}
								} else if (isNaN(oDate)) {
									addError( sElemDisplayName + ' must be a valid date.', i);
								} else {
									/* assume the date is valid and change it to dd/mm/yyyy format */
									oElem.value = oDate.getDate() + '/' + (oDate.getMonth() + 1) + '/' + oDate.getFullYear();
								}

								break;

							case "notype":
								/* do nothing */
								break;

							case "select":
								/* do nothing */
								break;

							case "multi-select":
								/* do nothing */
								break;

							case "checkbox":
								/* do nothing */
								break;

							case "radiobuttons":
								/* do nothing */
								break;

							default:
								alert('Validation error: Unknown data type specified for ' + sElemDisplayName + ' field.');
						}

					} else if (bElemReq) {
						addError( sElemDisplayName + ' is required.', i);
					}

			} /* end field type switch */
		}
	}

	/* If there is errors, alert the user, otherwise let the form submit */
	if (g_bError) {
		return g_sError+'<tr><td style=\"text-align:center;font-weight:bold\">Click To Close</td></tr></table>';
	} else {
		/* make sure we rename the form elements coherently so as not to confuse the cgi script */
		/*
		for (i=0; i < oForm.elements.length; i++) {
			oForm.elements[i].name = oForm.elements[i].name.split(';')[0];
		}
		*/
		return "good";
	}

}
