<?php
/**
 * api:		php
 * title:	upgrade.php
 * description:	Emulates functions from new PHP versions on older interpreters.
 * version:	20
 * license:	Public Domain
 * url:		http://freshmeat.net/projects/upgradephp
 * type:	functions
 * category:	library
 * priority:	auto
 * load_if:     (PHP_VERSION<5.3)
 * sort:	-255
 * provides:	upgrade-php, api:php5, json
 *
 *
 * By loading this library you get PHP version independence. It provides
 * downwards compatibility to older PHP interpreters by emulating missing
 * functions or constants using IDENTICAL NAMES. So this doesn't slow down
 * script execution on setups woth native support exists. It's just meant
 * as quick drop-in solution. It spares you from rewriting code or using
 * cumbersome workarounds instead of the richer 5.3+ function set.
 * 
 * It cannot mirror PHP5s extended OO-semantics and functionality into PHP4
 * however. A few features are added here that weren't part of PHP yet. And
 * some other function collections are separated out into the ext/ directory.
 * It doesn't produce many custom error messages (YAGNI), and instead leaves
 * reporting to invoked functions or for native PHP execution.
 * 
 * And further this is PUBLIC DOMAIN (no copyright, no license, no warranty)
 * so therefore compatible to ALL open source licenses. You could rip this
 * paragraph out to republish this instead only under more restrictive terms
 * or your favorite license (GNU LGPL/GPL, BSDL, MPL/CDDL, Artistic/PHPL, ..)
 *
 * Any contribution is appreciated. <milky*users#sf#net>
 *
 */





/**
 *                                   -------------------------- FUTURE ---
 * @group SVN
 * @since future
 *
 * Following functions aren't implemented in current PHP versions, but
 * might already be in CVS/SVN.
 *
 * @removed
 *    setcookie2
 *
 */



/**
 *                                   ------------------------ OVERRIDE ---
 *
 */
if (defined("UPGRADEPHP_OVERRIDE") and function_exists("runkit_function_remove")) {
   ini_set("runkit.internal_override", 1);
   runkit_function_rename("json_encode", "php::json_encode");
   runkit_function_rename("json_decode", "php::json_decode");
 }

/**
 *                                   ----------------------------- 5.5 ---
 * @group 5_5
 * @since 5.5
 *
 * Extensions in PHP 5.5
 *
 * @emulated
 *    boolval
 *    array_column
 *    json_last_error_msg
 *
 * @missing
 *    date_create_immutable
 *    date_create_immutable_from_format
 *    openssl_pbkdf2
 *    hash_pbkdf2
 *
 */




/**
 * Creates a new array from excerpting columns from a list of arrays. Optionally
 * uses a key column from there for indexing.
 *
 * Full reimplementation at https://github.com/ramsey/array_column/blob/master/src/array_column.php
 *
 */
if (!function_exists("array_column")) {
   function array_column($array, $column, $key=NULL) {

      $result = array();
      assert('isset($column) /*array_column() expects two params*/')
      and assert('is_scalar($column) && is_scalar($key) /*array_column() key and column should be ints/strings*/')
      and assert('is_array($array) /*array_column() input array isn\'t one*/');

      // traverse array
      foreach ($array as $row) {
         if (isset($row[$column])) {
       
            // fetch as ordered list 
            if (($key === NULL) || !isset($row[$key]))  {   // this is odd in the native implementation, if the $key column is absent, it just appends
               $result[] = $row[$column];
            }   
           
            // or retain key value from another column for indexing
            else {
               //isset($result[$row[$key]]) and trigger_error("array_column(): a key occured twice, value was overwritten", E_USER_NOTICE);
               $result[ $row[$key] ] = $row[$column];
            }
         }
      }
      return $result;
   }
}


/**
 * That's basically just a typecast. Returns the PHP-interpreted booleanish equivalent of values.
 * Just exists for parity with intval/floatval/etc.
 *
 */
if (!function_exists("boolval")) {
   function boolval($var) {
      return (bool)$var;
   }
}



/**
 * Convert json_last_error() numbers into readable string:
 *
 */
if (!function_exists("json_last_error_msg")) {
   function json_last_error_msg($num) {
      $msgs = array(
         JSON_ERROR_NONE => "No error has occurred",
         JSON_ERROR_DEPTH => "The maximum stack depth has been exceeded",
         JSON_ERROR_STATE_MISMATCH => "Invalid or malformed JSON",
         JSON_ERROR_CTRL_CHAR => "Control character error, possibly incorrectly encoded",
         JSON_ERROR_SYNTAX => "Syntax error",
         JSON_ERROR_UTF8 => "Malformed UTF-8 characters, possibly incorrectly encoded",
         JSON_ERROR_RECURSION => "One or more recursive references in the value to be encoded",
         JSON_ERROR_INF_OR_NAN => "One or more NAN or INF values in the value to be encoded",
         JSON_ERROR_UNSUPPORTED_TYPE => "A value of a type that cannot be encoded was given",
      );
      return $msgs[$num];
   }
}






/**
 *                                   ----------------------------- 5.4 ---
 * @group 5_4
 * @since 5.4
 *
 * Extensions in PHP 5.4
 *
 * @emulated
 *    gzdecode
 *    hex2bin
 *    session_status -> basic probing
 *
 * @stub
 *    class_uses
 *    trait_exists
 *    get_declared_traits
 *
 * @missing
 *    libxml_set_external_entity_loader
 *    zlib_encode -> PHP_ZLIB_ENCODE_FUNC(zlib_encode, 0);
 *    zlib_decode -> PHP_ZLIB_DECODE_FUNC(zlib_decode, PHP_ZLIB_ENCODING_ANY);
 *    session_register_shutdown
 *    socket_import_stream
 *    getimagesizefromstring
 *    header_register_callback
 *    http_response_code
 *    stream_set_chunk_size
 *    CallbackFilterIterator
 *    RecursiveCallbackFilterIterator
 *    SessionHandler
 *    ReflectionZendExtension
 *
 * @unimplementable
 *    imageantialias
 *    imagelayereffect
 *
 */



/**
 * Simple convenience function for pack H*,
 * Converts a hextuplet string into its binary representation.
 *
 */
if (!function_exists("hex2bin")) {
   function hex2bin($hex) {
       return pack("H*", $hex);
   }
}



/**
 * Set or get HTTP status code.
 *
 */
