File indexing completed on 2024-12-22 05:36:48

0001 <?php
0002 /**
0003  * Zend Framework
0004  *
0005  * LICENSE
0006  *
0007  * This source file is subject to the new BSD license that is bundled
0008  * with this package in the file LICENSE.txt.
0009  * It is also available through the world-wide-web at this URL:
0010  * http://framework.zend.com/license/new-bsd
0011  * If you did not receive a copy of the license and are unable to
0012  * obtain it through the world-wide-web, please send an email
0013  * to license@zend.com so we can send you a copy immediately.
0014  *
0015  * @category   Zend
0016  * @package    Zend_Json
0017  * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
0018  * @license    http://framework.zend.com/license/new-bsd     New BSD License
0019  * @version    $Id$
0020  */
0021 
0022 /**
0023  * Encode PHP constructs to JSON
0024  *
0025  * @category   Zend
0026  * @package    Zend_Json
0027  * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
0028  * @license    http://framework.zend.com/license/new-bsd     New BSD License
0029  */
0030 class Zend_Json_Encoder
0031 {
0032     /**
0033      * Whether or not to check for possible cycling
0034      *
0035      * @var boolean
0036      */
0037     protected $_cycleCheck;
0038 
0039     /**
0040      * Additional options used during encoding
0041      *
0042      * @var array
0043      */
0044     protected $_options = array();
0045 
0046     /**
0047      * Array of visited objects; used to prevent cycling.
0048      *
0049      * @var array
0050      */
0051     protected $_visited = array();
0052 
0053     /**
0054      * Constructor
0055      *
0056      * @param boolean $cycleCheck Whether or not to check for recursion when encoding
0057      * @param array $options Additional options used during encoding
0058      * @return void
0059      */
0060     protected function __construct($cycleCheck = false, $options = array())
0061     {
0062         $this->_cycleCheck = $cycleCheck;
0063         $this->_options = $options;
0064     }
0065 
0066     /**
0067      * Use the JSON encoding scheme for the value specified
0068      *
0069      * @param mixed $value The value to be encoded
0070      * @param boolean $cycleCheck Whether or not to check for possible object recursion when encoding
0071      * @param array $options Additional options used during encoding
0072      * @return string  The encoded value
0073      */
0074     public static function encode($value, $cycleCheck = false, $options = array())
0075     {
0076         $encoder = new self(($cycleCheck) ? true : false, $options);
0077         return $encoder->_encodeValue($value);
0078     }
0079 
0080     /**
0081      * Recursive driver which determines the type of value to be encoded
0082      * and then dispatches to the appropriate method. $values are either
0083      *    - objects (returns from {@link _encodeObject()})
0084      *    - arrays (returns from {@link _encodeArray()})
0085      *    - basic datums (e.g. numbers or strings) (returns from {@link _encodeDatum()})
0086      *
0087      * @param mixed $value The value to be encoded
0088      * @return string Encoded value
0089      */
0090     protected function _encodeValue(&$value)
0091     {
0092         if (is_object($value)) {
0093             return $this->_encodeObject($value);
0094         } else if (is_array($value)) {
0095             return $this->_encodeArray($value);
0096         }
0097 
0098         return $this->_encodeDatum($value);
0099     }
0100 
0101 
0102 
0103     /**
0104      * Encode an object to JSON by encoding each of the public properties
0105      *
0106      * A special property is added to the JSON object called '__className'
0107      * that contains the name of the class of $value. This is used to decode
0108      * the object on the client into a specific class.
0109      *
0110      * @param object $value
0111      * @return string
0112      * @throws Zend_Json_Exception If recursive checks are enabled and the object has been serialized previously
0113      */
0114     protected function _encodeObject(&$value)
0115     {
0116         if ($this->_cycleCheck) {
0117             if ($this->_wasVisited($value)) {
0118 
0119                 if (isset($this->_options['silenceCyclicalExceptions'])
0120                     && $this->_options['silenceCyclicalExceptions']===true) {
0121 
0122                     return '"* RECURSION (' . get_class($value) . ') *"';
0123 
0124                 } else {
0125                     // require_once 'Zend/Json/Exception.php';
0126                     throw new Zend_Json_Exception(
0127                         'Cycles not supported in JSON encoding, cycle introduced by '
0128                         . 'class "' . get_class($value) . '"'
0129                     );
0130                 }
0131             }
0132 
0133             $this->_visited[] = $value;
0134         }
0135 
0136         $props = '';
0137         if (method_exists($value, 'toJson')) {
0138             $props =',' . preg_replace("/^\{(.*)\}$/","\\1",$value->toJson());
0139         } else {
0140             if ($value instanceof IteratorAggregate) {
0141                 $propCollection = $value->getIterator();
0142             } elseif ($value instanceof Iterator) {
0143                 $propCollection = $value;
0144             } else {
0145                 $propCollection = get_object_vars($value);
0146             }
0147 
0148             foreach ($propCollection as $name => $propValue) {
0149                 if (isset($propValue)) {
0150                     $props .= ','
0151                             . $this->_encodeString($name)
0152                             . ':'
0153                             . $this->_encodeValue($propValue);
0154                 }
0155             }
0156         }
0157         $className = get_class($value);
0158         return '{"__className":' . $this->_encodeString($className)
0159                 . $props . '}';
0160     }
0161 
0162 
0163     /**
0164      * Determine if an object has been serialized already
0165      *
0166      * @param mixed $value
0167      * @return boolean
0168      */
0169     protected function _wasVisited(&$value)
0170     {
0171         if (in_array($value, $this->_visited, true)) {
0172             return true;
0173         }
0174 
0175         return false;
0176     }
0177 
0178 
0179     /**
0180      * JSON encode an array value
0181      *
0182      * Recursively encodes each value of an array and returns a JSON encoded
0183      * array string.
0184      *
0185      * Arrays are defined as integer-indexed arrays starting at index 0, where
0186      * the last index is (count($array) -1); any deviation from that is
0187      * considered an associative array, and will be encoded as such.
0188      *
0189      * @param array& $array
0190      * @return string
0191      */
0192     protected function _encodeArray(&$array)
0193     {
0194         $tmpArray = array();
0195 
0196         // Check for associative array
0197         if (!empty($array) && (array_keys($array) !== range(0, count($array) - 1))) {
0198             // Associative array
0199             $result = '{';
0200             foreach ($array as $key => $value) {
0201                 $key = (string) $key;
0202                 $tmpArray[] = $this->_encodeString($key)
0203                             . ':'
0204                             . $this->_encodeValue($value);
0205             }
0206             $result .= implode(',', $tmpArray);
0207             $result .= '}';
0208         } else {
0209             // Indexed array
0210             $result = '[';
0211             $length = count($array);
0212             for ($i = 0; $i < $length; $i++) {
0213                 $tmpArray[] = $this->_encodeValue($array[$i]);
0214             }
0215             $result .= implode(',', $tmpArray);
0216             $result .= ']';
0217         }
0218 
0219         return $result;
0220     }
0221 
0222 
0223     /**
0224      * JSON encode a basic data type (string, number, boolean, null)
0225      *
0226      * If value type is not a string, number, boolean, or null, the string
0227      * 'null' is returned.
0228      *
0229      * @param mixed& $value
0230      * @return string
0231      */
0232     protected function _encodeDatum(&$value)
0233     {
0234         $result = 'null';
0235 
0236         if (is_int($value) || is_float($value)) {
0237             $result = (string) $value;
0238             $result = str_replace(",", ".", $result);
0239         } elseif (is_string($value)) {
0240             $result = $this->_encodeString($value);
0241         } elseif (is_bool($value)) {
0242             $result = $value ? 'true' : 'false';
0243         }
0244 
0245         return $result;
0246     }
0247 
0248 
0249     /**
0250      * JSON encode a string value by escaping characters as necessary
0251      *
0252      * @param string& $value
0253      * @return string
0254      */
0255     protected function _encodeString(&$string)
0256     {
0257         // Escape these characters with a backslash:
0258         // " \ / \n \r \t \b \f
0259         $search  = array('\\', "\n", "\t", "\r", "\b", "\f", '"', '/');
0260         $replace = array('\\\\', '\\n', '\\t', '\\r', '\\b', '\\f', '\"', '\\/');
0261         $string  = str_replace($search, $replace, $string);
0262 
0263         // Escape certain ASCII characters:
0264         // 0x08 => \b
0265         // 0x0c => \f
0266         $string = str_replace(array(chr(0x08), chr(0x0C)), array('\b', '\f'), $string);
0267         $string = self::encodeUnicodeString($string);
0268 
0269         return '"' . $string . '"';
0270     }
0271 
0272 
0273     /**
0274      * Encode the constants associated with the ReflectionClass
0275      * parameter. The encoding format is based on the class2 format
0276      *
0277      * @param ReflectionClass $cls
0278      * @return string Encoded constant block in class2 format
0279      */
0280     private static function _encodeConstants(ReflectionClass $cls)
0281     {
0282         $result    = "constants : {";
0283         $constants = $cls->getConstants();
0284 
0285         $tmpArray = array();
0286         if (!empty($constants)) {
0287             foreach ($constants as $key => $value) {
0288                 $tmpArray[] = "$key: " . self::encode($value);
0289             }
0290 
0291             $result .= implode(', ', $tmpArray);
0292         }
0293 
0294         return $result . "}";
0295     }
0296 
0297 
0298     /**
0299      * Encode the public methods of the ReflectionClass in the
0300      * class2 format
0301      *
0302      * @param ReflectionClass $cls
0303      * @return string Encoded method fragment
0304      *
0305      */
0306     private static function _encodeMethods(ReflectionClass $cls)
0307     {
0308         $methods = $cls->getMethods();
0309         $result = 'methods:{';
0310 
0311         $started = false;
0312         foreach ($methods as $method) {
0313             if (! $method->isPublic() || !$method->isUserDefined()) {
0314                 continue;
0315             }
0316 
0317             if ($started) {
0318                 $result .= ',';
0319             }
0320             $started = true;
0321 
0322             $result .= '' . $method->getName(). ':function(';
0323 
0324             if ('__construct' != $method->getName()) {
0325                 $parameters  = $method->getParameters();
0326                 $paramCount  = count($parameters);
0327                 $argsStarted = false;
0328 
0329                 $argNames = "var argNames=[";
0330                 foreach ($parameters as $param) {
0331                     if ($argsStarted) {
0332                         $result .= ',';
0333                     }
0334 
0335                     $result .= $param->getName();
0336 
0337                     if ($argsStarted) {
0338                         $argNames .= ',';
0339                     }
0340 
0341                     $argNames .= '"' . $param->getName() . '"';
0342 
0343                     $argsStarted = true;
0344                 }
0345                 $argNames .= "];";
0346 
0347                 $result .= "){"
0348                          . $argNames
0349                          . 'var result = ZAjaxEngine.invokeRemoteMethod('
0350                          . "this, '" . $method->getName()
0351                          . "',argNames,arguments);"
0352                          . 'return(result);}';
0353             } else {
0354                 $result .= "){}";
0355             }
0356         }
0357 
0358         return $result . "}";
0359     }
0360 
0361 
0362     /**
0363      * Encode the public properties of the ReflectionClass in the class2
0364      * format.
0365      *
0366      * @param ReflectionClass $cls
0367      * @return string Encode properties list
0368      *
0369      */
0370     private static function _encodeVariables(ReflectionClass $cls)
0371     {
0372         $properties = $cls->getProperties();
0373         $propValues = get_class_vars($cls->getName());
0374         $result = "variables:{";
0375         $cnt = 0;
0376 
0377         $tmpArray = array();
0378         foreach ($properties as $prop) {
0379             if (! $prop->isPublic()) {
0380                 continue;
0381             }
0382 
0383             $tmpArray[] = $prop->getName()
0384                         . ':'
0385                         . self::encode($propValues[$prop->getName()]);
0386         }
0387         $result .= implode(',', $tmpArray);
0388 
0389         return $result . "}";
0390     }
0391 
0392     /**
0393      * Encodes the given $className into the class2 model of encoding PHP
0394      * classes into JavaScript class2 classes.
0395      * NOTE: Currently only public methods and variables are proxied onto
0396      * the client machine
0397      *
0398      * @param string $className The name of the class, the class must be
0399      *  instantiable using a null constructor
0400      * @param string $package Optional package name appended to JavaScript
0401      *  proxy class name
0402      * @return string The class2 (JavaScript) encoding of the class
0403      * @throws Zend_Json_Exception
0404      */
0405     public static function encodeClass($className, $package = '')
0406     {
0407         $cls = new ReflectionClass($className);
0408         if (! $cls->isInstantiable()) {
0409             // require_once 'Zend/Json/Exception.php';
0410             throw new Zend_Json_Exception("$className must be instantiable");
0411         }
0412 
0413         return "Class.create('$package$className',{"
0414                 . self::_encodeConstants($cls)    .","
0415                 . self::_encodeMethods($cls)      .","
0416                 . self::_encodeVariables($cls)    .'});';
0417     }
0418 
0419 
0420     /**
0421      * Encode several classes at once
0422      *
0423      * Returns JSON encoded classes, using {@link encodeClass()}.
0424      *
0425      * @param array $classNames
0426      * @param string $package
0427      * @return string
0428      */
0429     public static function encodeClasses(array $classNames, $package = '')
0430     {
0431         $result = '';
0432         foreach ($classNames as $className) {
0433             $result .= self::encodeClass($className, $package);
0434         }
0435 
0436         return $result;
0437     }
0438 
0439     /**
0440      * Encode Unicode Characters to \u0000 ASCII syntax.
0441      *
0442      * This algorithm was originally developed for the
0443      * Solar Framework by Paul M. Jones
0444      *
0445      * @link   http://solarphp.com/
0446      * @link   http://svn.solarphp.com/core/trunk/Solar/Json.php
0447      * @param  string $value
0448      * @return string
0449      */
0450     public static function encodeUnicodeString($value)
0451     {
0452         $strlen_var = strlen($value);
0453         $ascii = "";
0454 
0455         /**
0456          * Iterate over every character in the string,
0457          * escaping with a slash or encoding to UTF-8 where necessary
0458          */
0459         for($i = 0; $i < $strlen_var; $i++) {
0460             $ord_var_c = ord($value[$i]);
0461 
0462             switch (true) {
0463                 case (($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)):
0464                     // characters U-00000000 - U-0000007F (same as ASCII)
0465                     $ascii .= $value[$i];
0466                     break;
0467 
0468                 case (($ord_var_c & 0xE0) == 0xC0):
0469                     // characters U-00000080 - U-000007FF, mask 110XXXXX
0470                     // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
0471                     $char = pack('C*', $ord_var_c, ord($value[$i + 1]));
0472                     $i += 1;
0473                     $utf16 = self::_utf82utf16($char);
0474                     $ascii .= sprintf('\u%04s', bin2hex($utf16));
0475                     break;
0476 
0477                 case (($ord_var_c & 0xF0) == 0xE0):
0478                     // characters U-00000800 - U-0000FFFF, mask 1110XXXX
0479                     // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
0480                     $char = pack('C*', $ord_var_c,
0481                                  ord($value[$i + 1]),
0482                                  ord($value[$i + 2]));
0483                     $i += 2;
0484                     $utf16 = self::_utf82utf16($char);
0485                     $ascii .= sprintf('\u%04s', bin2hex($utf16));
0486                     break;
0487 
0488                 case (($ord_var_c & 0xF8) == 0xF0):
0489                     // characters U-00010000 - U-001FFFFF, mask 11110XXX
0490                     // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
0491                     $char = pack('C*', $ord_var_c,
0492                                  ord($value[$i + 1]),
0493                                  ord($value[$i + 2]),
0494                                  ord($value[$i + 3]));
0495                     $i += 3;
0496                     $utf16 = self::_utf82utf16($char);
0497                     $ascii .= sprintf('\u%04s', bin2hex($utf16));
0498                     break;
0499 
0500                 case (($ord_var_c & 0xFC) == 0xF8):
0501                     // characters U-00200000 - U-03FFFFFF, mask 111110XX
0502                     // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
0503                     $char = pack('C*', $ord_var_c,
0504                                  ord($value[$i + 1]),
0505                                  ord($value[$i + 2]),
0506                                  ord($value[$i + 3]),
0507                                  ord($value[$i + 4]));
0508                     $i += 4;
0509                     $utf16 = self::_utf82utf16($char);
0510                     $ascii .= sprintf('\u%04s', bin2hex($utf16));
0511                     break;
0512 
0513                 case (($ord_var_c & 0xFE) == 0xFC):
0514                     // characters U-04000000 - U-7FFFFFFF, mask 1111110X
0515                     // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
0516                     $char = pack('C*', $ord_var_c,
0517                                  ord($value[$i + 1]),
0518                                  ord($value[$i + 2]),
0519                                  ord($value[$i + 3]),
0520                                  ord($value[$i + 4]),
0521                                  ord($value[$i + 5]));
0522                     $i += 5;
0523                     $utf16 = self::_utf82utf16($char);
0524                     $ascii .= sprintf('\u%04s', bin2hex($utf16));
0525                     break;
0526             }
0527         }
0528 
0529         return $ascii;
0530      }
0531 
0532     /**
0533      * Convert a string from one UTF-8 char to one UTF-16 char.
0534      *
0535      * Normally should be handled by mb_convert_encoding, but
0536      * provides a slower PHP-only method for installations
0537      * that lack the multibye string extension.
0538      *
0539      * This method is from the Solar Framework by Paul M. Jones
0540      *
0541      * @link   http://solarphp.com
0542      * @param string $utf8 UTF-8 character
0543      * @return string UTF-16 character
0544      */
0545     protected static function _utf82utf16($utf8)
0546     {
0547         // Check for mb extension otherwise do by hand.
0548         if( function_exists('mb_convert_encoding') ) {
0549             return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8');
0550         }
0551 
0552         switch (strlen($utf8)) {
0553             case 1:
0554                 // this case should never be reached, because we are in ASCII range
0555                 // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
0556                 return $utf8;
0557 
0558             case 2:
0559                 // return a UTF-16 character from a 2-byte UTF-8 char
0560                 // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
0561                 return chr(0x07 & (ord($utf8{0}) >> 2))
0562                      . chr((0xC0 & (ord($utf8{0}) << 6))
0563                          | (0x3F & ord($utf8{1})));
0564 
0565             case 3:
0566                 // return a UTF-16 character from a 3-byte UTF-8 char
0567                 // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
0568                 return chr((0xF0 & (ord($utf8{0}) << 4))
0569                          | (0x0F & (ord($utf8{1}) >> 2)))
0570                      . chr((0xC0 & (ord($utf8{1}) << 6))
0571                          | (0x7F & ord($utf8{2})));
0572         }
0573 
0574         // ignoring UTF-32 for now, sorry
0575         return '';
0576     }
0577 }
0578