PageRenderTime 24ms CodeModel.GetById 40ms RepoModel.GetById 0ms app.codeStats 0ms

/src/lib/Zend/Json/Encoder.php

https://bitbucket.org/mkrasuski/magento-ce
PHP | 578 lines | 290 code | 74 blank | 214 comment | 41 complexity | 0282ee4cb81e78771c8829398f64082f MD5 | raw file
  1. <?php
  2. /**
  3. * Zend Framework
  4. *
  5. * LICENSE
  6. *
  7. * This source file is subject to the new BSD license that is bundled
  8. * with this package in the file LICENSE.txt.
  9. * It is also available through the world-wide-web at this URL:
  10. * http://framework.zend.com/license/new-bsd
  11. * If you did not receive a copy of the license and are unable to
  12. * obtain it through the world-wide-web, please send an email
  13. * to license@zend.com so we can send you a copy immediately.
  14. *
  15. * @category Zend
  16. * @package Zend_Json
  17. * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
  18. * @license http://framework.zend.com/license/new-bsd New BSD License
  19. * @version $Id$
  20. */
  21. /**
  22. * Encode PHP constructs to JSON
  23. *
  24. * @category Zend
  25. * @package Zend_Json
  26. * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
  27. * @license http://framework.zend.com/license/new-bsd New BSD License
  28. */
  29. class Zend_Json_Encoder
  30. {
  31. /**
  32. * Whether or not to check for possible cycling
  33. *
  34. * @var boolean
  35. */
  36. protected $_cycleCheck;
  37. /**
  38. * Additional options used during encoding
  39. *
  40. * @var array
  41. */
  42. protected $_options = array();
  43. /**
  44. * Array of visited objects; used to prevent cycling.
  45. *
  46. * @var array
  47. */
  48. protected $_visited = array();
  49. /**
  50. * Constructor
  51. *
  52. * @param boolean $cycleCheck Whether or not to check for recursion when encoding
  53. * @param array $options Additional options used during encoding
  54. * @return void
  55. */
  56. protected function __construct($cycleCheck = false, $options = array())
  57. {
  58. $this->_cycleCheck = $cycleCheck;
  59. $this->_options = $options;
  60. }
  61. /**
  62. * Use the JSON encoding scheme for the value specified
  63. *
  64. * @param mixed $value The value to be encoded
  65. * @param boolean $cycleCheck Whether or not to check for possible object recursion when encoding
  66. * @param array $options Additional options used during encoding
  67. * @return string The encoded value
  68. */
  69. public static function encode($value, $cycleCheck = false, $options = array())
  70. {
  71. $encoder = new self(($cycleCheck) ? true : false, $options);
  72. return $encoder->_encodeValue($value);
  73. }
  74. /**
  75. * Recursive driver which determines the type of value to be encoded
  76. * and then dispatches to the appropriate method. $values are either
  77. * - objects (returns from {@link _encodeObject()})
  78. * - arrays (returns from {@link _encodeArray()})
  79. * - basic datums (e.g. numbers or strings) (returns from {@link _encodeDatum()})
  80. *
  81. * @param mixed $value The value to be encoded
  82. * @return string Encoded value
  83. */
  84. protected function _encodeValue(&$value)
  85. {
  86. if (is_object($value)) {
  87. return $this->_encodeObject($value);
  88. } else if (is_array($value)) {
  89. return $this->_encodeArray($value);
  90. }
  91. return $this->_encodeDatum($value);
  92. }
  93. /**
  94. * Encode an object to JSON by encoding each of the public properties
  95. *
  96. * A special property is added to the JSON object called '__className'
  97. * that contains the name of the class of $value. This is used to decode
  98. * the object on the client into a specific class.
  99. *
  100. * @param object $value
  101. * @return string
  102. * @throws Zend_Json_Exception If recursive checks are enabled and the object has been serialized previously
  103. */
  104. protected function _encodeObject(&$value)
  105. {
  106. if ($this->_cycleCheck) {
  107. if ($this->_wasVisited($value)) {
  108. if (isset($this->_options['silenceCyclicalExceptions'])
  109. && $this->_options['silenceCyclicalExceptions']===true) {
  110. return '"* RECURSION (' . get_class($value) . ') *"';
  111. } else {
  112. #require_once 'Zend/Json/Exception.php';
  113. throw new Zend_Json_Exception(
  114. 'Cycles not supported in JSON encoding, cycle introduced by '
  115. . 'class "' . get_class($value) . '"'
  116. );
  117. }
  118. }
  119. $this->_visited[] = $value;
  120. }
  121. $props = '';
  122. if (method_exists($value, 'toJson')) {
  123. $props =',' . preg_replace("/^\{(.*)\}$/","\\1",$value->toJson());
  124. } else {
  125. if ($value instanceof IteratorAggregate) {
  126. $propCollection = $value->getIterator();
  127. } elseif ($value instanceof Iterator) {
  128. $propCollection = $value;
  129. } else {
  130. $propCollection = get_object_vars($value);
  131. }
  132. foreach ($propCollection as $name => $propValue) {
  133. if (isset($propValue)) {
  134. $props .= ','
  135. . $this->_encodeString($name)
  136. . ':'
  137. . $this->_encodeValue($propValue);
  138. }
  139. }
  140. }
  141. $className = get_class($value);
  142. return '{"__className":' . $this->_encodeString($className)
  143. . $props . '}';
  144. }
  145. /**
  146. * Determine if an object has been serialized already
  147. *
  148. * @param mixed $value
  149. * @return boolean
  150. */
  151. protected function _wasVisited(&$value)
  152. {
  153. if (in_array($value, $this->_visited, true)) {
  154. return true;
  155. }
  156. return false;
  157. }
  158. /**
  159. * JSON encode an array value
  160. *
  161. * Recursively encodes each value of an array and returns a JSON encoded
  162. * array string.
  163. *
  164. * Arrays are defined as integer-indexed arrays starting at index 0, where
  165. * the last index is (count($array) -1); any deviation from that is
  166. * considered an associative array, and will be encoded as such.
  167. *
  168. * @param array& $array
  169. * @return string
  170. */
  171. protected function _encodeArray(&$array)
  172. {
  173. $tmpArray = array();
  174. // Check for associative array
  175. if (!empty($array) && (array_keys($array) !== range(0, count($array) - 1))) {
  176. // Associative array
  177. $result = '{';
  178. foreach ($array as $key => $value) {
  179. $key = (string) $key;
  180. $tmpArray[] = $this->_encodeString($key)
  181. . ':'
  182. . $this->_encodeValue($value);
  183. }
  184. $result .= implode(',', $tmpArray);
  185. $result .= '}';
  186. } else {
  187. // Indexed array
  188. $result = '[';
  189. $length = count($array);
  190. for ($i = 0; $i < $length; $i++) {
  191. $tmpArray[] = $this->_encodeValue($array[$i]);
  192. }
  193. $result .= implode(',', $tmpArray);
  194. $result .= ']';
  195. }
  196. return $result;
  197. }
  198. /**
  199. * JSON encode a basic data type (string, number, boolean, null)
  200. *
  201. * If value type is not a string, number, boolean, or null, the string
  202. * 'null' is returned.
  203. *
  204. * @param mixed& $value
  205. * @return string
  206. */
  207. protected function _encodeDatum(&$value)
  208. {
  209. $result = 'null';
  210. if (is_int($value) || is_float($value)) {
  211. $result = (string) $value;
  212. $result = str_replace(",", ".", $result);
  213. } elseif (is_string($value)) {
  214. $result = $this->_encodeString($value);
  215. } elseif (is_bool($value)) {
  216. $result = $value ? 'true' : 'false';
  217. }
  218. return $result;
  219. }
  220. /**
  221. * JSON encode a string value by escaping characters as necessary
  222. *
  223. * @param string& $value
  224. * @return string
  225. */
  226. protected function _encodeString(&$string)
  227. {
  228. // Escape these characters with a backslash:
  229. // " \ / \n \r \t \b \f
  230. $search = array('\\', "\n", "\t", "\r", "\b", "\f", '"', '/');
  231. $replace = array('\\\\', '\\n', '\\t', '\\r', '\\b', '\\f', '\"', '\\/');
  232. $string = str_replace($search, $replace, $string);
  233. // Escape certain ASCII characters:
  234. // 0x08 => \b
  235. // 0x0c => \f
  236. $string = str_replace(array(chr(0x08), chr(0x0C)), array('\b', '\f'), $string);
  237. $string = self::encodeUnicodeString($string);
  238. return '"' . $string . '"';
  239. }
  240. /**
  241. * Encode the constants associated with the ReflectionClass
  242. * parameter. The encoding format is based on the class2 format
  243. *
  244. * @param ReflectionClass $cls
  245. * @return string Encoded constant block in class2 format
  246. */
  247. private static function _encodeConstants(ReflectionClass $cls)
  248. {
  249. $result = "constants : {";
  250. $constants = $cls->getConstants();
  251. $tmpArray = array();
  252. if (!empty($constants)) {
  253. foreach ($constants as $key => $value) {
  254. $tmpArray[] = "$key: " . self::encode($value);
  255. }
  256. $result .= implode(', ', $tmpArray);
  257. }
  258. return $result . "}";
  259. }
  260. /**
  261. * Encode the public methods of the ReflectionClass in the
  262. * class2 format
  263. *
  264. * @param ReflectionClass $cls
  265. * @return string Encoded method fragment
  266. *
  267. */
  268. private static function _encodeMethods(ReflectionClass $cls)
  269. {
  270. $methods = $cls->getMethods();
  271. $result = 'methods:{';
  272. $started = false;
  273. foreach ($methods as $method) {
  274. if (! $method->isPublic() || !$method->isUserDefined()) {
  275. continue;
  276. }
  277. if ($started) {
  278. $result .= ',';
  279. }
  280. $started = true;
  281. $result .= '' . $method->getName(). ':function(';
  282. if ('__construct' != $method->getName()) {
  283. $parameters = $method->getParameters();
  284. $paramCount = count($parameters);
  285. $argsStarted = false;
  286. $argNames = "var argNames=[";
  287. foreach ($parameters as $param) {
  288. if ($argsStarted) {
  289. $result .= ',';
  290. }
  291. $result .= $param->getName();
  292. if ($argsStarted) {
  293. $argNames .= ',';
  294. }
  295. $argNames .= '"' . $param->getName() . '"';
  296. $argsStarted = true;
  297. }
  298. $argNames .= "];";
  299. $result .= "){"
  300. . $argNames
  301. . 'var result = ZAjaxEngine.invokeRemoteMethod('
  302. . "this, '" . $method->getName()
  303. . "',argNames,arguments);"
  304. . 'return(result);}';
  305. } else {
  306. $result .= "){}";
  307. }
  308. }
  309. return $result . "}";
  310. }
  311. /**
  312. * Encode the public properties of the ReflectionClass in the class2
  313. * format.
  314. *
  315. * @param ReflectionClass $cls
  316. * @return string Encode properties list
  317. *
  318. */
  319. private static function _encodeVariables(ReflectionClass $cls)
  320. {
  321. $properties = $cls->getProperties();
  322. $propValues = get_class_vars($cls->getName());
  323. $result = "variables:{";
  324. $cnt = 0;
  325. $tmpArray = array();
  326. foreach ($properties as $prop) {
  327. if (! $prop->isPublic()) {
  328. continue;
  329. }
  330. $tmpArray[] = $prop->getName()
  331. . ':'
  332. . self::encode($propValues[$prop->getName()]);
  333. }
  334. $result .= implode(',', $tmpArray);
  335. return $result . "}";
  336. }
  337. /**
  338. * Encodes the given $className into the class2 model of encoding PHP
  339. * classes into JavaScript class2 classes.
  340. * NOTE: Currently only public methods and variables are proxied onto
  341. * the client machine
  342. *
  343. * @param string $className The name of the class, the class must be
  344. * instantiable using a null constructor
  345. * @param string $package Optional package name appended to JavaScript
  346. * proxy class name
  347. * @return string The class2 (JavaScript) encoding of the class
  348. * @throws Zend_Json_Exception
  349. */
  350. public static function encodeClass($className, $package = '')
  351. {
  352. $cls = new ReflectionClass($className);
  353. if (! $cls->isInstantiable()) {
  354. #require_once 'Zend/Json/Exception.php';
  355. throw new Zend_Json_Exception("$className must be instantiable");
  356. }
  357. return "Class.create('$package$className',{"
  358. . self::_encodeConstants($cls) .","
  359. . self::_encodeMethods($cls) .","
  360. . self::_encodeVariables($cls) .'});';
  361. }
  362. /**
  363. * Encode several classes at once
  364. *
  365. * Returns JSON encoded classes, using {@link encodeClass()}.
  366. *
  367. * @param array $classNames
  368. * @param string $package
  369. * @return string
  370. */
  371. public static function encodeClasses(array $classNames, $package = '')
  372. {
  373. $result = '';
  374. foreach ($classNames as $className) {
  375. $result .= self::encodeClass($className, $package);
  376. }
  377. return $result;
  378. }
  379. /**
  380. * Encode Unicode Characters to \u0000 ASCII syntax.
  381. *
  382. * This algorithm was originally developed for the
  383. * Solar Framework by Paul M. Jones
  384. *
  385. * @link http://solarphp.com/
  386. * @link http://svn.solarphp.com/core/trunk/Solar/Json.php
  387. * @param string $value
  388. * @return string
  389. */
  390. public static function encodeUnicodeString($value)
  391. {
  392. $strlen_var = strlen($value);
  393. $ascii = "";
  394. /**
  395. * Iterate over every character in the string,
  396. * escaping with a slash or encoding to UTF-8 where necessary
  397. */
  398. for($i = 0; $i < $strlen_var; $i++) {
  399. $ord_var_c = ord($value[$i]);
  400. switch (true) {
  401. case (($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)):
  402. // characters U-00000000 - U-0000007F (same as ASCII)
  403. $ascii .= $value[$i];
  404. break;
  405. case (($ord_var_c & 0xE0) == 0xC0):
  406. // characters U-00000080 - U-000007FF, mask 110XXXXX
  407. // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
  408. $char = pack('C*', $ord_var_c, ord($value[$i + 1]));
  409. $i += 1;
  410. $utf16 = self::_utf82utf16($char);
  411. $ascii .= sprintf('\u%04s', bin2hex($utf16));
  412. break;
  413. case (($ord_var_c & 0xF0) == 0xE0):
  414. // characters U-00000800 - U-0000FFFF, mask 1110XXXX
  415. // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
  416. $char = pack('C*', $ord_var_c,
  417. ord($value[$i + 1]),
  418. ord($value[$i + 2]));
  419. $i += 2;
  420. $utf16 = self::_utf82utf16($char);
  421. $ascii .= sprintf('\u%04s', bin2hex($utf16));
  422. break;
  423. case (($ord_var_c & 0xF8) == 0xF0):
  424. // characters U-00010000 - U-001FFFFF, mask 11110XXX
  425. // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
  426. $char = pack('C*', $ord_var_c,
  427. ord($value[$i + 1]),
  428. ord($value[$i + 2]),
  429. ord($value[$i + 3]));
  430. $i += 3;
  431. $utf16 = self::_utf82utf16($char);
  432. $ascii .= sprintf('\u%04s', bin2hex($utf16));
  433. break;
  434. case (($ord_var_c & 0xFC) == 0xF8):
  435. // characters U-00200000 - U-03FFFFFF, mask 111110XX
  436. // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
  437. $char = pack('C*', $ord_var_c,
  438. ord($value[$i + 1]),
  439. ord($value[$i + 2]),
  440. ord($value[$i + 3]),
  441. ord($value[$i + 4]));
  442. $i += 4;
  443. $utf16 = self::_utf82utf16($char);
  444. $ascii .= sprintf('\u%04s', bin2hex($utf16));
  445. break;
  446. case (($ord_var_c & 0xFE) == 0xFC):
  447. // characters U-04000000 - U-7FFFFFFF, mask 1111110X
  448. // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
  449. $char = pack('C*', $ord_var_c,
  450. ord($value[$i + 1]),
  451. ord($value[$i + 2]),
  452. ord($value[$i + 3]),
  453. ord($value[$i + 4]),
  454. ord($value[$i + 5]));
  455. $i += 5;
  456. $utf16 = self::_utf82utf16($char);
  457. $ascii .= sprintf('\u%04s', bin2hex($utf16));
  458. break;
  459. }
  460. }
  461. return $ascii;
  462. }
  463. /**
  464. * Convert a string from one UTF-8 char to one UTF-16 char.
  465. *
  466. * Normally should be handled by mb_convert_encoding, but
  467. * provides a slower PHP-only method for installations
  468. * that lack the multibye string extension.
  469. *
  470. * This method is from the Solar Framework by Paul M. Jones
  471. *
  472. * @link http://solarphp.com
  473. * @param string $utf8 UTF-8 character
  474. * @return string UTF-16 character
  475. */
  476. protected static function _utf82utf16($utf8)
  477. {
  478. // Check for mb extension otherwise do by hand.
  479. if( function_exists('mb_convert_encoding') ) {
  480. return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8');
  481. }
  482. switch (strlen($utf8)) {
  483. case 1:
  484. // this case should never be reached, because we are in ASCII range
  485. // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
  486. return $utf8;
  487. case 2:
  488. // return a UTF-16 character from a 2-byte UTF-8 char
  489. // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
  490. return chr(0x07 & (ord($utf8{0}) >> 2))
  491. . chr((0xC0 & (ord($utf8{0}) << 6))
  492. | (0x3F & ord($utf8{1})));
  493. case 3:
  494. // return a UTF-16 character from a 3-byte UTF-8 char
  495. // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
  496. return chr((0xF0 & (ord($utf8{0}) << 4))
  497. | (0x0F & (ord($utf8{1}) >> 2)))
  498. . chr((0xC0 & (ord($utf8{1}) << 6))
  499. | (0x7F & ord($utf8{2})));
  500. }
  501. // ignoring UTF-32 for now, sorry
  502. return '';
  503. }
  504. }