if (!function_exists("http_response_code")) {
   function http_response_code($which=NULL) {

       $cgi = ini_get("cgi.rfc2616_headers");
       $headers = preg_grep("#^Status:|^HTTP/\d\.\d#i", headers_list());

       # override       
       if ($which >= 100 and $which <= 999) {
           if ($headers) {
               header_remove(current($headers));
           }
           # implicit notices for headers_sent()
           header($cgi ? "HTTP/1.0 $which n/a" : "Status: $which n/a");
       }
       # get current
       elseif ($which == NULL) {
           if (!$headers || !preg_match("/\d\d\d/", current($headers), $m)) {
               return 200; #default
           }
           return intval($m[0]);
       }
       else trigger_error("invalid status number", E_USER_WARNING);
   }
}



/**
 * Sends a Location: header. Unlike the PHP-builtin, it won't complete relative URLs.
 * So it's RFC6616 compliant, not anal about the original HTTP/1.1 revision RFC2616.
 * The <meta> fallback is also extraneous.
 * Belongs to http extensions actually. (Will be grouped out in later upgradephp revision.)
 *
 */
if (!function_exists("http_redirect")) {
   function http_redirect($url, $params=array(), $session=false, $status=0) {
       if ($session) {
           $params[session_name()] = session_id();
       }
       if ($params) {
           $url .= strstr($url, "?") ? "&" : "?";
           $url .= http_build_query($params);
       }
       header("Location: $url"); #, $status ? $status : 301);
       $url = htmlspecialchars($url, ENT_QUOTES, "UTF-8");
       print "Redirecting to <a href=\"$url\">$url</a>\n";
       print "<meta http-equiv=\"Location\" content=\"$url\" />\n";
       exit; // built-in exit
   }
}



/**
 * Sends a Content-Type: header
 * Belongs to http extensions actually. (Will be grouped out in later upgradephp revision.)
 *
 */
if (!function_exists("http_send_content_type")) {
   function http_send_content_type($content_type="application/x-octetstream") {
       return header("Content-Type: $content_type");
   }
}



/**
 * @stub
 * Traits (partial classes, elaborate syntactic and academic workaround, because MI is
 * hard to implement in *compiled* languages) cannot be emulated in older interpreters.
 *
 */
if (!function_exists("class_uses")) {
   function class_uses($trait) {
       return false;
   }
}
if (!function_exists("trait_exists")) {
   function trait_exists($trait) {
       return false;
   }
}
if (!function_exists("get_declared_traits")) {
   function get_declared_traits($trait) {
       return (array)NULL;
   }
}



/**
 * Long predicted, officially available @since 5.4.
 *
 * Inflates a string enriched with gzip headers. Counterpart to gzencode().
 *
 */
if (!function_exists("gzdecode")) {
   function gzdecode($gzdata, $maxlen=NULL) {

      #-- decode header
      $len = strlen($gzdata);
      if ($len < 20) {
         return;
      }
      $head = substr($gzdata, 0, 10);
      $head = unpack("n1id/C1cm/C1flg/V1mtime/C1xfl/C1os", $head);
      list($ID, $CM, $FLG, $MTIME, $XFL, $OS) = array_values($head);
      $FTEXT = 1<<0;
      $FHCRC = 1<<1;
      $FEXTRA = 1<<2;
      $FNAME = 1<<3;
      $FCOMMENT = 1<<4;
      $head = unpack("V1crc/V1isize", substr($gzdata, $len-8, 8));
      list($CRC32, $ISIZE) = array_values($head);

      #-- check gzip stream identifier
      if ($ID != 0x1f8b) {
         trigger_error("gzdecode: not in gzip format", E_USER_WARNING);
         return;
      }
      #-- check for deflate algorithm
      if ($CM != 8) {
         trigger_error("gzdecode: cannot decode anything but deflated streams", E_USER_WARNING);
         return;
      }

      #-- start of data, skip bonus fields
      $s = 10;
      if ($FLG & $FEXTRA) {
         $s += $XFL;
      }
      if ($FLG & $FNAME) {
         $s = strpos($gzdata, "\000", $s) + 1;
      }
      if ($FLG & $FCOMMENT) {
         $s = strpos($gzdata, "\000", $s) + 1;
      }
      if ($FLG & $FHCRC) {
         $s += 2;  // cannot check
      }
      
      #-- get data, uncompress
      $gzdata = substr($gzdata, $s, $len-$s);
      if ($maxlen) {
         $gzdata = gzinflate($gzdata, $maxlen);
         return($gzdata);  // no checks(?!)
      }
      else {
         $gzdata = gzinflate($gzdata);
      }
      
      #-- check+fin
      $chk = crc32($gzdata);
      if ($CRC32 != $chk) {
         trigger_error("gzdecode: checksum failed (real$chk != comp$CRC32)", E_USER_WARNING);
      }
      elseif ($ISIZE != strlen($gzdata)) {
         trigger_error("gzdecode: stream size mismatch", E_USER_WARNING);
      }
      else {
         return($gzdata);
      }
   }
}



/**
 * Probes for format(?) before decoding with one of the gz* functions.
 *
 */
if (!function_exists("zlib_decode")) {
   function zlib_decode($data) {
       if (!strncmp($data, "\x1F\x8B", 2)) {
           return gzdecode($data);
       }
       elseif (!strncmp($data, "\x78\x9C", 2)) {
           return gzuncompress($data);
       }
       else {
           return gzinflate($data);
       }
   }
}

/**
 * Weird constants, not documented in the manual yet, but that's how the function declaration looks.
 *
 */
if (!function_exists("zlib_encode")) {
   if (!defined("ZLIB_ENCODING_DEFLATE")) {
       define("ZLIB_ENCODING_DEFLATE", 15);
       define("ZLIB_ENCODING_RAW", -15);
       define("ZLIB_ENCODING_GZIP", 31);
   }
   function zlib_encode($data, $method) {
       if ($method == ZLIB_ENCODING_RAW) {
           return gzdeflate($data);
       }
       elseif ($method == ZLIB_ENCODING_DEFLATE) {
           return gzcompress($data);
       }
       elseif ($method == ZLIB_ENCODING_GZIP) {
           return gzencode($data);
       }
       else trigger_error("encoding mode must be either ZLIB_ENCODING_RAW, ZLIB_ENCODING_GZIP or ZLIB_ENCODING_DEFLATE", E_USER_WARNING);
   }
}


/**
 * @stub Tests whether a session is established.
 *
 */
if (!function_exists("session_status")) {
   if (!defined("PHP_SESSION_DISABLED")) {
       define("PHP_SESSION_DISABLED", 0);
       define("PHP_SESSION_NONE", 1);
       define("PHP_SESSION_ACTIVE", 2);
   }
   function session_status() {
       return (ini_get("session.name") != "") ? PHP_SESSION_DISABLED : 
           (isset($_SESSION) ? PHP_SESSION_ACTIVE : PHP_SESSION_NONE);
   }
}





