// This is an easy to use wrapper function for AJAX requests
//
// parameters:
// 		URL: the URL of the page to which the AJAX request will be sent
// 		useGET: a boolean indicating whether the AJAX request will be sent by GET (if it is false, the request will be send
//				by POST)
// 		isAsynchronous: a boolean indicating whether the AJAX request will be asynchronous (if it is false, the request will
//				be synchronous)
//				-WARNING: be sure that ONLY ONE asynchronous AJAX request is operating at a time to avoid race conditions between
//				AJAX requests and prevent session data from falling out of sync
//		parameters: an associative array that contains name, value pairs for variables that will be submitted by AJAX
//				-the variable names and values will be automatically encoded to escape forbidden characters, so there is no need
//				to encode parameters before calling this function
//		callback: for asynchronous requests, this is the function that will be executed when the request returns; this parameter
//				does not need to be given for synchronous requests and will be ignored in that case. The response from the AJAX
//				request is eval'd and the result stored in an associative array which will be passed as the first and only parameter
//				to the callback function. If the result could not be eval'd response["status"] is set to false and response["message"]
//				will contain an error message.
//		timeout: an optional parameter that gives the number of milliseconds to wait before terminating an asynchronous AJAX
//				request (defaults to a 5 s timeout). Set to 0 to disable timing out.
//		passthrough: an optional parameter that gives a variable that will be passed to the callback in addition to the response from
//				the AJAX request (in this case the callback function should take the form callback(response, passthrough) ).
//
// this function returns an associative array with the following elements if the request is synchronous; if the request is asynchronous this is the form of the message parameter
// passed to the callback
// 		status: true if the called script indicated success, false otherwise
//		message: a string containing a message from the called script, if there is any (usually used to contain the error message
//				if the status is false)
//		some called scripts may add additional elements to the returned array
function AJAXRequestExecute(URL, useGET, isAsynchronous, parameters, callback, timeout, passthrough)
{
	// sets the timeout to its default value if the parameter was not passed
	if(timeout == null)
		timeout = 5000;
	
	var xmlHttpWrapper = getXMLHTTPObject();
	var xmlHttp = xmlHttpWrapper.xmlHttp;
	
	if(xmlHttp == null)
	{
		// if the browser doesn't support AJAX, return
		var returnArray  = new Array();
		returnArray["status"] = false;
		returnArray["message"] = "This system requires that your browser supports AJAX, but it does not. You will have to use another browser to use this page.";
		if(isAsynchronous)
		{
			if(passthrough == null)
				callback(returnArray);
			else
				callback(returnArray, passthrough);
			return;
		}
		else
			return returnArray;
	}

	// let the request time out after the set interval
	if(isAsynchronous && (timeout != 0))
	{
		setTimeout(function() {
			xmlHttpTimeout(xmlHttpWrapper, callback, passthrough);
		}, timeout);
	}
	
	
	if(useGET)
	{
		// build the list of variables to be submitted
		var formData = "?";
		var firstEntry = true;
		for(var variableName in parameters)
		{
			formData += (firstEntry ? "" : "&") + encodeURIComponent(variableName) + "=" + encodeURIComponent(parameters[variableName]);
			firstEntry = false;
		}
		
		xmlHttp.open("GET", URL + formData, isAsynchronous);
		xmlHttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
		xmlHttp.setRequestHeader("Cache-Control", "no-cache"); 
		xmlHttp.setRequestHeader("Connection", "close");
		xmlHttp.send(null);
	}
	else
	{
		// build the list of variables to be submitted
		var formData = "";
		var firstEntry = true;
		for(var variableName in parameters)
		{
			formData += (firstEntry ? "" : "&") + variableName + "=" + encodeURIComponent(parameters[variableName]);
			firstEntry = false;
		}
		
		xmlHttp.open("POST", URL, isAsynchronous);
		xmlHttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
		xmlHttp.setRequestHeader("Cache-Control", "no-cache"); 
		xmlHttp.setRequestHeader("Content-length", formData.length);
		xmlHttp.setRequestHeader("Connection", "close");
		xmlHttp.send(formData);
	}
	
	if(isAsynchronous)
	{
		xmlHttp.onreadystatechange=function()
		{
			if(xmlHttp.readyState == 4)
			{
				// status code zero usually indicates that the request was aborted and should not be processed
				if(xmlHttp.status == 0)
					return;
				
				// indicate that this request has returned
				xmlHttpWrapper.processed = true;
				
				// if there was an error, return
				if(xmlHttp.status != 200)
				{
					var response = new Array();
					response["status"] = false;
					response["message"] = "Server request failed with an HTTP error. The HTTP status code returned was: " + xmlHttp.status + " " + xmlHttp.statusText + "."
											+ (xmlHttp.responseText == "" ? "" : " The response text is: " + xmlHttp.responseText);

					if(passthrough == null)
						callback(response);
					else
						callback(response, passthrough);
				}
				else
				{
					var response = AJAXResponseHandler(xmlHttp.responseText);
					
					if(passthrough == null)
						callback(response);
					else
						callback(response, passthrough);
				}
			}
		}
	}
	else
		return AJAXResponseHandler(xmlHttp.responseText);
}


