/*
Foul - Form Validation Language Version 1.7.1
Copyright (C) 2005  Bryan English

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

function Foul(){
	this.version = '1.7.2';
	this.form = null;
	this.breakpoints = false;
	this.interactive = false;
	this.tests = new Array();
	this.tests_index = new Array();
	this.formats = new Array();
	this.formats_index = new Array();
	this.local = new Array();
	this.defmsg = {
		"^\~([^~]+)\~$": "[[field]] is a required field.",
		" is (null|empty|blank)": "[[field]] is a required field.",
		" not email": "Please enter a valid email address.",
		" not (date\-us|date\-us\-y2k)": "Please enter a valid date in the form of mm/dd/yyyy.",
    " not zip-state-match(\-us)?": "Please verify the zip code you entered is correct for the state.",
		" not (zip(\-us)?|zipcode(\-us)?)": "Please enter a valid ZIP.",
		" not (state(\-us)?|postalcode(\-us)?)": "Please enter a valid state.",
		" not url\-http": "Please enter a valid url in the form of http://domain/path/file",
		" not url\-ftp": "Please enter a valid ftp url in the form of ftp://[user:pass@]domain/path/file",
		" not url\-news": "Please enter a valid news url in the form of news://domain/path/file",
		" not url": "Please enter a valid url in the form of protocol://[user:pass@]domain/path/file",
    " not (ssn|social\-security\-number)": "Please specify a valid social security number.",
		"numeric": " is a number."
		};
	this.errorMessage = "There is a problem with your submission:\n";
	this.errorMessage = "Er is een probleem met je invoer:\n";
	this.STATES = new Array('AL','AK','AS','AZ','AR','CA','CO','CT','DE','DC','FM','FL','GA','GU','HI','ID','IL','IN','IA','KS','KY','LA','ME','MH','MD','MA','MI','MN','MS','MO','MT','NE','NV','NH','NJ','NM','NY','NC','ND','MP','OH','OK','OR','PW','PA','PR','RI','SC','SD','TN','TX','UT','VT','VI','VA','WA','WV','WI','WY','AE','AA','AE','AE','AE','AP');
	this.STATE_ZIP_LOOKUP = {'FM':'969','AS':'96799','GU':'969','AL':'35-36','AK':'995-999','AZ':'85-86','AR':'716-729','CA':'900-961','CO':'80-81','CT':'06','DE':'197-199','DC':'200-205','FL':'32-34','GA':'30-31','HI':'967-968','ID':'832-839','IL':'60-62','IN':'46-47','IA':'50-52','KS':'66-67','KY':'40-42','LA':'700-715','ME':'039-049','MH':'969','MD':'206-219','MA':'010-027','MI':'48-49','MN':'55-56','MS':'386-399','MO':'63-65','MT':'59','NE':'68-69','NV':'89','NH':'030-038','NJ':'07-08','NM':'87-88','NY':'10-14','NC':'27-28','ND':'58','MP':'969','OH':'43-45','OK':'73-73','OR':'97','PW':'969','PA':'150-196','PR':'006-009','RI':'028-029','SC':'29','SD':'57','TN':'370-385','TX':'75-79','UT':'84','VT':'05','VI':'006-009','VA':'220-246','WA':'980-994','WV':'247-269','WI':'53-54','WY':'820-831','AE':'09','AA':'340','AP':'962-966'};
	this.add = function(v,m,i){
		var checksum = v.split("~");
		if ((checksum.length+1) % 2 == 1){this.error("Syntax error in " + v);}
		if (!m){
			for (var reg in this.defmsg){
				if(v.search(new RegExp(reg)) != -1){
          var field = v.match(/\~([^~]+)\~/)[1];
					m = this.defmsg[reg].replace(/\[\[field\]\]/g,field);
					break;
				}
       }
      }
      if (!m){
         this.error('['+ v +'] Test missing error message.');
      }
      var matches,re = new RegExp('\~([^~]+)\~','g');
      while(matches = re.exec(v)){
         if (!this.tests_index[RegExp.$1]){
            this.tests_index[RegExp.$1] = new Array();
         }
         this.tests_index[RegExp.$1][this.tests_index[RegExp.$1].length] = this.tests.length;         
      }
		this.tests[this.tests.length] = new Array(v,m,i);
   };
	this.when = this.add;
   this.format = function(v){
      var checksum = v.split("~");
      if((checksum.length+1) % 2 == 1){
         this.error("Syntax error in " + v);
      }
      var matches,re = new RegExp('\~([^~]+)\~','g');
      while(matches = re.exec(v)){
         if (!this.formats_index[RegExp.$1]){
            this.formats_index[RegExp.$1] = new Array();
         }
         this.formats_index[RegExp.$1][this.formats_index[RegExp.$1].length] = this.formats.length;         
      }
      var n = v.match(/\~([^~]+)\~/)[1];
		this.formats[this.formats.length] = new Array(v,n);
   };
 	this.error = function(e){
      var buffer = 'Foul ' + this.version + ' Error!';
      buffer += '\n_______________________________________\n';
      buffer += e + '\n\n';
      alert(buffer);
   }
	this.get_value = function(e){
		if(e.type!=null)
			switch(e.type){
				case "text": case "hidden": case "password": case "textarea": case "file":return(e.value);break;
				case "checkbox":return(((e.checked)?e.value:''));break;
				case "select-one":var o = e.options[e.selectedIndex];
					return(((o.value==null)?o.text:o.value));break;
				}
		else
			for(var cnt=0;cnt<e.length;cnt++)
				if(e[cnt].checked)return(e[cnt].value);
		return(false);
		};
   this.string_merge = function(template,data,reverse){
			var dindex = (reverse)?data.length-1:0;
			var dend = (reverse)?-1:data.length;
			var cnt = (reverse)?template.length-1:0;
			var end = (reverse)?-1:template.length;
			var inc = (reverse)?-1:1;
			var value = newdata = '';
      while (cnt != end && dindex != dend){
         switch (template.charAt(cnt)){
            case 'x':
               value = data.charAt(dindex);
               dindex+=inc;
            break;
            case 'X':
               cnt+=inc;
               if(reverse){
                  value = template.charAt(cnt) + data.charAt(dindex);
               } else {
                  value = data.charAt(dindex) + template.charAt(cnt);
               }
               dindex+=inc;
            break;
            default:
               value = template.charAt(cnt); 
            break;
         }
         newdata = ((reverse==null)?newdata:'') + value + ((reverse)?newdata:''); 
         cnt+=inc;
      }
		return(newdata);
	};
	this.validate = function(form){
		var errors = '';
		this.formatter(form);
		errors = this.test(form);
		if(errors!=''){
			alert(this.errorMessage+errors);
			return false;
			}
		return true;
		}	
	this.inspect = function(field){
      this.form = field.form;
      if (this.interactive && this.formats_index[field.name]){
         for(var cnt=0;cnt<this.formats_index[field.name].length;cnt++){
      		this.tokenize(this.formats[this.formats_index[field.name][cnt]][0]);
         }
      }
      if (this.breakpoints && this.tests_index[field.name]){
         var errors = '';
         for(var cnt=0;cnt<this.tests_index[field.name].length;cnt++){
            if(this.tokenize(this.tests[this.tests_index[field.name][cnt]][0])){
	   		   alert(this.tests[this.tests_index[field.name][cnt]][1]);
               return;
            }            
         }
      }
	}	
   this.chomp = function(str){
   	  if(!str)return '';
      str = str.match(/\s*(.*\S)\s*/);
      return str[1];
      }
   this.onion = function(str,start,end){
      var cnt,tally = 1;
      for(cnt=1;cnt<str.length && tally!=0;cnt++){
         if(str.charAt(cnt) == start)tally++;
         if(str.charAt(cnt) == end)tally--;
         }
      return(str.substring(1,cnt-1));
      }
   this.tokenize = function(str){
      var left,right,bool = null;
      var result = false;
      str = this.chomp(str);
      if(str.charAt(0) == '('){
         left = this.onion(str,"(",")");
         right = str.substring(str.indexOf(left)+left.length,str.length);
         left = str.substring(1,str.length-1);
         result = this.tokenize(left);
         }
      else{         
         left = str.match(/\~[^\~]+\~.*?(?= and | or |$)/)[0];
         right = str.substring(str.indexOf(left)+left.length,str.length);
         result = this.evaluate(left);
			result = (result==-1?false:result)
         }
      bool = right.match(/ or | and |\s*$/)[0];
      right = right.substring(right.indexOf(bool)+bool.length,right.length);
      switch(this.chomp(bool)){
         case "":
            return result;
         case "and":
            return(result && this.tokenize(right));
         case "or":
            return(result || this.tokenize(right));
         }
      }
   this.formatter = function(form){
      this.form = form;
      for(var cnt=0;cnt<this.formats.length;cnt++)
         this.tokenize(this.formats[cnt][0]);			
      };	
	this.test = function(form){
		var errors = '';
      this.form = form;
      for(var cnt=0;cnt<this.tests.length;cnt++)
         if(this.tokenize(this.tests[cnt][0]))
			   errors += '\n- ' + this.tests[cnt][1];
		return errors;
      };		 
   this.evaluate = function(str){
			var list = str.match(/\~([^~]+)\~(?:\s(\S+)(?:\s(.*))?)?/);
			var field = list[1];		
			var value = (this.form[field])?this.get_value(this.form[field]):this.local[field];
			var test = (list[2]==null||list[2]=="")?"null":list[2];
			var param = list[3];      
      switch(test){
         case 'as': 
            if(value == null || value == '')return(true);				
				param = param.split(' ');
				switch(param[0]){
					case 'number':
					case 'integer': 
						this.form[field].value = (isNaN(parseInt(value.replace(/[^0-9\.]/g,''))))?'':parseInt(value.replace(/[^0-9\.]/g,''));
						break;
					case 'float':
					case 'decimal': 
						this.form[field].value = (isNaN(parseFloat(value.replace(/[^0-9\.]/g,''))))?'':parseFloat(value.replace(/[^0-9\.]/g,''));
						break;
					case 'pad':
					case 'padded':
						var s = new String(value);
						while(s.length < param[1])
							s = param[2] + s;
						this.form[field].value = s;
						break;
               case 'phone-us':
                  var s = value.replace(/\D/g,'');
                  this.form[field].value = this.string_merge('x (Xxx) xxx-xxxx',s,true);
                  break;
               case 'credit-card':
                  var s = value.replace(/\D/g,'');
                  this.form[field].value = this.string_merge('xxxx xxxx xxxx xxxx',s);
                  break;
               case 'ssn':
               case 'social-security-number':
                  var s = value.replace(/\D/g,'');
                  this.form[field].value = this.string_merge('xxx-xx-xxxx',s);
                  break;
               case 'date-us':
                  var s = value.replace(/\s/g,'');
                  var matches = s.match(/(\d\d?)\D(\d\d?)\D(\d\d(?:\d\d)?)/);
                  if (matches){
                     this.form[field].value = matches[1] + '/' + matches[2] + '/' + matches[3];
                  }
                  break;
               case 'date-us-y2k':
                  var s =   value.replace(/\s/g,'');
                  var matches = s.match(/(\d\d?)\D(\d\d?)\D(\d\d(?:\d\d)?)/);
                  if (matches){
                     if (matches[3].length == 2){
                        d = new Date();
                        max = d.getFullYear() - 2000 + 20;
                        matches[3] = (parseInt(matches[3])>max?'19':'20') + matches[3];
                     }
                     this.form[field].value = matches[1] + '/' + matches[2] + '/' + matches[3];
                  }
                  break;
               case 'url':
               case 'url-http':
                  var matches = value.match(/([a-zA-Z0-9\.\-]+(\/[\S]*)?)\s*$/);
                  this.form[field].value = 'http://' + matches[1];
                  break;
               case 'url-ftp':
                  var matches = value.match(/(([\S]+\:[\S]+\@)?[a-zA-Z0-9\.\-]+(\/[\S]*)?)\s*$/);
                  this.form[field].value = 'ftp://' + matches[1];
                  break;
               case 'url-news':
                  var matches = value.match(/([a-zA-Z0-9\.\-]+(\/[\S]*)?)\s*$/);
                  this.form[field].value = 'news://' + matches[1];
                  break;
               case 'uppercase':
                  this.form[field].value = value.toUpperCase();
                  break;
               default:
                  this.error('"' + test + "\" test doesn't exist.");
                  break;
               }
				return(true);
				break;
			case 'is':
			case 'has':
			case 'does':
         case '=':
            return(this.evaluate('~'+field+'~ '+ param));
            break;
         case '!':
         case 'not':
				var result = this.evaluate('~'+field+'~ '+ param);
				if(result!=-1)
	               return(!this.evaluate('~'+field+'~ '+ param));
				else
				   return(-1);
            break;
         case 'empty':
         case 'blank':
         case 'null':
            if(value == null || value == '' || value == 'aaa')return(true);
            break;
         case 'range':
         case 'between':
            if(value == null || value == '')return(-1);
            param = param.split(/\s/);
				if(value > parseFloat(param[0]) && value < parseFloat(param[1]))return(true);
            break;
         case 'greater-than':
         case '>':
            if(value == null || value == '' || isNaN(value))return(-1);
            value = parseFloat(value);
            param = parseFloat(param);
				if(value > param)return(true);
            break;
         case 'less-than':
         case '<':
            if(value == null || value == '' || isNaN(value))return(-1);
            value = parseFloat(value);
            param = parseFloat(param);
				if(value < param)return(true);
            break;
         case 'email':
            if(value == null || value == '')return(-1);
            if(/^.+\@..+\..+/.test(value))return(true);
            break;
         case 'length':
            if(value == null || value == '')return(-1);
            param = param.split(/\s/);
						if(param.length > 1){
							this.local["_LOCAL_" + field] = parseInt(value.length);
							return(this.evaluate('~_LOCAL_'+field+'~ '+ param.join(' ')));
							}
						else{
							return(value.length == parseInt(param[0]));
						}
            break;
         case 'number':
         case 'float':
         case 'decimal':
         case 'numeric':
            if(value == null || value == '')return(-1);
            if(!isNaN(value))return(true);
            break;
			case 'valid_credit_card':
			case 'valid_cc':
			case 'vcc':
				if(value == null || value == '')return(false);
            value = value.replace(/\D/g,'');
				if (value.length > 19)
					return (false);
				var sum = 0; mul = 1; l = value.length;
				for (i = 0; i < l; i++) {
					var digit = value.substring(l-i-1,l-i);
					var tproduct = parseInt(digit ,10)*mul;
					if (tproduct >= 10)
						sum += (tproduct % 10) + 1;
					else
						sum += tproduct;
					if (mul == 1)
						mul++;
					else
						mul--;
					}
				if ((sum % 10) == 0)return (true);
				break;
         case 'date-us':
            if(value == null || value == '')return(-1);
            if (/\d\d?\/\d\d?\/\d{2,4}/.test(value)){
               var pcs = value.match(/(\d\d?)\/(\d\d?)\/(\d{2,4})/);
               var d = new Date(pcs[3],pcs[1]-1,pcs[2]);
               var y = ((pcs[3].length == 2)?d.getYear():d.getFullYear());
               if (y == pcs[3] && d.getMonth()+1 == pcs[1] && d.getDate() == pcs[2])
                  return(true);
            }
            break;
         case 'date-us-y2k':
            if(value == null || value == '')return(-1);

            if (/\d\d?\/\d\d?\/\d{4}/.test(value)){
               var pcs = value.match(/(\d\d?)\/(\d\d?)\/(\d{4})/);
               var d = new Date(pcs[3],pcs[1]-1,pcs[2]);
               if (d.getFullYear() == pcs[3] && d.getMonth()+1 == pcs[1] && d.getDate() == pcs[2])
                  return(true);
               }
            break;
         case 'password':
            if(value == null || value == '')return(-1);         
            if((!/\s|\t|\n|\r/.test(value)) && this.evaluate('~'+ field +'~ length > 8'))return(true);
            break;
         case '==':
         case 'same-as':
         case 'equal-to':
            if(value == null || value == '')return(-1);
            if(value == this.get_value(this.form[this.chomp(param)]))return(true);
            break;
         case 'password-verified':
         case 'password-confirmed':
            if(value == null || value == '')return(-1);
            if(value == this.get_value(this.form['confirm_' + field]))return(true);
            break;
         case 'blanks':
         case 'spaces':
            if(value == null || value == '')return(-1);                   
            if(/\s/.test(value))return(true);
            break;
         case 'url':
            if(value == null || value == '')return(-1);                   
            if(/^(https?|ftp|news)\:\/\/([\S]+\:[\S]+\@)?[a-zA-Z0-9\.\-]+(\/[\S]*)?$/.test(value))return(true);
            break;
         case 'url-http':
            if(value == null || value == '')return(-1);                   
            if(/^https?\:\/\/[a-zA-Z0-9\.\-]+(\/[\S]*)?$/.test(value))return(true);
            break;
         case 'url-ftp':
            if(value == null || value == '')return(-1);                   
            if(/^ftp\:\/\/([\S]+\:[\S]+\@)?[a-zA-Z0-9\.\-]+(\/[\S]*)?$/.test(value))return(true);
            break;
         case 'url-news':
            if(value == null || value == '')return(-1);                   
            if(/^news\:\/\/[a-zA-Z0-9\.\-]+(\/[\S]*)?$/.test(value))return(true);
            break;
         case 'zip':
         case 'zipcode':
         case 'zipcode-us':
         case 'zip-us':
            if (value == null || value == '')return(-1);
            if (/^\d{5}(\-\d{4})?$/.test(value))return true;
            break;
         case 'state':
         case 'state-us':
         case 'postalcode':
         case 'postalcode-us':
            if (value == null || value == '')return(-1);
            var re = new RegExp('^(' + this.STATES.join('|') + ')$','i');
            if (re.test(value))return true;
            break;
         case 'zip-state-match':
         case 'zip-state-match-us':
            if (value == null || value == '')return(-1);
   			var result = this.evaluate('~'+ param +'~ state');
            if (result && this.form[param]){
               var state = this.get_value(this.form[param]).toUpperCase();
               var range = this.STATE_ZIP_LOOKUP[state].split('-');
               var digits = range[0].length;
               var zip = parseInt(value.substring(0,digits));
               if (range.length > 1){
                  if (zip >= parseInt(range[0]) && zip <= parseInt(range[1])) return true;
               } else {
                  if (zip == parseInt(range[0])) return true
               }
            } else {
               return(-1);
            }
            break;
         case 'ssn':
         case 'social-security-number':
            if (value == null || value == '')return(-1);
            if (/^\d\d\d\-\d\d\-\d\d\d\d$/.test(value) &&
               !/(^000)|(\-00\-)|(0000$)/.test(value) &&
               !/987\-65\-432[0-9]/.test(value)) return true;
            break;
         case 'file-type':
            if (value == null || value == '')return(-1);
            var params = param.split(/[\s,]/g);
            for (cnt=0;cnt<params.length;cnt++){
               var re = new RegExp('\.'+ params[cnt] +'$','ig');
               if (re.test(value)) return true;
            }
            break;
         case 'file-type-image':
            if (value == null || value == '')return(-1);
            if (this.evaluate('~' + field + '~ file-type ani b3d bmp dib cam clp crw cr2 cur dcm acr ima dcx dds djvu iw44 ecw emf eps fpx fsh g3 gif icl ico ics ids iff lbm img jp2 jpc j2k jpf jpg jpeg jpe jpm kdc ldf lwf mng jng nlm nol ngg gsm pbm pcd pcx pgm png ppm psd psp ras sun raw rle sff sfw sgi rgb sid swf tga tif tiff wbmp wmf xbm xpm')) return true;
            break;
         case 'file-type-executable':
            if (value == null || value == '')return(-1);
            if (this.evaluate('~' + field + '~ file-type ade adp bas chm cmd cpl crt hlp hta inf ins isp jse lnk mdb mde msc msi msp mst ocx pcd pif pot ppt sct shb shs sys url vb vbe vbs wsc wsf exe js vbs scr com bat wsh reg dll')) return true;
            break;
         default:
            this.error('"' + test + "\" test doesn't exist.");
            break;
		}
      return false;
      };
   }
var foul = new Foul();