/**
 *                                   ----------------------------- 5.3 ---
 * @group 5_3
 * @since 5.3
 *
 * Known additions of PHP 5.3
 *
 * @emulated
 *    ob_get_headers (stub)
 *    preg_filter
 *    lcfirst
 *    class_alias
 *    header_remove
 *    parse_ini_string
 *    array_replace
 *    array_replace_recursive
 *    str_getcsv
 *    forward_static_call
 *    forward_static_call_array
 *    quoted_printable_encode
 *    E_DEPRECATED
 *    E_USER_DEPRECATED
 *
 * @missing
 *    get_called_class
 *    stream_context_get_params
 *    stream_context_set_default
 *    stream_supports_lock
 *    hash_copy
 *    date_create_from_format
 *    date_parse_from_format
 *    date_get_last_errors
 *    date_add
 *    date_sub
 *    date_diff
 *    date_timestamp_set
 *    date_timestamp_get
 *    timezone_location_get
 *    date_interval_create_from_date_string
 *    date_interval_format
 *
 */



/**
 * @since PHP 5.3.0
 */
if (!defined('E_DEPRECATED')) { define('E_DEPRECATED', 8192); }
if (!defined('E_USER_DEPRECATED')) { define('E_USER_DEPRECATED', 16384); }


/**
 * preg_replace() variant, which filters out any unmatched $subject.
 *
 */
if (!function_exists("preg_filter")) {
   function preg_filter($pattern, $replacement, $subject, $limit=-1, $count=NULL) {

      // just do the replacing first, and eventually filter later
      $r = preg_replace($pattern, $replacement, $subject, $limit, $count);

      // look at subject lines one-by-one, remove from result per index
      foreach ((array)$subject as $si=>$s) {
         $any = 0;
         foreach ((array)$pattern as $p) {
            $any = $any ||preg_match($p, $s);
         }
         // remove if NONE of the patterns matched
         if (!$any) {
            if (is_array($r)) {
               unset($r[$si]);  // del from result array
            }
            else {
               return NULL;  // subject was a str
            }
         }
      }

      return $r;    // is already string if $subject was too
   }
}



/**
 * Lowercase first character.
 *
 * @param string
 * @return string
 */
if (!function_exists("lcfirst")) {
   function lcfirst($str) {
      return strlen($str) ? strtolower($str[0]) . substr($str, 1) : "";
   }
}



/**
 * @stub  cannot be emulated, because output buffering functions
 *        already swallow up any sent http header
 * @since 5.3.?
 *
 * get all ob_ soaked headers(),
 *
 */
if (!function_exists("ob_get_headers")) {
   function ob_get_headers() {
      return (array)NULL;
   }
}



/**
 * @stub  Cannot be emulated correctly, but let's try.
 *
 */
if (!function_exists("header_remove")) {
   function header_remove($name="") {
      if (strlen($name) and ($name = preg_replace("/[^-_.\w\d]+/", "", $name))) header("$name: \t");
      // Apache1.3? removed duplettes, empty header overrides previous.
      // ONLY if case was identical to previous header() call. (Very uncertain for applications which need to resort to such code smell.)
   }
}



/**
 * WTF?
 * At least an explaning reference was available on the php.net manual.
 * Why the parameters are supposed to be optional is a mystery.
 *
 */
if (!function_exists("class_alias")) {
   function class_alias($original, $alias) {
      $abstract = "";
      if (class_exists("ReflectionClass")) {
         $oc = new ReflectionClass($original);
         $abstract = $oc->isAbstract() ? "abstract" : "";
      }
      eval("$abstract class $alias extends $original { /* identical subclass */ }");
      return get_parent_class($alias) == $original;
   }
}




/**
 * Hey, reimplementin is fun.
 * (Could have used a data: wrapper for parse_ini_file, but that wouldn't work for php<5.2, and the data:// (!) wrapper is flaky anyway.)
 *
 */
if (!function_exists("parse_ini_string")) {
   function parse_ini_string($ini, $sectioned=false, $raw=0) {
      $r = array();
      $map = array("true"=>1, "yes"=>1, "1"=>1, "null"=>"", "false"=>"", "no"=>"", "0"=>0);
      $section = "";
      foreach (explode("\n", $ini) as $line) {
         if (!strlen($line)) {
         }
         // handle [sections]
         elseif (($line[0] == "[") and preg_match("/\[([-_\w ]+)\]/", $line, $uu)) {
            $section = $uu[1];
         }
         elseif (/*deprecated*/($line[0] != "#") && ($line[0] != ";") && ($i = strpos($line, "="))) {
            // key=value split
            $n = trim(substr($line, 0, $i));
            $v = trim(substr($line, $i+1));
            // replace special values
            if (!$raw) {
               $v=trim($v, '"');   // should actually use regex, to handle key="..\n.." multiline values
               $v=trim($v, "'");
               if (isset($map[$v])) {
                  $v=$map[$v];
               }
            }
            // special array[]= keys allowed
            if ($i = strpos($n, "[")) {
               $r[$section][substr($n, 0, $i)][] = $v;
            }
            else {
               $r[$section][$n] = $v;
            }
         }
      }
      return $sectioned ? $r : call_user_func_array("array_merge", $r);
   }
}




/**
 * Inject values from supplemental arrays into $target, according to its keys.
 *
 * @param array  $targt
 * @param+ array $supplements
 * @return array
 */
if (!function_exists("array_replace")) {
   function array_replace(/* & (?) */$target/*, $from, $from2, ...*/) {
      $merge = func_get_args();
      array_shift($merge);
      foreach ($merge as $add) {
         foreach ($add as $i=>$v) {
            $target[$i] = $v;
         }
      }
      return $target;
   }
}


/**
 * Descends into sub-arrays when replacing values by key in $target array.
 *
 */
if (!function_exists("array_replace_recursive")) {
   function array_replace_recursive($target/*, $from1, $from2, ...*/) {
      $merge = func_get_args();
      array_shift($merge);

      // loop through all merge arrays
      foreach ($merge as $from) {
         foreach ($from as $i=>$v) {
            // just add (wether array or scalar) if key does not exist yet
            if (!isset($target[$i])) {
               $target[$i] = $v;
            }
            // dive in
            elseif (is_array($v) && is_array($target[$i])) {
               $target[$i] = array_replace_recursive($target[$i], $v);
            }
            // replace
            else {
               $target[$i] = $v;
            }
         }
      }
      return $target;
   }
}




/**
 * Breaks up a SINGLE LINE in CSV format.
 * abc,123,"text with spaces",xy,"\""
 *
 */
