// ReAction Server - Support functions for XML based validation
// All code must comply with W3C DOM Level 1 Standard to ensure maximum cross browser support
//
// (c) Tikit Limited
//
// Modifications:
// 10Mar05 - check for existence of maxLength attribute, only do check if it exists
function rsValidate(form)
{
// Get the node for this reaction from the XML island
// The form name will be 'RS' followed by the Reaction ID, e.g. RSREACTION1
var reactionXmlId = String(form.name + 'XML');
var reactionNode = rsGetReactionNode(reactionXmlId);
var field, ctrlNode, submitCtrl;
var warnMsg = '';
var ctrlLabel = '';
var checkingListFieldName = ''; // If checking a list set the fieldname.
var checkingListFieldOk = false; // If checking a list set the fieldname.
var ccTypeCtrl, ccNumberCtrl, ccCvvCtrl;
var ccErrorNo = 0;
// Array of possible credit card errors
var ccErrors = new Array ()
ccErrors [0] = "Unknown card type";
ccErrors [1] = "No card number provided";
ccErrors [2] = "Credit card number is in invalid format";
ccErrors [3] = "Credit card number is invalid";
ccErrors [4] = "Credit card number has an inappropriate number of digits";
ccErrors [5] = "Warning! This credit card number is associated with a scam attempt";
ccErrors [6] = "CVV is in invalid format";
// Process each element in the form and match it with control info from the XML island
if (reactionNode){
for (var i=0; i< form.length; i++) {
field=form.elements[i];
// SIR 23024 - If check list or radio buttons, warn no value set when moving to next control
if (checkingListFieldName != "" && checkingListFieldName != field.name) {
if (!checkingListFieldOk && rsGetChildNodeValue(ctrlNode, 'required').toLowerCase() == 'yes') {
warnMsg += '\n- A value is required for \'' + ctrlLabel + '\'.';
}
checkingListFieldOk = false;
checkingListFieldName = "";
}
// look for and hang on to the submit control, we'll use it later
if (field.name == 'SUBMIT' || field.type == 'submit')
submitCtrl = field;
// If this was a decline\cancel button on a payment form then just submit. Code
// on server side will handle getting us back to the event and won't do payment
if (field.name == 'buttonChoice') {
if (field.value == '500') {
if (form.action == 'RSPayFlow.asp') {
return true;
}
}
}
// see if there's a node for this control
ctrlNode = rsGetControlNode(reactionNode, field.name);
if (ctrlNode){
// there is... get the label
ctrlLabel = rsGetChildNodeValue(ctrlNode, 'label');
// if there's a trailing :, remove it so we can use it for prompts etc
if (ctrlLabel){
if (ctrlLabel.charAt(ctrlLabel.length-1) == ':')
ctrlLabel = ctrlLabel.substr(0,ctrlLabel.length-1);
}
else{
ctrlLabel = field.name;
}
// Remember validate controls
ctrlValidate = rsGetChildNodeValue(ctrlNode, 'validate');
if (ctrlValidate.length > 0) {
if (ctrlValidate.toLowerCase() == 'cardtype') {
ccTypeCtrl = field;
}
if (ctrlValidate.toLowerCase() == 'cardnumber') {
ccNumberCtrl = field;
}
if (ctrlValidate.toLowerCase() == 'cvv') {
ccCvvCtrl = field;
}
if (ctrlValidate.toLowerCase() == 'expmonth') {
ccExpMonthCtrl = field;
}
if (ctrlValidate.toLowerCase() == 'expyear') {
ccExpYearCtrl = field;
}
}
// check control type
switch(field.type){
case 'text':
// check for required status
if (field.value.length == 0 && rsGetChildNodeValue(ctrlNode, 'required').toLowerCase() == 'yes'){
warnMsg += '\n- A value is required for \'' + ctrlLabel + '\'.';
}
break;
case 'textarea':
// check for required status
if (field.value.length == 0 && rsGetChildNodeValue(ctrlNode, 'required').toLowerCase() == 'yes'){
warnMsg += '\n- A value is required for \'' + ctrlLabel + '\'.';
}
// check length
var maxLen = rsGetChildNodeValue(ctrlNode, 'maxLength');
if (maxLen && maxLen != '' && field.value.length > maxLen){
warnMsg += '\n- There is too much text for \'' + ctrlLabel + '\' - you entered ' + field.value.length + ' characters, but only ' + maxLen + ' are allowed.';
}
break;
case 'select-one':
// RXM - additions for country validation begin
// check for required status. Can't have empty value either ... now used by month and year
if ((field.value == ' - Select an option - ' || field.value.length == 0) && rsGetChildNodeValue(ctrlNode, 'required').toLowerCase() == 'yes'){
warnMsg += '\n- A value is required for \'' + ctrlLabel + '\'.';
}
// RXM - additions for country validation end
break;
case 'select-multiple':
// SIR 23024 - AF can be mandatory so check multi select box
if (field.value == ' - Select an option - ' || field.value.length == 0) {
if (rsGetChildNodeValue(ctrlNode, 'required').toLowerCase() == 'yes') {
warnMsg += '\n- A value is required for \'' + ctrlLabel + '\'.';
}
}
break;
case 'radio':
// SIR 23024 - AF can be mandatory so check radio buttons check list
if (field.checked) {
checkingListFieldOk = field.checked;
}
checkingListFieldName = field.name;
break;
case 'checkbox':
// SIR 23024 - AF can be mandatory so check check box list
if (field.checked) {
checkingListFieldOk = field.checked;
}
checkingListFieldName = field.name;
break;
case 'submit':
break;
default:
//alert('Other control type: ' + field.type);
break;
} // end switch
}
}
// SIR 23024 - If check list or radio buttons, warn no value set when moving to next control
if (checkingListFieldName != "") {
if (!checkingListFieldOk && rsGetChildNodeValue(ctrlNode, 'required').toLowerCase() == 'yes') {
warnMsg += '\n- A value is required for \'' + ctrlLabel + '\'.';
}
checkingListFieldOk = false;
checkingListFieldName = "";
}
// If we got cc details then validate those fields
if (ccNumberCtrl && ccTypeCtrl) {
ccErrorNo = checkCreditCard (ccNumberCtrl.value, ccTypeCtrl.value, ccCvvCtrl);
if (ccErrorNo >= 0) {
warnMsg += '\n- ' + ccErrors[ccErrorNo];
}
if (ccExpMonthCtrl && ccExpYearCtrl) {
var d = new Date();
var y = parseInt(d.getFullYear().toString().slice(2), 10);
var m = d.getMonth() + 1;
if (( parseInt(ccExpYearCtrl.value < y, 10))
|| (y == parseInt(ccExpYearCtrl.value, 10) && parseInt(ccExpMonthCtrl.value, 10) < m)) {
warnMsg += '\n- Expiry date cannot be in the past.';
}
}
}
// If we haven't got the submit control yet (e.g. if it's a graphic), try this:
if (!submitCtrl)
submitCtrl = document.getElementById(form.name + 'SUBMIT');
}
// KAR - DO NOT REMOVE - This is required for the Promotional Code section for Event Management. If this is removed, PayPal payments will be incorrect.
if (document.getElementById("UPDATECLICKED")) {
if (!document.getElementById("UPDATECLICKED").innerHTML) {
alert("Please click the 'Update' button before proceeding.");
return false;
}
}
// If no warning msg allow to proceed
if (!warnMsg){
// hide submit button to prevent multiple clicks
if (submitCtrl){
// for IE, could set outerHTML. For cross-platform, get innerHTML on parent
submitCtrl.parentNode.innerHTML = "Processing, please wait...";
//OR could use: submitCtrl.width = 0;
}
return true;
}
// Display warning
alert('Please correct the following problems to allow us to process your response:\n' + warnMsg);
// return false so form doesn't get submitted
return false;
}
// Return the node representing the specified reaction
function rsGetReactionNode(reactionName)
{
// This will work for IE
var doc = window.document.getElementsByTagName('RSXMLINFO')[0];
// Need this for Mozilla
if (!doc) {
doc = window.document.getElementsByTagName('xml')[0];
}
// SIR 22794 - Prevent XML Data island text sometimes being output on non-IE or BlackBerry
// This is for when HTML5 method of data island with script tags used
if (!doc) {
var xmlSource = document.getElementById('RSXMLDATAISLAND').textContent;
var parser = new DOMParser();
doc = parser.parseFromString(xmlSource, "application/xml");
}
if (doc){
var eventNode = doc.getElementsByTagName('event')[0];
var i = 0;
var reactionNode;
if (eventNode){
while (eventNode.childNodes[i]){
reactionNode = eventNode.childNodes[i];
for (var j=0; j < reactionNode.attributes.length; j++){
if (reactionNode.attributes[j]){
if (reactionNode.attributes[j].name == 'id' && reactionNode.attributes[j].value == reactionName)
return reactionNode;
}
}
i++;
}
}
}
else
alert('Your browser does not permit us to validate your input.');
}
// Return a node representing the supplied control from the supplied reaction node
function rsGetControlNode(reactionNode, wantedControlName)
{
var controlsNode = reactionNode.getElementsByTagName('controls')[0];
var controlNode, controlName;
controlNode = controlsNode.firstChild;
while (controlNode){
controlName = rsGetChildNodeValue(controlNode, 'name');
if (controlName.toUpperCase() == wantedControlName.toUpperCase()){
return controlNode;
}
controlNode = controlNode.nextSibling;
}
}
// Return text contents of named child node
function rsGetChildNodeValue(parentNode, childNodeName)
{
var node = parentNode.firstChild;
while (node){
if (node.nodeType == 1 && node.childNodes.length > 0){
if (node.nodeName.toUpperCase() == childNodeName.toUpperCase()){
return node.firstChild.nodeValue;
}
}
node = node.nextSibling;
}
// Return empty string if we didn't find match
return '';
}
function checkCreditCard (cardnumber, cardname, cvvCtrl) {
var ccErrorNo = 0;
// Array to hold the permitted card characteristics
var cards = new Array();
// Define the cards we support. You may add addtional card types as follows.
// Name: As in the selection box of the form - must be same as user's
// Length: List of possible valid lengths of the card number for the card
// prefixes: List of possible prefixes for the card
// checkdigit: Boolean to say whether there is a check digit
cards [0] = {name: "Visa",
length: "13,16",
cvvlength: "3",
prefixes: "4",
checkdigit: true};
cards [1] = {name: "MasterCard",
length: "16",
cvvlength: "3",
prefixes: "51,52,53,54,55",
checkdigit: true};
cards [2] = {name: "DinersClub",
length: "14,16",
cvvlength: "3",
prefixes: "36,54,55",
checkdigit: true};
cards [3] = {name: "AmEx",
length: "15",
cvvlength: "4",
prefixes: "34,37",
checkdigit: true};
cards [4] = {name: "Discover",
length: "16",
cvvlength: "3",
prefixes: "6011,622,64,65",
checkdigit: true};
cards [5] = {name: "JCB",
length: "16",
cvvlength: "3",
prefixes: "35",
checkdigit: true};
cards [6] = {name: "American Express",
length: "15",
cvvlength: "4",
prefixes: "34,37",
checkdigit: true};
cards [7] = {name: "Diners Club",
length: "14,16",
cvvlength: "3",
prefixes: "36,54,55",
checkdigit: true};
// Establish card type
var cardType = -1;
for (var i=0; i= 0; i--) {
// Extract the next digit and multiply by 1 or 2 on alternative digits.
calc = Number(cardNo.charAt(i)) * j;
// If the result is in two digits add 1 to the checksum total
if (calc > 9) {
checksum = checksum + 1;
calc = calc - 10;
}
// Add the units element to the checksum total
checksum = checksum + calc;
// Switch the value of j
if (j ==1) {j = 2} else {j = 1};
}
// All done - if checksum is divisible by 10, it is a valid modulus 10.
// If not, report an error.
if (checksum % 10 != 0) {
ccErrorNo = 3;
return ccErrorNo;
}
}
// Check it's not a spam number
if (cardNo == '5490997771092064') {
ccErrorNo = 5;
return ccErrorNo;
}
// The following are the card-specific checks we undertake.
var LengthValid = false;
var PrefixValid = false;
var undefined;
// We use these for holding the valid lengths and prefixes of a card type
var prefix = new Array ();
var lengths = new Array ();
// Load an array with the valid prefixes for this card
prefix = cards[cardType].prefixes.split(",");
// Now see if any of them match what we have in the card number
for (i=0; i