// a support function for the AJAXRequestExecute() function
function AJAXResponseHandler(responseText)
{
	try
	{
		var response = eval('(' + responseText + ')');
	}
	catch(e)
	{
		var resultArray = new Array();
		resultArray["status"] = false;
		resultArray["message"] = "Could not parse the server response to the current AJAX request. The received response was \"" + responseText + "\".";
		
		response = resultArray;
	}

	return response;
}


// basic AJAX function that creates the XMLHTTPObject
function getXMLHTTPObject()
{	
	// Create a wrapper so we can add the custom "processed" property
	var xmlHttpWrapper = new Object();
	var xmlHttp = null;
	
	if (window.XMLHttpRequest) {
		// for Internet Explorer 7+, Firefox, Opera 8.0+, Safari, Chrome
		xmlHttp=new XMLHttpRequest();
	} else {
		try
		{
			// for Internet Explorer 6
    		xmlHttp=new ActiveXObject("Msxml2.XMLHTTP");
		}
		catch (e)
		{
			try
			{   
				// for Internet Explorer 5.5   
				xmlHttp=new ActiveXObject("Microsoft.XMLHTTP");
			}
			catch (e)
			{			
				// ignore and fall through
				xmlHttp = null;
			}
		}
	}
	
	// add a property to indicate whether the request has been processed
	// this will be used to determine whether to invoke the timeout code
	if(xmlHttp != null) {
		xmlHttpWrapper.processed = false;
		xmlHttpWrapper.xmlHttp = xmlHttp;
	}
	
	return xmlHttpWrapper;
}


// handled timing out the AJAX request
function xmlHttpTimeout(xmlHttpWrapper, callback, passthrough)
{
	var xmlHttp = xmlHttpWrapper.xmlHttp;
	xmlHttp.abort();

	// if the timeout has elapsed but the AJAX request is not done, terminate the request and invoke the callback
	if(!xmlHttpWrapper.processed)
	{
		var response = new Array();
		response["status"] = false;
		response["message"] = "Communication with the server timed out.";
		
		if(passthrough == null)
			callback(response);
		else
			callback(response, passthrough);
	}
}