if (!function_exists("str_getcsv")) {
   function str_getcsv($line, $del=",", $q='"', $esc="\\", $rm_spaces="\s*") {
      $line = rtrim($line, "\r\n");
      preg_match_all("/\G $rm_spaces ([^$q$del]*?) $rm_spaces $del | $q(( [$esc$esc][$q]|[^$q]* )+)$q \s* $del /xms", $line.$del, $r);
      foreach ($r[1] as $i=>$v) {  // merge both captures
         if (empty($v) && strlen($r[2][$i])) {
            $r[1][$i] = str_replace("$esc$q", "$q", $r[2][$i]);  // remove escape character
         }            # use stripcslashes to support standard CSV \r \n escapes
      }
      return($r[1]);
   }
}



/**
 * @stub: Basically aliases for function calls; just throw an error if called from main() and not from within a class.
 * The real implementations would behave on late static binding, though.
 *
 */
if (!function_exists("forward_static_call")) {
   function forward_static_call_array($callback, $args=NULL) {
      return call_user_func_array($callback, $args);
   }
   function forward_static_call($callback /*, ... */) {
      $args = func_get_args();
      array_shift($args);
      return call_user_func_array($callback, $args);
   }
}




/**
 * Encodes special chars as =0D=0A patterns. Soft-break at 76 characters.
 *
 */
if (!function_exists("quoted_printable_encode")) {
   function quoted_printable_encode($str) {
      $str = preg_replace_callback("/([\\000-\\041=\\176-\\377])/", "quoted_printable_encode___hexquote", $str);
      $str = preg_replace("/(.{1,76})(?<=[^=][^=])/ims", "\$1=\r\n", $str); // QP-soft-break
      return $str;
   }
   function quoted_printable_encode___hexquote($match) {
      return "=" . strtoupper(dechex(ord($match[0])));
   }
}













/**
 *                                   ------------------------------ 5.2 ---
 * @group 5_2
 * @since 5.2
 *
 * Additions of PHP 5.2.0
 * - some listed here might have appeared earlier or in release candidates
 *
 * @emulated
 *    json_encode
 *    json_decode
 *    error_get_last
 *    preg_last_error
 *    lchown
 *    lchgrp
 *    E_RECOVERABLE_ERROR
 *    M_SQRTPI
 *    M_LNPI
 *    M_EULER
 *    M_SQRT3
 *    array_fill_keys  (@doc: 4.2 or 5.2 ?)
 *    array_diff_key   (@doc: 5.1 or 5.2 ?)
 *    array_diff_ukey
 *    array_product
 *    inet_ntop
 *    inet_pton
 *    array_intersect_key
 *    array_intersect_ukey
 *    mysql_set_charset
 *
 * @missing
 *    sys_getloadavg
 *    ftp_ssl_connect
 *    XmlReader
 *    XmlWriter
 *    PDO*
 *    pdo_drivers     (should be in ext/pdo)
 *
 * @unimplementable
 *    stream_*
 *
 */





/**
 * @since unknown
 */
if (!defined("E_RECOVERABLE_ERROR")) { define("E_RECOVERABLE_ERROR", 4096); }



/**
 * Converts PHP variable or array into a "JSON" (JavaScript value expression
 * or "object notation") string.
 *
 * @compat
 *    Output seems identical to PECL versions. "Only" 20x slower than PECL version.
 * @bugs
 *    Doesn't take care with unicode too much - leaves UTF-8 sequences alone.
 *
 * @param  $var mixed  PHP variable/array/object
 * @return string      transformed into JSON equivalent
 */
if (!defined("JSON_HEX_TAG")) {
   define("JSON_HEX_TAG", 1);
   define("JSON_HEX_AMP", 2);
   define("JSON_HEX_APOS", 4);
   define("JSON_HEX_QUOT", 8);
   define("JSON_FORCE_OBJECT", 16);
 }
if (!defined("JSON_NUMERIC_CHECK")) {
   define("JSON_NUMERIC_CHECK", 32);      // 5.3.3
 }
if (!defined("JSON_UNESCAPED_SLASHES")) {
   define("JSON_UNESCAPED_SLASHES", 64);  // 5.4.0
   define("JSON_PRETTY_PRINT", 128);      // 5.4.0
   define("JSON_UNESCAPED_UNICODE", 256); // 5.4.0
 }
if (!function_exists("json_encode")) {
   function json_encode($var, $options=0, $_indent="") {
      global ${'.json_last_error'};
      ${'.json_last_error'} = JSON_ERROR_NONE;
            
      #-- prepare JSON string
      $obj = ($options & JSON_FORCE_OBJECT);
      list($_space, $_tab, $_nl) = ($options & JSON_PRETTY_PRINT) ? array(" ", "    $_indent", "\n") : array("", "", "");
      $json = "$_indent";
      
      if ($options & JSON_NUMERIC_CHECK and is_string($var) and is_numeric($var)) {
          $var = (strpos($var, ".") || strpos($var, "e")) ? floatval($var) : intval($var);
      }
      
      #-- add array entries
      if (is_array($var) || ($obj=is_object($var))) {

         #-- check if array is associative
         if (!$obj) {
            $keys = array_keys((array)$var);
            $obj = !($keys == array_keys($keys));   // keys must be in 0,1,2,3, ordering, but PHP treats integers==strings otherwise
         }

         #-- concat individual entries
         $empty = 0; $json = "";
         foreach ((array)$var as $i=>$v) {
            $json .= ($empty++ ? ",$_nl" : "")    // comma separators
                   . $_tab . ($obj ? (json_encode((string)$i, $options & ~JSON_NUMERIC_CHECK, $_tab) . ":$_space") : "")   // assoc prefix
                   . (json_encode($v, $options, $_tab));    // value
         }

         #-- enclose into braces or brackets
         $json = $obj ? "{"."$_nl$json$_nl$_indent}" : "[$_nl$json$_nl$_indent]";
      }

      #-- strings need some care
      elseif (is_string($var)) {

         if (!preg_match("/^.+$/su", $var)) {
            trigger_error("json_encode: invalid UTF-8 encoding in string, cannot proceed.", E_USER_WARNING);
            $var = NULL;
         }
         $rewrite = array(
             "\\" => "\\\\",
             "\"" => "\\\"",
           "\010" => "\\b",
             "\f" => "\\f",
             "\n" => "\\n",
             "\r" => "\\r", 
             "\t" => "\\t",
             "/"  => $options & JSON_UNESCAPED_SLASHES ? "/" : "\\/",
             "<"  => $options & JSON_HEX_TAG  ? "\\u003C" : "<",
             ">"  => $options & JSON_HEX_TAG  ? "\\u003E" : ">",
             "'"  => $options & JSON_HEX_APOS ? "\\u0027" : "'",
             "\"" => $options & JSON_HEX_QUOT ? "\\u0022" : "\\\"",
             "&"  => $options & JSON_HEX_AMP  ? "\\u0026" : "&",
         );
         $var = strtr($var, $rewrite);
         //@COMPAT control chars should probably be stripped beforehand, not escaped as here
         if (function_exists("iconv") && ($options & JSON_UNESCAPED_UNICODE) == 0) {
            $var = preg_replace_callback("/[^\\x{0020}-\\x{007F}]/u", "json_encode___utf8_escape", $var);
         }
         $json = '"' . $var . '"';
      }

      #-- basic types
      elseif (is_bool($var)) {
         $json = $var ? "true" : "false";
      }
      elseif ($var === NULL) {
         $json = "null";
      }
      elseif (is_int($var)) {
         $json = "$var";
      }
      elseif (is_float($var)) {
         if (is_nan($var) || is_infinite($var)) {
            ${'.json_last_error'} = JSON_ERROR_INF_OR_NAN;
            return;
         }
         else {
            $json = "$var";
         }
      }

      #-- something went wrong
      else {
         trigger_error("json_encode: don't know what a '" .gettype($var). "' is.", E_USER_WARNING);
         ${'.json_last_error'} = JSON_ERROR_UNSUPPORTED_TYPE;
         return;
      }
      
      #-- done
      return($json);
   }
   function json_encode___utf8_escape($match) {
      return "\\u" . current(unpack("H*", iconv("UTF-8", "UCS-2BE", $match[0])));
   }
}

