// 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