
/*
    Copyright 2007 Matt Fellows 
    
    Email: Matt.Fellows@onegeek.com.au
    Web: http://www.onegeek.com.au
    
    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 3 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, see <http://www.gnu.org/licenses/>.
    
*/

/**
 * Serialize/Deserializer any JavaScript object to a valid XML String (NOT JSON)
 */
function JSSerializer() {
  
  /* Private members */
  var isIE = navigator.userAgent.toLowerCase().indexOf("msie") > -1;
  var isMoz = document.implementation && document.implementation.createDocument;
  
  // Unused parameters
  var use_encryption = false;
  var use_compression = false;
  
  /* Public attributes */
  
  /* Public methods */
  this.serialize = serialize;
  this.deserialize = deserialize;
  this.objectName = "";
  
  /**
   * Serialize a JS object into an XML String for storage / transmission
   * (i.e. cookie, download etc.)
   * 
   * @param {Object} objectToSerialize - The object to be serialized
   * @param {Object} objectName - (Optional) Name of the object being passed in
   * @param {Object} indentSpace - (Optional) Use this as an indentSpace
   * @return An String (XML document compressed and/or encrypted) specifying the object
   */
  function serialize(objectToSerialize, objectName, indentSpace) {
     indentSpace = indentSpace?indentSpace:'';
     
     var type = GetTypeName(objectToSerialize);

      
     var s = indentSpace  + '<' + objectName +  ' type="' + type + '">';
  
     switch(type){
      case "number":
      case "string":
      case "boolean": {
        s += objectToSerialize; 
      } 
     
      break;
       
       case "date":{
        s += objectToSerialize.toLocaleString(); 
       }
       break;
       
        case "Function": {
          s += "\n";        
          s += "<![CDATA["+objectToSerialize+"]]>";
          s += indentSpace;
        }
      break;     
       
      case "array": {
        s += "\n";
          
          for(var name in objectToSerialize){
            s += serialize(objectToSerialize[name], ('index' + name ), indentSpace + "   ");
          };
          
          s += indentSpace;
      }
      break;
          
      default: {
        s += "\n";
        
        for(var name in objectToSerialize){
          s += serialize(objectToSerialize[name], name, indentSpace + "   ");
        };
        
        s += indentSpace;
      }
      break;
  
     }
     
    s += "</" + objectName + ">\n"; 
       
      return s;
  };
  
  /**
   * Deserialize a serialized XML object into a javascript object
   * Uses deserial recursively to rebuild the javascript 
   * @see deserial
   * @param {Object} XmlText
   */
  function deserialize(XmlText) {
    var _doc = getDom(XmlText); 
    return deserial(_doc.childNodes[0]);
  }
  
  /**
   * Get the DOM object from an XML doc
   * NB: Works for IE and Mozilla
   * @param {Object} strXml
   */
  function getDom(strXml) {
    var _doc = null;
  
    if (isIE) {
      _doc = new ActiveXObject("Msxml2.DOMDocument.3.0");
      _doc.loadXML(strXml); 
    }
    else {
      var parser = new DOMParser();
      _doc = parser.parseFromString(strXml, "text/xml");
    }
  
    return _doc;
  }
  
  /**
   * Deserialize an XML DOM object into a javascript object
   * 
   * NB: This function uses recursion
   * @param {Object} domObject - The DOM object to deserialize into a JS Object
   */
  function deserial(domObject) {
    var retObj; 
    var nodeType = getNodeType(domObject);
     
    if (isSimpleVar(nodeType)) {
      if (isIE) {
        return StringToObject(domObject.text, nodeType);
      }
      else {
        return StringToObject(domObject.textContent, nodeType);
      }
    }
    
    switch(nodeType) {
      case "array": {
        return deserializeArray(domObject);
      }
      
      case "Function": {        
        return deserializeFunction(domObject);
      }   
      
      case "object":
      default: {
        try {
          retObj = eval("new "+ nodeType + "()");
          this.objectName=nodeType;

        }
        catch(e) {
          // create generic class
          retObj = new Object();
        }
      }
      break;
    }
    
    for(var i = 0; i < domObject.childNodes.length; i++) {
      var Node = domObject.childNodes[i];
      retObj[Node.nodeName] = deserial(Node);
    }
  
    return retObj;
  }
  
  /**
   * Check if the current element is one of the primitive data types
   * @param {String} type - The "type" attribute of the current node
   */
  function isSimpleVar(type)
  {
    switch(type) {
      case "int":
      case "string":
      case "String":
      case "Number":
      case "number":
      case "Boolean":
      case "boolean":
      case "bool":
      case "dateTime":
      case "Date":
      case "date":
      case "float":
        return true;
    }
    
    return false;
  }
  
  /**
   * Convert a string to an object
   * @param {String} text - The text to parse into the new object
   * @param {String} type - The type of object that you wish to parse TO
   */
  function StringToObject(text, type) {
    var retObj = null;
  
    switch(type)
    {
      case "int":
        return parseInt(text);   
         
      case "number": {
        var outNum;
        
        if (text.indexOf(".") > 0) {
          return parseFloat(text);    
        }
        else {
          return parseInt(text);    
        }
      } 
           
      case "string":
      case "String":
        return text;      retObj = [];
         
      case "dateTime":
      case "date":
      case "Date":
        return new Date(text);
          
      case "float":
        return parseFloat(text, 10);
        
      case "bool": {
          if (text == "true" || text == "True") {
            return true;
          }
          else {
            return false;
          }
        }
        return parseBool(text); 
    }
  
    return retObj;  
  }
  
  /**
   * Get the name of an object by extracting it from it's constructor attribute
   * @param {Object} obj - The object for which the name is to be found
   */
  function getClassName(obj) {   
    try {
      var ClassName = obj.constructor.toString();
      ClassName = ClassName.substring(ClassName.indexOf("function") + 8, ClassName.indexOf('(')).replace(/ /g,'');
      return ClassName;
    }
    catch(e) {
      return "NULL";
    }
  }
  
  /**
   * Get the type of Object by checking against the Built-in objects.
   * If no built in object is found, call getClassName
   * @see getClassName
   * @param {Object} obj - The object for which the type is to be found
   */ 
  function GetTypeName(obj) {
    if (obj instanceof Array)
      return "array";
      
    if (obj instanceof Date)
      return "date";  
      
    var type = typeof(obj);
  
    if (isSimpleVar(type)) {
      return type;
    }
    
    type = getClassName(obj); 
    
    return type;
  }
  
  /**
   * Deserialize an Array
   * @param {XML String} node - The node to deserialize into an Array
   * @return The deserialized Array 
   */
  function deserializeArray(node) {
    retObj = [];
          
    // Cycle through the array's TOP LEVEL children
    while(child = node.firstChild) {
      //alert('First Child: |' + child.textContent+"|")
      // delete child so it's children aren't recursed
      node.removeChild(node.firstChild);
                
      var nodeType = getNodeType(child);
      //alert(nodeType)
      
      if(isSimpleVar(nodeType)) {
        //alert('simple')
        retObj[retObj.length] = child.textContent;
      } else {
        //alert('complex var of type: '+nodeType +", name: "+child.nodeName +", content: "+child.textContent+", value: "+child.nodeValue)
        var tmp = child.textContent;
        if(child.textContent.trim() != '') {
          retObj[retObj.length] = deserial(child) 
        }           
      }                   
    }     
    return retObj;      
  }
  
  /**
   * Deserialize a Function
   * @param {XML String} node - The node to deserialize into a Function
   * @return The deserialized Function
   */ 
  function deserializeFunction(func) {
    if(func && func.text) {
      return eval(this.objectName + ".prototype." + func.tagName + "=" + func.text);
    }
  }
  
  /**
   * Get the type attribute of an element if there is one,
   * otherwise return generic 'object'
   * 
   * NB: This function is used on the resulting serialized XML and not on
   *     any actual javascript object
   * @param {XML} node - The node for which the type is to be found
   */   
  function getNodeType(node) {
    var nodeType = "object";
    if(node != null){
		if (node.attributes != null && node.attributes.length != 0) {
		  var tmp = node.attributes.getNamedItem("type");
		  if (tmp != null) {
		    nodeType = node.attributes.getNamedItem("type").nodeValue;
		  }
		}
    }
    
    return nodeType;  
  } 
}