/**
 * Parses a JSON (JavaScript value expression) string into a PHP variable
 * (array or object).
 *
 * @compat
 *    Behaves similar to PECL version, but is less quiet on errors.
 *    Now even decodes unicode \uXXXX string escapes into UTF-8.
 *    "Only" 27 times slower than native function.
 * @bugs
 *    Might parse some misformed representations, when other implementations
 *    would scream error or explode.
 * @code
 *    This is state machine spaghetti code. Needs the extranous parameters to
 *    process subarrays, etc. When it recursively calls itself, $n is the
 *    current position, and $waitfor a string with possible end-tokens.
 *
 * @param   $json string   JSON encoded values
 * @param   $assoc bool    pack data into php array/hashes instead of objects
 * @return  mixed          parsed into PHP variable/array/object
 */
if (!function_exists("json_decode")) {

   defined("JSON_OBJECT_AS_ARRAY") or define("JSON_OBJECT_AS_ARRAY", 1);     // undocumented
   defined("JSON_BIGINT_AS_STRING") or define("JSON_BIGINT_AS_STRING", 2);    // 5.4.0
   defined("JSON_PARSE_JAVASCRIPT") or define("JSON_PARSE_JAVASCRIPT", 4);    // unquoted object keys, and single quotes ' strings identical to double quoted, more relaxed parsing

   function json_decode($json, $assoc=FALSE, $limit=512, $options=0, /*emu_args*/$n=0,$state=0,$waitfor=0) {
      global ${'.json_last_error'};
      ${'.json_last_error'} = JSON_ERROR_NONE;

      #-- maximum nesting depth for decoding
      if ($limit < 0) {
          ${'.json_last_error'} = JSON_ERROR_DEPTH;
          return; // fall through
      }

      #-- result var
      $val = NULL;
      
      // shortcut state for parsing errors
      $FAILURE = array(
          NULL,   // result var
          1<<31   // tokenizer position
      );
      
      // transliterations from JSON to PHP values
      static $lang_eq = array("true" => TRUE, "false" => FALSE, "null" => NULL);
      static $str_eq = array("n"=>"\012", "r"=>"\015", "\\"=>"\\", '"'=>'"', "f"=>"\f", "b"=>"\010", "t"=>"\t", "/"=>"/");
      
      #-- strip UTF-8 BOM (the native version doesn't do this, but .. should)
      while (strncmp($json, "\xEF\xBB\xBF", 3) == 0) {
          trigger_error("UTF-8 BOM prefaces JSON, that's invalid for PHPs native json_decode", E_USER_ERROR);
          $json = substr($json, 3);
      }

      #-- flat char-wise parsing
      for (/*$n=0,*/ $len = strlen($json); $n<$len; /*$n++*/) {
         $c = $json[$n];

         #-= in-string
         if ($state==='"' or $state==="'") {

            if ($c == '\\') {
               $c = $json[++$n];
               
               // simple C escapes
               if (isset($str_eq[$c])) {
                  $val .= $str_eq[$c];
               }

               // here we transform \uXXXX Unicode (always 4 nibbles) references to UTF-8
               elseif ($c == "u") {
                  // read just 16bit (therefore value can't be negative)
                  $hex = hexdec( substr($json, $n+1, 4) );
                  $n += 4;
                  // Unicode ranges
                  if ($hex < 0x80) {    // plain ASCII character
                     $val .= chr($hex);
                  }
                  elseif ($hex < 0x800) {   // 110xxxxx 10xxxxxx 
                     $val .= chr(0xC0 + $hex>>6) . chr(0x80 + $hex&63);
                  }
                  elseif ($hex <= 0xFFFF) { // 1110xxxx 10xxxxxx 10xxxxxx 
                     $val .= chr(0xE0 + $hex>>12) . chr(0x80 + ($hex>>6)&63) . chr(0x80 + $hex&63);
                  }
                  // other ranges, like 0x1FFFFF=0xF0, 0x3FFFFFF=0xF8 and 0x7FFFFFFF=0xFC do not apply
               }

               // for JS (not JSON) the extraneous backslash just gets omitted
               elseif ($options & JSON_PARSE_JAVASCRIPT) {
                  if (is_numeric($c) and preg_match("/[0-3][0-7][0-7]|[0-7]{1,2}/", substr($json, $n), $m)) {
                     $val .= chr(octdec($m[0]));
                     $n += strlen($m[0]) - 1;
                  }
                  else {
                     $val .= $c;
                  }
               }
               
               // redundant backslashes disallowed in JSON
               else {
                  $val .= "\\$c";
                  ${'.json_last_error'} = JSON_ERROR_CTRL_CHAR; // not quite, but
                  trigger_error("Invalid backslash escape for JSON \\$c", E_USER_WARNING);
                  return $FAILURE;
               }
            }

            // end of string
            elseif ($c == $state) {
               $state = 0;
            }

            //@COMPAT: specialchars check - but native json doesn't do it?
            #elseif (ord($c) < 32) && !in_array($c, $str_eq)) {
            #   ${'.json_last_error'} = JSON_ERROR_CTRL_CHAR;
            #}
            
            // a single character was found
            else/*if (ord($c) >= 32)*/ {
               $val .= $c;
            }
         }

         #-> end of sub-call (array/object)
         elseif ($waitfor && (strpos($waitfor, $c) !== false)) {
            return array($val, $n);  // return current value and state
         }
         
         #-= in-array
         elseif ($state===']') {
            list($v, $n) = json_decode($json, $assoc, $limit, $options, $n, 0, ",]");
            $val[] = $v;
            if ($json[$n] == "]") { return array($val, $n); }
         }

         #-= in-object
         elseif ($state==='}') {
            // quick regex parsing cheat for unquoted JS object keys
            if ($options & JSON_PARSE_JAVASCRIPT and $c != '"' and preg_match("/^\s*(?!\d)(\w\pL*)\s*/u", substr($json, $n), $m)) {
                $i = $m[1];
                $n = $n + strlen($m[0]);
            }
            else {
                // this allowed non-string indicies
                list($i, $n) = json_decode($json, $assoc, $limit, $options, $n, 0, ":");
            }
            list($v, $n) = json_decode($json, $assoc, $limit, $options, $n+1, 0, ",}");
            $val[$i] = $v;
            if ($json[$n] == "}") { return array($val, $n); }
         }

         #-- looking for next item (0)
         else {
         
            #-> whitespace
            if (preg_match("/\s/", $c)) {
               // skip
            }

            #-> string begin
            elseif ($c == '"') {
               $state = $c;
            }

            #-> object
            elseif ($c == "{") {
               list($val, $n) = json_decode($json, $assoc, $limit-1, $options, $n+1, '}', "}");
               
               if ($val && $n) {
                  $val = $assoc ? (array)$val : (object)$val;
               }
            }

            #-> array
            elseif ($c == "[") {
               list($val, $n) = json_decode($json, $assoc, $limit-1, $options, $n+1, ']', "]");
            }

            #-> numbers
            elseif (preg_match("#^(-?\d+(?:\.\d+)?)(?:[eE]([-+]?\d+))?#", substr($json, $n), $uu)) {
               $val = $uu[1];
               $n += strlen($uu[0]) - 1;
               if (strpos($val, ".")) {  // float
                  $val = floatval($val);
               }
               elseif ($val[0] == "0") {  // oct
                  $val = octdec($val);
               }
               else {
                  $toobig = strval(intval($val)) !== strval($val);
                  if ($toobig and !isset($uu[2]) and ($options & JSON_BIGINT_AS_STRING)) {
                      $val = $val;  // keep lengthy numbers as string
                  }
                  elseif ($toobig or isset($uu[2])) {  // must become float anyway
                      $val = floatval($val);
                  }
                  else {  // int
                      $val = intval($val);
                  }
               }
               // exponent?
               if (isset($uu[2])) {
                  $val *= pow(10, (int)$uu[2]);
               }
            }

            #-> boolean or null
            elseif (preg_match("#^(true|false|null)\b#", substr($json, $n), $uu)) {
               $val = $lang_eq[$uu[1]];
               $n += strlen($uu[1]) - 1;
            }

            #-> JS-string begin
            elseif ($options & JSON_PARSE_JAVASCRIPT and $c == "'") {
               $state = $c;
            }

            #-> comment
            elseif ($options & JSON_PARSE_JAVASCRIPT and ($c == "/") and ($json[$n+1]=="*")) {
               // just find end, skip over
               ($n = strpos($json, "*/", $n+1)) or ($n = strlen($json));
            }

            #-- parsing error
            else {
               // PHPs native json_decode() breaks here usually and QUIETLY
               trigger_error("json_decode: error parsing '$c' at position $n", E_USER_WARNING);
               ${'.json_last_error'} = JSON_ERROR_SYNTAX;
               return $waitfor ? $FAILURE : NULL;
            }

         }//state
         
         #-- next char
         if ($n === NULL) { ${'.json_last_error'} = JSON_ERROR_STATE_MISMATCH; return NULL; }   // ooops, seems we have two failure modes
         $n++;
      }//for

      #-- final result
      return ($val);
   }
}


