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