/**
 * Trim spaces from a string
 * @usage stringObject.trim();
 */
if(!String.prototype.trim) {
  String.prototype.trim = function() {
  a = this.replace(/^\s+/, '');
  return a.replace(/\s+$/, '');
  };
}

  /**
    * o------------------------------------------------------------------------------o
    * | This package is licensed under the Phpguru license. A quick summary is       |
    * | that for commercial use, there is a small one-time licensing fee to pay. For |
    * | registered charities and educational institutes there is a reduced license   |
    * | fee available. You can read more  at:                                        |
    * |                                                                              |
    * |                  http://www.phpguru.org/static/license.html                  |
    * o------------------------------------------------------------------------------o
    *
    * © Copyright 2008,2009 Richard Heyes
    */

    /**
    * Unserializes a PHP serialized data type. Currently handles:
    *  o Strings
    *  o Integers
    *  o Doubles
    *  o Arrays
    *  o Booleans
    *  o NULL
    *  o Objects
    * 
    * alert()s will be thrown if the function is passed something it
    * can't handle or incorrect data.
    *
    * @param  string input The serialized PHP data
    * @return mixed        The resulting datatype
    */
    function PHP_Unserialize(input)
    {
        var result = PHP_Unserialize_(input);
        return result[0];
    }


    /**
    * Function which performs the actual unserializing
    *
    * @param string input Input to parse
    */
    function PHP_Unserialize_(input)
    {
        var length = 0;
        
        switch (input.charAt(0)) {
            /**
            * Array
            */
            case 'a':
                length = PHP_Unserialize_GetLength(input);
                input  = input.substr(String(length).length + 4);

                var arr   = new Array();
                var key   = null;
                var value = null;

                for (var i=0; i<length; ++i) {
                    key   = PHP_Unserialize_(input);
                    input = key[1];

                    value = PHP_Unserialize_(input);
                    input = value[1];

                    arr[key[0]] = value[0];
                }

                input = input.substr(1);
                return [arr, input];
                break;
            
            /**
            * Objects
            */
            case 'O':
                length = PHP_Unserialize_GetLength(input);
                var classname = String(input.substr(String(length).length + 4, length));
                
                input  = input.substr(String(length).length + 6 + length);
                var numProperties = Number(input.substring(0, input.indexOf(':')))
                input = input.substr(String(numProperties).length + 2);

                var obj      = new Object();
                var property = null;
                var value    = null;

                for (var i=0; i<numProperties; ++i) {
                    key   = PHP_Unserialize_(input);
                    input = key[1];
                    
                    // Handle private/protected
                    key[0] = key[0].replace(new RegExp('^\x00' + classname + '\x00'), '');
                    key[0] = key[0].replace(new RegExp('^\x00\\*\x00'), '');

                    value = PHP_Unserialize_(input);
                    input = value[1];

                    obj[key[0]] = value[0];
                }

                input = input.substr(1);
                return [obj, input];
                break;

            /**
            * Strings
            */
            case 's':
                length = PHP_Unserialize_GetLength(input);
                return [String(input.substr(String(length).length + 4, length)), input.substr(String(length).length + 6 + length)];
                break;

            /**
            * Integers and doubles
            */
            case 'i':
            case 'd':
                var num = Number(input.substring(2, input.indexOf(';')));
                return [num, input.substr(String(num).length + 3)];
                break;
            
            /**
            * Booleans
            */
            case 'b':
                var bool = (input.substr(2, 1) == 1);
                return [bool, input.substr(4)];
                break;
            
            /**
            * Null
            */
            case 'N':
                return [null, input.substr(2)];
                break;

            /**
            * Unsupported
            */
            case 'o':
            case 'r':
            case 'C':
            case 'R':
            case 'U':
                alert('Error: Unsupported PHP data type found!');

            /**
            * Error
            */
            default:
                return [null, null];
                break;
        }
    }
    

    /**
    * Returns length of strings/arrays etc
    *
    * @param string input Input to parse
    */
    function PHP_Unserialize_GetLength(input)
    {
        input = input.substring(2);
        var length = Number(input.substr(0, input.indexOf(':')));
        return length;
    }