/**
 * @stub
 *
 * Should return last JSON decoding error.
 *
 */
if (!defined("JSON_ERROR_NONE")) {
   define("JSON_ERROR_NONE", 0);
   define("JSON_ERROR_DEPTH", 1);
   define("JSON_ERROR_STATE_MISMATCH", 2);
   define("JSON_ERROR_CTRL_CHAR", 3);
   define("JSON_ERROR_SYNTAX", 4);
   define("JSON_ERROR_UTF8", 5);
   define("JSON_ERROR_RECURSION", 6);
   define("JSON_ERROR_INF_OR_NAN", 7);
   define("JSON_ERROR_UNSUPPORTED_TYPE", 7);
 }
if (!function_exists("json_last_error")) {
   function json_last_error() {
       global ${'.json_last_error'}; 
       return ${'.json_last_error'}; // gives a notice if json_decode was never invoked before (no status constant for that)
   }
}



/**
 * @stub
 *
 * Should return last PCRE error.
 *
 */
if (!function_exists("preg_last_error")) {
   if (!defined("PREG_NO_ERROR")) { define("PREG_NO_ERROR", 0); }
   if (!defined("PREG_INTERNAL_ERROR")) { define("PREG_INTERNAL_ERROR", 1); }
   if (!defined("PREG_BACKTRACK_LIMIT_ERROR")) { define("PREG_BACKTRACK_LIMIT_ERROR", 2); }
   if (!defined("PREG_RECURSION_LIMIT_ERROR")) { define("PREG_RECURSION_LIMIT_ERROR", 3); }
   if (!defined("PREG_BAD_UTF8_ERROR")) { define("PREG_BAD_UTF8_ERROR", 4); }
   function preg_last_error() {
      return PREG_NO_ERROR;
   }
}




/**
 * returns path of the system directory for temporary files
 *
 * @since 5.2.1
 */
if (!function_exists("sys_get_temp_dir")) {
   function sys_get_temp_dir() {
      # check possible alternatives
      ($temp = ini_get("temp_dir"))
      or
      ($temp = @$_ENV["TMPDIR"])
      or
      ($temp = @$_ENV["TEMP"])
      or
      ($temp = @$_ENV["TMP"])
      or
      ($temp = "/tmp");
      # fin
      return($temp);
   }
}



/**
 * @stub
 *
 * Should return associative array with last error message.
 *
 */
if (!function_exists("error_get_last")) {
   function error_get_last() {
      return array(
         "type" => 0,
         "message" => $GLOBALS["php_errormsg"],
         "file" => "unknonw",
         "line" => 0,
      );
   }
}