//a public domain implementation of the PHP function json_encode
function json_encode(mixed_val) {
    // http://kevin.vanzonneveld.net
    // + original by: Public Domain (http://www.json.org/json2.js)
    // + reimplemented by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
    // * example 1: json_encode(['e', {pluribus: 'unum'}]);
    // * returns 1: '[\n "e",\n {\n "pluribus": "unum"\n}\n]'
 
    /*
	 * http://www.JSON.org/json2.js 2008-11-19 Public Domain. NO WARRANTY
	 * EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. See
	 * http://www.JSON.org/js.html
	 */
    
    var value = mixed_val;
 
    var quote = function (string) {
        var escapable = /[\\\"\u0000-\u001f\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g;
        var meta = {    // table of character substitutions
            '\b': '\\b',
            '\t': '\\t',
            '\n': '\\n',
            '\f': '\\f',
            '\r': '\\r',
            '"' : '\\"',
            '\\': '\\\\'
        };
 
        escapable.lastIndex = 0;
        return escapable.test(string) ?
        '"' + string.replace(escapable, function (a) {
            var c = meta[a];
            return typeof c === 'string' ? c :
            '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
        }) + '"' :
        '"' + string + '"';
    };
 
    var str = function(key, holder) {
        var gap = '';
        var indent = '    ';
        var i = 0;          // The loop counter.
        var k = '';          // The member key.
        var v = '';          // The member value.
        var length = 0;
        var mind = gap;
        var partial = [];
        var value = holder[key];
 
        // If the value has a toJSON method, call it to obtain a replacement
		// value.
        if (value && typeof value === 'object' &&
            typeof value.toJSON === 'function') {
            value = value.toJSON(key);
        }
        
        // What happens next depends on the value's type.
        switch (typeof value) {
            case 'string':
                return quote(value);
 
            case 'number':
                // JSON numbers must be finite. Encode non-finite numbers as null.
                return isFinite(value) ? String(value) : 'null';
 
            case 'boolean':
            case 'null':
                // If the value is a boolean or null, convert it to a string. Note:
                // typeof null does not produce 'null'. The case is included here in
                // the remote chance that this gets fixed someday.
 
                return String(value);
 
            case 'object':
                // If the type is 'object', we might be dealing with an object or an array or
                // null.
                // Due to a specification blunder in ECMAScript, typeof null is
				// 'object',
                // so watch out for that case.
                if (!value) {
                    return 'null';
                }
 
                // Make an array to hold the partial results of stringifying this object value.
                gap += indent;
                partial = [];
 
                // Is the value an array?
                if (Object.prototype.toString.apply(value) === '[object Array]') {
                    // The value is an array. Stringify every element. Use null as a placeholder
                    // for non-JSON values.
 
                    length = value.length;
                    for (i = 0; i < length; i += 1) {
                        partial[i] = str(i, value) || 'null';
                    }
 
                    // Join all of the elements together, separated with commas, and wrap them in
                    // brackets.
                    v = partial.length === 0 ? '[]' :
                    gap ? '[\n' + gap +
                    partial.join(',\n' + gap) + '\n' +
                    mind + ']' :
                    '[' + partial.join(',') + ']';
                    gap = mind;
                    return v;
                }
 
                // Iterate through all of the keys in the object.
                for (k in value) {
                    if (Object.hasOwnProperty.call(value, k)) {
                        v = str(k, value);
                        if (v) {
                            partial.push(quote(k) + (gap ? ': ' : ':') + v);
                        }
                    }
                }
 
                // Join all of the member texts together, separated with commas,
                // and wrap them in braces.
                v = partial.length === 0 ? '{}' :
                gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' +
                mind + '}' : '{' + partial.join(',') + '}';
                gap = mind;
                return v;
        }
    };
 
    // Make a fake root object containing our value under the key of ''.
    // Return the result of stringifying the value.
    return str('', {
        '': value
    });
}


// a public domain implementation of the PHP function urldecode
function urldecode( str ) {
    // http://kevin.vanzonneveld.net
    // + original by: Philip Peterson
    // + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
    // + input by: AJ
    // + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
    // + improved by: Brett Zamir (http://brett-zamir.me)
    // + input by: travc
    // + input by: Brett Zamir (http://brett-zamir.me)
    // + bugfixed by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
    // + improved by: Lars Fischer
    // % note 1: info on what encoding functions to use from:
	// http://xkr.us/articles/javascript/encode-compare/
    // * example 1: urldecode('Kevin+van+Zonneveld%21');
    // * returns 1: 'Kevin van Zonneveld!'
    // * example 2: urldecode('http%3A%2F%2Fkevin.vanzonneveld.net%2F');
    // * returns 2: 'http://kevin.vanzonneveld.net/'
    // * example 3:
	// urldecode('http%3A%2F%2Fwww.google.nl%2Fsearch%3Fq%3Dphp.js%26ie%3Dutf-8%26oe%3Dutf-8%26aq%3Dt%26rls%3Dcom.ubuntu%3Aen-US%3Aunofficial%26client%3Dfirefox-a');
    // * returns 3:
	// 'http://www.google.nl/search?q=php.js&ie=utf-8&oe=utf-8&aq=t&rls=com.ubuntu:en-US:unofficial&client=firefox-a'
    
    var histogram = {}, ret = str.toString(), unicodeStr='', hexEscStr='';
    
    var replacer = function(search, replace, str) {
        var tmp_arr = [];
        tmp_arr = str.split(search);
        return tmp_arr.join(replace);
    };
    
    // The histogram is identical to the one in urlencode.
    histogram["'"]   = '%27';
    histogram['(']   = '%28';
    histogram[')']   = '%29';
    histogram['*']   = '%2A';
    histogram['~']   = '%7E';
    histogram['!']   = '%21';
    histogram['%20'] = '+';
    histogram['\u00DC'] = '%DC';
    histogram['\u00FC'] = '%FC';
    histogram['\u00C4'] = '%D4';
    histogram['\u00E4'] = '%E4';
    histogram['\u00D6'] = '%D6';
    histogram['\u00F6'] = '%F6';
    histogram['\u00DF'] = '%DF'; 
    histogram['\u20AC'] = '%80';
    histogram['\u0081'] = '%81';
    histogram['\u201A'] = '%82';
    histogram['\u0192'] = '%83';
    histogram['\u201E'] = '%84';
    histogram['\u2026'] = '%85';
    histogram['\u2020'] = '%86';
    histogram['\u2021'] = '%87';
    histogram['\u02C6'] = '%88';
    histogram['\u2030'] = '%89';
    histogram['\u0160'] = '%8A';
    histogram['\u2039'] = '%8B';
    histogram['\u0152'] = '%8C';
    histogram['\u008D'] = '%8D';
    histogram['\u017D'] = '%8E';
    histogram['\u008F'] = '%8F';
    histogram['\u0090'] = '%90';
    histogram['\u2018'] = '%91';
    histogram['\u2019'] = '%92';
    histogram['\u201C'] = '%93';
    histogram['\u201D'] = '%94';
    histogram['\u2022'] = '%95';
    histogram['\u2013'] = '%96';
    histogram['\u2014'] = '%97';
    histogram['\u02DC'] = '%98';
    histogram['\u2122'] = '%99';
    histogram['\u0161'] = '%9A';
    histogram['\u203A'] = '%9B';
    histogram['\u0153'] = '%9C';
    histogram['\u009D'] = '%9D';
    histogram['\u017E'] = '%9E';
    histogram['\u0178'] = '%9F';
 
    for (unicodeStr in histogram) {
        hexEscStr = histogram[unicodeStr]; // Switch order when decoding
        ret = replacer(hexEscStr, unicodeStr, ret); // Custom replace. No
													// regexing
    }
    
    // End with decodeURIComponent, which most resembles PHP's encoding functions
    ret = decodeURIComponent(ret);
 
    return ret;
}


function htmlentities (string, quote_style) {
    // http://kevin.vanzonneveld.net
    // +   original by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
    // +    revised by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
    // +   improved by: nobbler
    // +    tweaked by: Jack
    // +   bugfixed by: Onno Marsman
    // +    revised by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
    // +    bugfixed by: Brett Zamir (http://brett-zamir.me)
    // +      input by: Ratheous
    // -    depends on: get_html_translation_table
    // *     example 1: htmlentities('Kevin & van Zonneveld');
    // *     returns 1: 'Kevin &amp; van Zonneveld'
    // *     example 2: htmlentities("foo'bar","ENT_QUOTES");
    // *     returns 2: 'foo&#039;bar'
 
    var hash_map = {}, symbol = '', tmp_str = '', entity = '';
    tmp_str = string.toString();
    
    if (false === (hash_map = this.get_html_translation_table('HTML_ENTITIES', quote_style))) {
        return false;
    }
    hash_map["'"] = '&#039;';
    for (symbol in hash_map) {
        entity = hash_map[symbol];
        tmp_str = tmp_str.split(symbol).join(entity);
    }
    
    return tmp_str;
}


//a public domain implementation of the PHP function html_entity_decode
function html_entity_decode( string, quote_style ) {
    // http://kevin.vanzonneveld.net
    // + original by: john (http://www.jd-tech.net)
    // + input by: ger
    // + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
    // + revised by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
    // + bugfixed by: Onno Marsman
    // + improved by: marc andreu
    // + revised by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
    // + bugfixed by: Brett Zamir (http://brett-zamir.me)
    // - depends on: get_html_translation_table
    // * example 1: html_entity_decode('Kevin &amp; van Zonneveld');
    // * returns 1: 'Kevin & van Zonneveld'
    // * example 2: html_entity_decode('&amp;lt;');
    // * returns 2: '&lt;'
 
    var histogram = {}, symbol = '', tmp_str = '', entity = '';
    tmp_str = string.toString();
    
    if (false === (histogram = this.get_html_translation_table('HTML_ENTITIES', quote_style))) {
        return false;
    }
 
    for (symbol in histogram) {
        entity = histogram[symbol];
        tmp_str = tmp_str.split(entity).join(symbol);
    }
    tmp_str = tmp_str.split('&#039;').join("'");
    
    return tmp_str;
}


// a support function for the above function
function get_html_translation_table(table, quote_style) {
    // http://kevin.vanzonneveld.net
    // + original by: Philip Peterson
    // + revised by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
    // + bugfixed by: noname
    // + bugfixed by: Alex
    // + bugfixed by: Marco
    // + bugfixed by: madipta
    // + improved by: KELAN
    // + improved by: Brett Zamir (http://brett-zamir.me)
    // + bugfixed by: Brett Zamir (http://brett-zamir.me)
    // % note: It has been decided that we're not going to add global
    // % note: dependencies to php.js. Meaning the constants are not
    // % note: real constants, but strings instead. integers are also supported
	// if someone
    // % note: chooses to create the constants themselves.
    // * example 1: get_html_translation_table('HTML_SPECIALCHARS');
    // * returns 1: {'"': '&quot;', '&': '&amp;', '<': '&lt;', '>': '&gt;'}
    
    var entities = {}, histogram = {}, decimal = 0, symbol = '';
    var constMappingTable = {}, constMappingQuoteStyle = {};
    var useTable = {}, useQuoteStyle = {};
    
    // Translate arguments
    constMappingTable[0]      = 'HTML_SPECIALCHARS';
    constMappingTable[1]      = 'HTML_ENTITIES';
    constMappingQuoteStyle[0] = 'ENT_NOQUOTES';
    constMappingQuoteStyle[2] = 'ENT_COMPAT';
    constMappingQuoteStyle[3] = 'ENT_QUOTES';
 
    useTable       = !isNaN(table) ? constMappingTable[table] : table ? table.toUpperCase() : 'HTML_SPECIALCHARS';
    useQuoteStyle = !isNaN(quote_style) ? constMappingQuoteStyle[quote_style] : quote_style ? quote_style.toUpperCase() : 'ENT_COMPAT';
 
    if (useTable !== 'HTML_SPECIALCHARS' && useTable !== 'HTML_ENTITIES') {
        throw new Error("Table: "+useTable+' not supported');
        // return false;
    }
 
    if (useTable === 'HTML_ENTITIES') {
        entities['160'] = '&nbsp;';
        entities['161'] = '&iexcl;';
        entities['162'] = '&cent;';
        entities['163'] = '&pound;';
        entities['164'] = '&curren;';
        entities['165'] = '&yen;';
        entities['166'] = '&brvbar;';
        entities['167'] = '&sect;';
        entities['168'] = '&uml;';
        entities['169'] = '&copy;';
        entities['170'] = '&ordf;';
        entities['171'] = '&laquo;';
        entities['172'] = '&not;';
        entities['173'] = '&shy;';
        entities['174'] = '&reg;';
        entities['175'] = '&macr;';
        entities['176'] = '&deg;';
        entities['177'] = '&plusmn;';
        entities['178'] = '&sup2;';
        entities['179'] = '&sup3;';
        entities['180'] = '&acute;';
        entities['181'] = '&micro;';
        entities['182'] = '&para;';
        entities['183'] = '&middot;';
        entities['184'] = '&cedil;';
        entities['185'] = '&sup1;';
        entities['186'] = '&ordm;';
        entities['187'] = '&raquo;';
        entities['188'] = '&frac14;';
        entities['189'] = '&frac12;';
        entities['190'] = '&frac34;';
        entities['191'] = '&iquest;';
        entities['192'] = '&Agrave;';
        entities['193'] = '&Aacute;';
        entities['194'] = '&Acirc;';
        entities['195'] = '&Atilde;';
        entities['196'] = '&Auml;';
        entities['197'] = '&Aring;';
        entities['198'] = '&AElig;';
        entities['199'] = '&Ccedil;';
        entities['200'] = '&Egrave;';
        entities['201'] = '&Eacute;';
        entities['202'] = '&Ecirc;';
        entities['203'] = '&Euml;';
        entities['204'] = '&Igrave;';
        entities['205'] = '&Iacute;';
        entities['206'] = '&Icirc;';
        entities['207'] = '&Iuml;';
        entities['208'] = '&ETH;';
        entities['209'] = '&Ntilde;';
        entities['210'] = '&Ograve;';
        entities['211'] = '&Oacute;';
        entities['212'] = '&Ocirc;';
        entities['213'] = '&Otilde;';
        entities['214'] = '&Ouml;';
        entities['215'] = '&times;';
        entities['216'] = '&Oslash;';
        entities['217'] = '&Ugrave;';
        entities['218'] = '&Uacute;';
        entities['219'] = '&Ucirc;';
        entities['220'] = '&Uuml;';
        entities['221'] = '&Yacute;';
        entities['222'] = '&THORN;';
        entities['223'] = '&szlig;';
        entities['224'] = '&agrave;';
        entities['225'] = '&aacute;';
        entities['226'] = '&acirc;';
        entities['227'] = '&atilde;';
        entities['228'] = '&auml;';
        entities['229'] = '&aring;';
        entities['230'] = '&aelig;';
        entities['231'] = '&ccedil;';
        entities['232'] = '&egrave;';
        entities['233'] = '&eacute;';
        entities['234'] = '&ecirc;';
        entities['235'] = '&euml;';
        entities['236'] = '&igrave;';
        entities['237'] = '&iacute;';
        entities['238'] = '&icirc;';
        entities['239'] = '&iuml;';
        entities['240'] = '&eth;';
        entities['241'] = '&ntilde;';
        entities['242'] = '&ograve;';
        entities['243'] = '&oacute;';
        entities['244'] = '&ocirc;';
        entities['245'] = '&otilde;';
        entities['246'] = '&ouml;';
        entities['247'] = '&divide;';
        entities['248'] = '&oslash;';
        entities['249'] = '&ugrave;';
        entities['250'] = '&uacute;';
        entities['251'] = '&ucirc;';
        entities['252'] = '&uuml;';
        entities['253'] = '&yacute;';
        entities['254'] = '&thorn;';
        entities['255'] = '&yuml;';
    }
 
    if (useQuoteStyle !== 'ENT_NOQUOTES') {
        entities['34'] = '&quot;';
    }
    if (useQuoteStyle === 'ENT_QUOTES') {
        entities['39'] = '&#39;';
    }
    entities['60'] = '&lt;';
    entities['62'] = '&gt;';
 
    // ascii decimals for better compatibility
    entities['38'] = '&amp;';
 
    // ascii decimals to real symbols
    for (decimal in entities) {
        symbol = String.fromCharCode(decimal);
        histogram[symbol] = entities[decimal];
    }
    
    return histogram;
}


function strip_tags (str, allowed_tags) {
    // Strips HTML and PHP tags from a string  
    // 
    // version: 909.322
    // discuss at: http://phpjs.org/functions/strip_tags
    // +   original by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
    // +   improved by: Luke Godfrey
    // +      input by: Pul
    // +   bugfixed by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
    // +   bugfixed by: Onno Marsman
    // +      input by: Alex
    // +   bugfixed by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
    // +      input by: Marc Palau
    // +   improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
    // +      input by: Brett Zamir (http://brett-zamir.me)
    // +   bugfixed by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
    // +   bugfixed by: Eric Nagel
    // +      input by: Bobby Drake
    // +   bugfixed by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
    // +   bugfixed by: Tomasz Wesolowski
    // *     example 1: strip_tags('<p>Kevin</p> <b>van</b> <i>Zonneveld</i>', '<i><b>');
    // *     returns 1: 'Kevin <b>van</b> <i>Zonneveld</i>'
    // *     example 2: strip_tags('<p>Kevin <img src="someimage.png" onmouseover="someFunction()">van <i>Zonneveld</i></p>', '<p>');
    // *     returns 2: '<p>Kevin van Zonneveld</p>'
    // *     example 3: strip_tags("<a href='http://kevin.vanzonneveld.net'>Kevin van Zonneveld</a>", "<a>");
    // *     returns 3: '<a href='http://kevin.vanzonneveld.net'>Kevin van Zonneveld</a>'
    // *     example 4: strip_tags('1 < 5 5 > 1');
    // *     returns 4: '1 < 5 5 > 1'
    var key = '', allowed = false;
    var matches = [];
    var allowed_array = [];
    var allowed_tag = '';
    var i = 0;
    var k = '';
    var html = '';
 
    var replacer = function (search, replace, str) {
        return str.split(search).join(replace);
    };
 
    // Build allowes tags associative array
    if (allowed_tags) {
        allowed_array = allowed_tags.match(/([a-zA-Z0-9]+)/gi);
    }
 
    str += '';
 
    // Match tags
    matches = str.match(/(<\/?[\S][^>]*>)/gi);
 
    // Go through all HTML tags
    for (key in matches) {
        if (isNaN(key)) {
            // IE7 Hack
            continue;
        }
 
        // Save HTML tag
        html = matches[key].toString();
 
        // Is tag not in allowed list? Remove from str!
        allowed = false;
 
        // Go through all allowed tags
        for (k in allowed_array) {
            // Init
            allowed_tag = allowed_array[k];
            i = -1;
 
            if (i != 0) { i = html.toLowerCase().indexOf('<'+allowed_tag+'>');}
            if (i != 0) { i = html.toLowerCase().indexOf('<'+allowed_tag+' ');}
            if (i != 0) { i = html.toLowerCase().indexOf('</'+allowed_tag)   ;}
 
            // Determine
            if (i == 0) {
                allowed = true;
                break;
            }
        }
 
        if (!allowed) {
            str = replacer(html, "", str); // Custom replace. No regexing
        }
    }
 
    return str;
}