function serialize (mixed_value) {
    // http://kevin.vanzonneveld.net
    // +   original by: Arpad Ray (mailto:arpad@php.net)
    // +   improved by: Dino
    // +   bugfixed by: Andrej Pavlovic
    // +   bugfixed by: Garagoth
    // +      input by: DtTvB (http://dt.in.th/2008-09-16.string-length-in-bytes.html)
    // +   bugfixed by: Russell Walker (http://www.nbill.co.uk/)
    // +   bugfixed by: Jamie Beck (http://www.terabit.ca/)
    // %          note: We feel the main purpose of this function should be to ease the transport of data between php & js
    // %          note: Aiming for PHP-compatibility, we have to translate objects to arrays
    // *     example 1: serialize(['Kevin', 'van', 'Zonneveld']);
    // *     returns 1: 'a:3:{i:0;s:5:"Kevin";i:1;s:3:"van";i:2;s:9:"Zonneveld";}'
    // *     example 2: serialize({firstName: 'Kevin', midName: 'van', surName: 'Zonneveld'});
    // *     returns 2: 'a:3:{s:9:"firstName";s:5:"Kevin";s:7:"midName";s:3:"van";s:7:"surName";s:9:"Zonneveld";}'
 
    var _getType = function (inp) {
        var type = typeof inp, match;
        var key;
        if (type == 'object' && !inp) {
            return 'null';
        }
        if (type == "object") {
            if (!inp.constructor) {
                return 'object';
            }
            var cons = inp.constructor.toString();
            match = cons.match(/(\w+)\(/);
            if (match) {
                cons = match[1].toLowerCase();
            }
            var types = ["boolean", "number", "string", "array"];
            for (key in types) {
                if (cons == types[key]) {
                    type = types[key];
                    break;
                }
            }
        }
        return type;
    };
    var type = _getType(mixed_value);
    var val, ktype = '';
    
    switch (type) {
        case "function": 
            val = ""; 
            break;
        case "boolean":
            val = "b:" + (mixed_value ? "1" : "0");
            break;
        case "number":
            val = (Math.round(mixed_value) == mixed_value ? "i" : "d") + ":" + mixed_value;
            break;
        case "string":
            val = "s:" + encodeURIComponent(mixed_value).replace(/%../g, 'x').length + ":\"" + mixed_value + "\"";
            break;
        case "array":
        case "object":
            val = "a";
            
            if (type == "object") {
                var objname = mixed_value.constructor.toString().match(/(\w+)\(\)/);
                if (objname == undefined) {
                    return;
                }
                objname[1] = this.serialize(objname[1]);
                var name = objname.input.substring("function ".length,objname.input.indexOf("("));
                val = "O:" + name.length + ":\"" + name + "\"" ;
                
                //val = "O" + objname[1].substring(1, objname[1].length - 1);
            }
            
            var count = 0;
            var vals = "";
            var okey;
            var key;
            for (key in mixed_value) {
                ktype = _getType(mixed_value[key]);
                if (ktype == "function") { 
                    continue; 
                }
                
                okey = (key.match(/^[0-9]+$/) ? parseInt(key, 10) : key);
                vals += this.serialize(okey) +
                        this.serialize(mixed_value[key]);
                count++;
            }
            val += ":" + count + ":{" + vals + "}";
            break;
        case "undefined": // Fall-through
        default: // if the JS object has a property which contains a null value, the string cannot be unserialized by PHP
            val = "N";
            break;
    }
    if (type != "object" && type != "array") {
        val += ";";
    }
    return val;
}


function unserialize ( inp ) {    
// http://kevin.vanzonneveld.net    
// +   original by: Arpad Ray (mailto:arpad@php.net)    
// *     example 1: JSON.parse();   
 // *     returns 1: ['Kevin', 'van', 'Zonneveld']  //   
 //*******************************************************************************  //  // *   Reviewed at: 19/03/2008  
 // +            by: Pedro Tainha (email@pedrotainha.com or pedrotainha@gmail.com)  //  
 // Notes:  //  Fixed bugs for the cases: 'b'(boolean) and 'a'(array)  //  //*******************************************************************************       
  error = 0;   
  if (inp == "" || inp.length < 2) { errormsg = "input is too short";return; } 
  var val, kret, vret, cval;    
  var type = inp.charAt(0);    
  var cont = inp.substring(2);    
  var size = 0, divpos = 0, endcont = 0, rest = "", next = "";     
  
  switch (type) {    
	case "N": // null  
        if (inp.charAt(1) != ";") {            errormsg = "missing ; for null";        }   
             // leave val undefined    
                 rest = cont;        break;    
   case "b": // boolean        
		if (!/[01];/.test(cont.substring(0,2))) {            errormsg = "value not 0 or 1, or missing ; for boolean";        }        
		val = (cont.charAt(0) == "1");        rest = cont.substring(2);  //changed...        
	    break;    
   case "s": // string        
		val = "";        divpos = cont.indexOf(":");        if (divpos == -1) {            errormsg = "missing : for string";            break;        } 
		size = parseInt(cont.substring(0, divpos));        if (size == 0) {            
		if (cont.length - divpos < 4) {                errormsg = "string is too short";                break;            }            
		rest = cont.substring(divpos + 4);            break;        }        if ((cont.length - divpos - size) < 4) {           
		 errormsg = "string is too short";            break;        }        if (cont.substring(divpos + 2 + size, divpos + 4 + size) != "\";") 
		 {            errormsg = "string is too long, or missing \";";        }        
		 val = cont.substring(divpos + 2, divpos + 2 + size);        rest = cont.substring(divpos + 4 + size);        break;   
    case "i": // integer    
    case "d": // float          
			var dotfound = 0;        for (var t = 0; t < cont.length; t++) {            cval = cont.charAt(t);            
			if (isNaN(parseInt(cval)) && !(type == "d" && cval == "." && !dotfound++)) {                endcont = t;                break;            }        }        
			if (!endcont || cont.charAt(endcont) != ";") {            errormsg = "missing or invalid value, or missing ; for int/float";        }        
			val = cont.substring(0, endcont);        val = (type == "i" ? parseInt(val) : parseFloat(val));        rest = cont.substring(endcont + 1);        break;    
	case "a": // array        
		if (cont.length < 3) {
			errormsg = "array is too short";
			return;
	    }    		    
		divpos = cont.indexOf(":", 1);  
		if (divpos == -1) {            
			errormsg = "missing : for array";            return;        
		}        
		size = parseInt(cont.substring(1*divpos, 0));  //changed...          
		cont = cont.substring(divpos + 2);        
		val = new Array();        
		if (cont.length < 1) {
		    errormsg = "array is too short";            
		    return;        
		} 
		for (var i = 0; i + 1 < size * 2; i += 2) {
			 kret = JSON.parse(cont, 1);                 
			 if (error || kret[0] == undefined || kret[1] == "") {
			                 errormsg = "missing or invalid key, or missing value for array";                
			                 return;  
			 
			 }                  
			 vret = JSON.parse(kret[1], 1);                
				if (error) {
				       errormsg = "invalid value for array";   
				       return;   
			          }
			val[kret[0]] = vret[0];            
			cont = vret[1];        
		}          
		if (cont.charAt(0) != "}") {
		    errormsg = "missing ending }, or too many values for array";            
		    return;        
		}          
		rest = cont.substring(1);        
		break;   
   case "O": // object        
		divpos = cont.indexOf(":");       
			 if (divpos == -1) {errormsg = "missing : for object";return;}
			        
			  size = parseInt(cont.substring(0, divpos));        
			  var objname = cont.substring(divpos + 2, divpos + 2 + size);      
			    if (cont.substring(divpos + 2 + size, divpos + 4 + size) != "\":") {errormsg = "object name is too long, or missing \":";return;}
			    var objprops = JSON.parse("a:" + cont.substring(divpos + 4 + size), 1);         
			    if (error) {            errormsg = "invalid object properties";            return;        }
			    rest = objprops[1];          
			    var i =0;
			    var counter=0;
			    for (key in objprops[0]) 
			              counter++;  

			    var objout = "function " + objname + "(";
			    for (key in objprops[0]) {            
						if(i == counter - 1)
							objout += "" + key + "){"; 
						else
							objout += "" + key + ","; 
						i++;	
				}


			    i =0;
			    
			    for (key in objprops[0]) {
						    
							var ret = eval("objprops[0]." + key + "[0]");
						    if(ret == undefined)        
								objout += "this." + key + "=objprops[0]['" + key + "'];";
							else{							
								objout += "this." + key + "=new Array(";
								var j = eval("objprops[0]." + key + ".length");
								var k =0;
								for(k=0;k < j;k++)
									if (k != j - 1)
										objout +=  "objprops[0]." +  key + "[" + k + "],";
									else
										objout +=  "objprops[0]." +  key + "[" + k + "]);";
										
							}				 
				}
						
						objout += "} val=new " + objname + "();";        
						eval(objout);
		break;    
	    default:        
				errormsg = "invalid input type";    
	}      
	return (arguments.length == 1 ? val : [val, rest]);
}
			          
/**
 * Implements JSON stringify and parse functions
 * v1.0
 *
 * By Craig Buckler, Optimalworks.net
 *
 * As featured on SitePoint.com
 * Please use as you wish at your own risk.
*
 * Usage:
 *
 * // serialize a JavaScript object to a JSON string
 * var str = JSON.stringify(object);
 *
 * // de-serialize a JSON string to a JavaScript object
 * var obj = JSON.parse(str); PHP json_decode
 */ 

var JSON = JSON || {};

// implement JSON.stringify serialization
JSON.stringify = JSON.stringify || function (obj) {

	var t = typeof (obj);
	if (t != "object" || obj === null) {

		// simple data type
		if (t == "string") obj = '"'+obj+'"';
		return String(obj);

	}
	else {

		// recurse array or object
		var n, v, json = [], arr = (obj && obj.constructor == Array);

		for (n in obj) {
			v = obj[n]; t = typeof(v);

			if (t == "string") v = '"'+v+'"';
			else if (t == "object" && v !== null) v = JSON.stringify(v);

			json.push((arr ? "" : '"' + n + '":') + String(v));
		}

		return (arr ? "[" : "{") + String(json) + (arr ? "]" : "}");
	}
};


// implement JSON.parse de-serialization
JSON.parse = JSON.parse || function (str) {
	if (str === "") str = '""';
	eval("var p=" + str + ";");
	return p;
};


/**
 * Object PHP_Serializer
 * 	JavaScript to PHP serialize / unserialize class.
 * This class converts php variables to javascript and vice versa.
 *
 * PARSABLE JAVASCRIPT < === > PHP VARIABLES:
 *	[ JAVASCRIPT TYPE ]		[ PHP TYPE ]
 *	Array		< === > 	array
 *	Object		< === > 	class (*)
 *	String		< === > 	string
 *	Boolean		< === > 	boolean
 *	null		< === > 	null
 *	Number		< === > 	int or double
 *	Date		< === > 	class
 *	Error		< === > 	class
 *	Function	< === > 	class (*)
 *
 * (*) NOTE:
 * Any PHP serialized class requires the native PHP class to be used, then it's not a
 * PHP => JavaScript converter, it's just a usefull serilizer class for each
 * compatible JS and PHP variable types.
 * Lambda, Resources or other dedicated PHP variables are not usefull for JavaScript.
 * There are same restrictions for javascript functions*** too then these will not be sent.
 *
 * *** function test(); alert(php.serialize(test)); will be empty string but
 * *** mytest = new test(); will be sent as test class to php
 * _____________________________________________
 *
 * EXAMPLE:
 *	var php = new PHP_Serializer(); // use new PHP_Serializer(true); to enable UTF8 compatibility
 *	alert(php.unserialize(php.serialize(somevar)));
 *	// should alert the original value of somevar
 * ---------------------------------------------
 * @author              Andrea Giammarchi
 * @site		www.devpro.it
 * @date                2005/11/26
 * @lastmod             2006/05/15 19:00 [modified stringBytes method and removed replace for UTF8 and \r\n]
 * 			[add UTF8 var again, PHP strings if are not encoded with utf8_encode aren't compatible with this object]
 *			[Partially rewrote for a better stability and compatibility with Safari or KDE based browsers]
 *			[UTF-8 now has a native support, strings are converted automatically with ISO or UTF-8 charset]
 *
 * @specialthanks	Fabio Sutto, Kentaromiura, Kroc Camen, Cecile Maigrot, John C.Scott, Matteo Galli
 *
 * @version             2.2, tested on FF 1.0, 1.5, IE 5, 5.5, 6, 7 beta 2, Opera 8.5, Konqueror 3.5, Safari 2.0.3
 */
function PHP_Serializer(UTF8) {
	
	/** public methods */
	function serialize(v) {
		// returns serialized var
		var	s;
		switch(v) {
			case null:
				s = "N;";
				break;
			default:
				s = this[this.__sc2s(v)] ? this[this.__sc2s(v)](v) : this[this.__sc2s(__o)](v);
				break;
		};
		return s;
	};
	
	function unserialize(s) {
		// returns unserialized var from a php serialized string
		__c = 0;
		__s = s;
		return this[__s.substr(__c, 1)]();
	};
	
	function stringBytes(s) {
		// returns the php lenght of a string (chars, not bytes)
		return s.length;
	};
	
	function stringBytesUTF8(s) {
		// returns the php lenght of a string (bytes, not chars)
		var 	c, b = 0,
			l = s.length;
		while(l) {
			c = s.charCodeAt(--l);
			b += (c < 128) ? 1 : ((c < 2048) ? 2 : ((c < 65536) ? 3 : 4));
		};
		return b;
	};
	
	/** private methods */
	function __sc2s(v) {
		return v.constructor.toString();
	};
	
	function __sc2sKonqueror(v) {
		var	f;
		switch(typeof(v)) {
			case ("string" || v instanceof String):
				f = "__sString";
				break;
			case ("number" || v instanceof Number):
				f = "__sNumber";
				break;
			case ("boolean" || v instanceof Boolean):
				f = "__sBoolean";
				break;
			case ("function" || v instanceof Function):
				f = "__sFunction";
				break;
			default:
				f = (v instanceof Array) ? "__sArray" : "__sObject";
				break;
		};
		return f;
	};
	
	function __sNConstructor(c) {
		return (c === "[function]" || c === "(Internal Function)");
	};
	
	function __sCommonAO(v) {
		var	b, n,
			a = 0,
			s = [];
		for(b in v) {
			n = v[b] == null;
			if(n || v[b].constructor != Function) {
				s[a] = [
					(!isNaN(b) && parseInt(b).toString() === b ? this.__sNumber(b) : this.__sString(b)),
					(n ? "N;" : this[this.__sc2s(v[b])] ? this[this.__sc2s(v[b])](v[b]) : this[this.__sc2s(__o)](v[b]))
				].join("");
				++a;
			};
		};
		return [a, s.join("")];
	};
	
	function __sBoolean(v) {
		return ["b:", (v ? "1" : "0"), ";"].join("");
	};
	
	function __sNumber(v) {
		var 	s = v.toString();
		return (s.indexOf(".") < 0 ? ["i:", s, ";"] : ["d:", s, ";"]).join("");
	};
	
	function __sString(v) {
		return ["s:", v.length, ":\"", v, "\";"].join("");
	};
	
	function __sStringUTF8(v) {
		return ["s:", this.stringBytes(v), ":\"", v, "\";"].join("");
	};
	
	function __sArray(v) {
		var 	s = this.__sCommonAO(v);
		return ["a:", s[0], ":{", s[1], "}"].join("");
	};
	
	function __sObject(v) {
		var 	o = this.__sc2s(v),
			n = o.substr(__n, (o.indexOf("(") - __n)),
			s = this.__sCommonAO(v);
		return ["O:", this.stringBytes(n), ":\"", n, "\":", s[0], ":{", s[1], "}"].join("");
	};
	
	function __sObjectIE7(v) {
		var 	o = this.__sc2s(v),
			n = o.substr(__n, (o.indexOf("(") - __n)),
			s = this.__sCommonAO(v);
		if(n.charAt(0) === " ")
			n = n.substring(1);
		return ["O:", this.stringBytes(n), ":\"", n, "\":", s[0], ":{", s[1], "}"].join("");
	};
	
	function __sObjectKonqueror(v) {
		var	o = v.constructor.toString(),
			n = this.__sNConstructor(o) ? "Object" : o.substr(__n, (o.indexOf("(") - __n)),
			s = this.__sCommonAO(v);
		return ["O:", this.stringBytes(n), ":\"", n, "\":", s[0], ":{", s[1], "}"].join("");
	};
	
	function __sFunction(v) {
		return "";
	};
	
	function __uCommonAO(tmp) {
		var	a, k;
		++__c;
		a = __s.indexOf(":", ++__c);
		k = parseInt(__s.substr(__c, (a - __c))) + 1;
		__c = a + 2;
		while(--k)
			tmp[this[__s.substr(__c, 1)]()] = this[__s.substr(__c, 1)]();
		return tmp;
	};

	function __uBoolean() {
		var	b = __s.substr((__c + 2), 1) === "1" ? true : false;
		__c += 4;
		return b;
	};
	
	function __uNumber() {
		var	sli = __s.indexOf(";", (__c + 1)) - 2,
			n = Number(__s.substr((__c + 2), (sli - __c)));
		__c = sli + 3;
		return n;
	};
	
	function __uStringUTF8() {
		var 	c, sls, sli, vls,
			pos = 0;
		__c += 2;
		sls = __s.substr(__c, (__s.indexOf(":", __c) - __c));
		sli = parseInt(sls);
		vls = sls = __c + sls.length + 2;
		while(sli) {
			c = __s.charCodeAt(vls);
			pos += (c < 128) ? 1 : ((c < 2048) ? 2 : ((c < 65536) ? 3 : 4));
			++vls;
			if(pos === sli)
				sli = 0;
		};
		pos = (vls - sls);
		__c = sls + pos + 2;
		return __s.substr(sls, pos);
	};
	
	function __uString() {
		var 	sls, sli;
		__c += 2;
		sls = __s.substr(__c, (__s.indexOf(":", __c) - __c));
		sli = parseInt(sls);
		sls = __c + sls.length + 2;
		__c = sls + sli + 2;
		return __s.substr(sls, sli);
	};
	
	function __uArray() {
		var	a = this.__uCommonAO([]);
		++__c;
		return a;
	};
	
	function __uObject() {
		var 	tmp = ["s", __s.substr(++__c, (__s.indexOf(":", (__c + 3)) - __c))].join(""),
			a = tmp.indexOf("\""),
			l = tmp.length - 2,
			o = tmp.substr((a + 1), (l - a));
		if(eval(["typeof(", o, ") === 'undefined'"].join("")))
			eval(["function ", o, "(){};"].join(""));
		__c += l;
		eval(["tmp = this.__uCommonAO(new ", o, "());"].join(""));
		++__c;
		return tmp;
	};
	
	function __uNull() {
		__c += 2;
		return null;
	};
	
	function __constructorCutLength() {
		function ie7bugCheck(){};
		var	o1 = new ie7bugCheck(),
			o2 = new Object(),
			c1 = __sc2s(o1),
			c2 = __sc2s(o2);
		if(c1.charAt(0) !== c2.charAt(0))
			__ie7 = true;
		return (__ie7 || c2.indexOf("(") !== 16) ? 9 : 10;
	};
	
	/** private variables */
	var 	__c = 0,
		__ie7 = false,
		__b = __sNConstructor(__c.constructor.toString()),
		__n = __b ? 9 : __constructorCutLength(),
		__s = "",
		__a = [],
		__o = {},
		__f = function(){};
	
	/** public prototypes */
	PHP_Serializer.prototype.serialize = serialize;
	PHP_Serializer.prototype.unserialize = unserialize;
	PHP_Serializer.prototype.stringBytes = UTF8 ? stringBytesUTF8 : stringBytes;
	
	/** serialize: private prototypes */
	if(__b) { // Konqueror / Safari prototypes
		PHP_Serializer.prototype.__sc2s = __sc2sKonqueror;
		PHP_Serializer.prototype.__sNConstructor = __sNConstructor;
		PHP_Serializer.prototype.__sCommonAO = __sCommonAO;
		PHP_Serializer.prototype[__sc2sKonqueror(__b)] = __sBoolean;
		PHP_Serializer.prototype.__sNumber = 
		PHP_Serializer.prototype[__sc2sKonqueror(__n)] = __sNumber;
		PHP_Serializer.prototype.__sString = PHP_Serializer.prototype[__sc2sKonqueror(__s)] = UTF8 ? __sStringUTF8 : __sString;
		PHP_Serializer.prototype[__sc2sKonqueror(__a)] = __sArray;
		PHP_Serializer.prototype[__sc2sKonqueror(__o)] = __sObjectKonqueror;
		PHP_Serializer.prototype[__sc2sKonqueror(__f)] = __sFunction;
	}
	else { // FireFox, IE, Opera prototypes
		PHP_Serializer.prototype.__sc2s = __sc2s;
		PHP_Serializer.prototype.__sCommonAO = __sCommonAO;
		PHP_Serializer.prototype[__sc2s(__b)] = __sBoolean;
		PHP_Serializer.prototype.__sNumber = 
		PHP_Serializer.prototype[__sc2s(__n)] = __sNumber;
		PHP_Serializer.prototype.__sString = PHP_Serializer.prototype[__sc2s(__s)] = UTF8 ? __sStringUTF8 : __sString;
		PHP_Serializer.prototype[__sc2s(__a)] = __sArray;
		PHP_Serializer.prototype[__sc2s(__o)] = __ie7 ? __sObjectIE7 : __sObject;
		PHP_Serializer.prototype[__sc2s(__f)] = __sFunction;
	};
	
	/** unserialize: private prototypes */
	PHP_Serializer.prototype.__uCommonAO = __uCommonAO;
	PHP_Serializer.prototype.b = __uBoolean;
	PHP_Serializer.prototype.i =
	PHP_Serializer.prototype.d = __uNumber;
	PHP_Serializer.prototype.s = UTF8 ? __uStringUTF8 : __uString;
	PHP_Serializer.prototype.a = __uArray;
	PHP_Serializer.prototype.O = __uObject;
	PHP_Serializer.prototype.N = __uNull;
};