/**
 * @flag quirky, exec, realmode
 *
 * Change owner of a symlink filename.
 *
 */
if (!function_exists("lchown")) {
   function lchown($fn, $user) {
      if (PHP_OS != "Linux") {
         return false;
      }
      $user = escapeshellcmd($user);
      $fn = escapeshellcmd($fn);
      exec("chown -h '$user' '$fn'", $uu, $state);
      return($state);
   }
}



/**
 * @flag quirky, exec, realmode
 *
 * Change group of a symlink filename.
 *
 */
if (!function_exists("lchgrp")) {
   function lchgrp($fn, $group) {
      return lchown($fn, ":$group");
   }
}



/**
 * @doc: Got this function new in PHP 5.2, but documentation says 4.2 ???
 * 
 * array_fill() with given $keys
 *
 */
if (!function_exists("array_fill_keys")) {
   function array_fill_keys($keys, $value) {
      return array_combine($keys, array_fill(0, count($keys), $value));
   }
}



/**
 * @doc: php manual says 5.1, but function appeared with 5.2
 *
 * Returns array entries, whose keys are not in any of the comparison arrays.
 *
 */
if (!function_exists("array_diff_key")) {
   function array_diff_key($base /*...*/) {
      $other = func_get_args();
      array_shift($other);

      $cmp = call_user_func_array("array_merge", array_map("array_keys", $other));

      foreach ($cmp as $key) {
            $key = (string) $key;
            if (array_key_exists($key, $base)) {
               // cannot compare if $key is actually a string in $base
               unset($base[$key]);
            }
      }
      return ($base);
   }
}




/**
 * @doc: php manual says 5.1, but function appeared with 5.2
 *
 * Uses callback function to compare array keys.
 * Callback returns -1, 0, +1, and then some keys are filtered???
 * Let's assume ==0 is meant for no difference --> and no difference => filter out
 *
 */
if (!function_exists("array_diff_ukey")) {
   function array_diff_ukey($base, $other_arrays/*...*/, $callback) {
      $other = func_get_args();
      array_shift($other);
      $callback = array_pop($other);
      
      $cmp = call_user_func_array("array_merge", array_map("array_keys", $other));

      foreach ($base as $key=>$value) {
         // compare against each key from $other arrays
         foreach ($cmp as $k) {
            if ($callback($key, $k) === 0) {
               unset($base[$key]);
            }
         }
      }
      return $base;      
   }
}



/**
 * @doc: 5.1 vs 5.2
 *
 * Keeps only array-entries, if key exists also in comparison arrays
 *
 */
if (!function_exists("array_intersect_key")) {
   function array_intersect_key($base /*...*/) {
      $all_arrays = array_map("array_keys", func_get_args());
      $keep = call_user_func_array("array_intersect", $all_arrays);
      
      $r = array();
      foreach ($keep as $k) {
         $r[$k] = $base[$k];
      }
      return ($r);
   }
}



/**
 * @doc: 5.1 vs 5.2
 *
 * array_uintersect on keys
 *
 */
if (!function_exists("array_intersect_ukey")) {
   function array_intersect_ukey(/*...*/) {
      $args = func_get_args();
      $base = $args[0];
      $callback = array_pop($other);

      $keys = array_map("array_values", $args);
      $intersect = call_user_func_array("array_uintersect", array_merge($keys, array($callback)));
      
      $r = array();
      foreach ($intersect as $key) {
         $r[$key] = $base[$key];
      }
      return $r;
   }
}







/**
 * Hmmm.
 *
 */
if (!function_exists("array_product")) {
   function array_product($multiply_us) {
      $r = count($multiply_us) ? 1 : NULL;
      foreach ($multiply_us as $m) {
         $r = $r * $m;
      }
      return $r;
   }
}



/**
 * Converts chr/bin/string-representation to human-readable IP text.
 *
 */
if (!function_exists("inet_ntop")) {
   function inet_ntop($bin) {
      if (strlen($bin) == 4) {   // IPv4
         return implode(".", array_map("ord", str_split($bin, 1)));
      }
      elseif (strlen($bin) == 16) {  // IPv6
         return preg_replace("/:?(0000:)+/", "::", implode(":", str_split(bin2hex($bin), 4)));
      }
      elseif (strlen($bin) == 6) {  // MAC
         return implode(":", str_split(bin2hex($bin), 2));
      }
   }
}


/**
 * Compact IPv4 1.2.3.4 or IPv6 ::FFFF:0001 addresses into binary string.
 *
 */
if (!function_exists("inet_pton")) {
   function inet_pton($str) {
      if (strpos($str, ".")) {  // IPv4
         return array_map("chr", explode(".", $str));
      }
      elseif (strstr($str, ":")) { // IPv6
         $str = str_replace("::", str_repeat(":", 2 + 7 - substr_count($str, ":")), $str);   // padding "::" can appear anywhere inside, replaces 7-x other :0000 colons and zeros
         $str = implode(array_map("inet_pton___ipv6_pad", explode(":", $str)));
         return pack("H32", $str);
      }
   }
   function inet_pton___ipv6_pad($s) {
      return str_pad($s, 4, "0", STR_PAD_LEFT);
   }
}


/**
 * @since 5.2.3
 * SET NAMES $charset
 *
 */
if (!function_exists("mysql_set_charset")) {
   function mysql_set_charset($charset, $link=NULL) {
      return mysqli_query("SET NAMES '$charset'", $link);
   }
}




/**
 *                                   ------------------------------ 5.1 ---
 * @group 5_1
 * @since 5.1
 *
 * Additions in PHP 5.1
 * - most functions here appeared in -rc1 already
 * - and were backported to 4.4 series?
 *
 * @emulated
 *    hash_hmac
 *    property_exists
 *    time_sleep_until
 *    fputcsv
 *    strptime
 *    ENT_COMPAT
 *    ENT_QUOTES
 *    ENT_NOQUOTES
 *    htmlspecialchars_decode
 *    PHP_INT_SIZE
 *    PHP_INT_MAX
 *    M_SQRTPI
 *    M_LNPI
 *    M_EULER
 *    M_SQRT3
 *
 * @missing
 *    strptime
 *
 * @unimplementable
 *    ...
 *
 */



/**
 * HMAC as per rfc2104,
 * only works with PHP-available "md5" and "sha1" algorithm backends
 *
 */
if (!function_exists("hash_hmac")) {
   function hash_hmac($H, $data, $key, $raw=0) {

       # algorithm parameters
       static $bitsize = array("sha1"=>160, "md5"=>128, "sha256"=>256, "sha512"=>512, "sha384"=>384, "sha224"=>224, "ripemd"=>160);
       $B = 64;

       # bring key to block size 64, hash it first if longer
       if (strlen($key) > $B) {
           $key = $H($key, 1);
       }
       $key .= str_repeat("\0", $B - strlen($key));

       # padding, XOR with key
       $inner_pad = "";
       $outer_pad = "";
       for ($i=0; $i<$B; $i++) {
           $inner_pad .= chr(0x36 ^ ord($key[$i]));
           $outer_pad .= chr(0x5C ^ ord($key[$i]));
       }

       # apply hash
       $data = $H($outer_pad . $H($inner_pad . $data, 1), 1);
       
       # bin2hex
       return $raw ? $data : bin2hex($data);
   }
}



/**
 * Constants for future 64-bit integer support.
 *
 */
if (!defined("PHP_INT_SIZE")) { define("PHP_INT_SIZE", 4); }
if (!defined("PHP_INT_MAX")) { define("PHP_INT_MAX", 2147483647); }



/**
 * @flag bugfix
 * @see #33895
 *
 * Missing constants in 5.1, originally appeared in 4.0.
 */
if (!defined("M_SQRTPI")) { define("M_SQRTPI", 1.7724538509055); }
if (!defined("M_LNPI")) { define("M_LNPI", 1.1447298858494); }
if (!defined("M_EULER")) { define("M_EULER", 0.57721566490153); }
if (!defined("M_SQRT3")) { define("M_SQRT3", 1.7320508075689); }




/**
 * removes entities &lt; &gt; &amp; and eventually &quot; from HTML string
 *
 */
if (!function_exists("htmlspecialchars_decode")) {
   if (!defined("ENT_COMPAT")) { define("ENT_COMPAT", 2); }
   if (!defined("ENT_QUOTES")) { define("ENT_QUOTES", 3); }
   if (!defined("ENT_NOQUOTES")) { define("ENT_NOQUOTES", 0); }
   function htmlspecialchars_decode($string, $quotes=2) {
      $d = $quotes & ENT_COMPAT;
      $s = $quotes & ENT_QUOTES;
      return str_replace(
         array("&lt;", "&gt;", ($s ? "&quot;" : "&.-;"), ($d ? "&#039;" : "&.-;"), "&amp;"),
         array("<",    ">",    "'",                      "\"",                     "&"),
         $string
      );
   }
}



/**
 * @flag needs5
 *
 * Checks for existence of object property, should return TRUE even for NULL values.
 *
 * @compat
 *    no test for edge cases
 */
if (!function_exists("property_exists")) {
   function property_exists($obj, $propname) {
      if (is_object($obj)) {
         $props = array_keys(get_object_vars($obj));
      }
      elseif (class_exists($obj)) {
         $props = array_keys(get_class_vars($obj));
      }
      return !empty($props) and in_array($propname, $props);
   }
}



/**
 * halt execution, until given timestamp
 *
 */
if (!function_exists("time_sleep_until")) {
   function time_sleep_until($t) {
      $delay = $t - time();
      if ($delay < 0) {
         trigger_error("time_sleep_until: timestamp in the past", E_USER_WARNING);
         return false;
      }
      else {
         sleep((int)$delay);
         #usleep(($delay - floor($delay)) * 1000000);
         return true;
      }
   }
}



/**
 * @untested
 *
 * Writes an array as CSV text line into opened filehandle.
 *
 */
if (!function_exists("fputcsv")) {
   function fputcsv($fp, $fields, $delim=",", $encl='"') {
      $line = "";
      foreach ((array)$fields as $str) {
         $line .= ($line ? $delim : "")
                . $encl
                . str_replace(array('\\', $encl), array('\\\\'. '\\'.$encl), $str)
                . $encl;
      }
      fwrite($fp, $line."\n");
   }
}



/**
 * @flag basic
 * @untested
 *
 * @compat
 *    only implements a few basic regular expression lookups
 *    no idea how to handle all of it
 */
if (!function_exists("strptime")) {
   function strptime($str, $format) {
      static $expand = array(
         "%D" => "%m/%d/%y",
         "%T" => "%H:%M:%S",
      );
      static $map_r = array(
          "%S"=>"tm_sec",
          "%M"=>"tm_min",
          "%H"=>"tm_hour",
          "%d"=>"tm_mday",
          "%m"=>"tm_mon",
          "%Y"=>"tm_year",
          "%y"=>"tm_year",
          "%W"=>"tm_wday",
          "%D"=>"tm_yday",
          "%u"=>"unparsed",
      );
      static $names = array(
         "Jan" => 1, "Feb" => 2, "Mar" => 3, "Apr" => 4, "May" => 5, "Jun" => 6,
         "Jul" => 7, "Aug" => 8, "Sep" => 9, "Oct" => 10, "Nov" => 11, "Dec" => 12,
         "Sun" => 0, "Mon" => 1, "Tue" => 2, "Wed" => 3, "Thu" => 4, "Fri" => 5, "Sat" => 6,
      );

      #-- transform $format into extraction regex
      $format = str_replace(array_keys($expand), array_values($expand), $format);
      $preg = preg_replace("/(%\w)/", "(\w+)", preg_quote($format));

      #-- record the positions of all STRFCMD-placeholders
      preg_match_all("/(%\w)/", $format, $positions);
      $positions = $positions[1];
      
      #-- get individual values
      if (preg_match("#$preg#", "$str", $extracted)) {

         #-- get values
         foreach ($positions as $pos=>$strfc) {
            $v = $extracted[$pos + 1];

            #-- add
            if ($n = $map_r[$strfc]) {
               $vals[$n] = ($v > 0) ? (int)$v : $v;
            }
            else {
               $vals["unparsed"] .= $v . " ";
            }
         }
         
         #-- fixup some entries
         $vals["tm_wday"] = $names[ substr($vals["tm_wday"], 0, 3) ];
         if ($vals["tm_year"] >= 1900) {
            $tm_year -= 1900;
         }
         elseif ($vals["tm_year"] > 0) {
            $vals["tm_year"] += 100;
         }
         if ($vals["tm_mon"]) {
            $vals["tm_mon"] -= 1;
         }
         else {
            $vals["tm_mon"] = $names[ substr($vals["tm_mon"], 0, 3) ] - 1;
         }
         
         #-- calculate wday
         // ... (mktime)
      }
      return isset($vals) ? $vals : false;
   }
}






/**
 *                                   ----------------------------- 5.0 ---
 * @group 5_0
 * @since 5.0
 * @moved ext/php50.php
 *
 *
 * Now even the 5.1 compatibility code is seriously redundant in 2015,
 * so the 4.1 till 5.0 have been extracted out into ext/php50.php
 *
 */


?>