1<?php
2/**
3 * $Id: JSON.php 40 2007-06-18 11:43:15Z spocke $
4 *
5 * @package MCManager.utils
6 * @author Moxiecode
7 * @copyright Copyright � 2007, Moxiecode Systems AB, All rights reserved.
8 */
9
10define('JSON_BOOL', 1);
11define('JSON_INT', 2);
12define('JSON_STR', 3);
13define('JSON_FLOAT', 4);
14define('JSON_NULL', 5);
15define('JSON_START_OBJ', 6);
16define('JSON_END_OBJ', 7);
17define('JSON_START_ARRAY', 8);
18define('JSON_END_ARRAY', 9);
19define('JSON_KEY', 10);
20define('JSON_SKIP', 11);
21
22define('JSON_IN_ARRAY', 30);
23define('JSON_IN_OBJECT', 40);
24define('JSON_IN_BETWEEN', 50);
25
26class Moxiecode_JSONReader {
27	var $_data, $_len, $_pos;
28	var $_value, $_token;
29	var $_location, $_lastLocations;
30	var $_needProp;
31
32	public function __construct($data) {
33		$this->_data = $data;
34		$this->_len = strlen($data);
35		$this->_pos = -1;
36		$this->_location = JSON_IN_BETWEEN;
37		$this->_lastLocations = array();
38		$this->_needProp = false;
39	}
40
41    /**
42     * Old syntax of class constructor. Deprecated in PHP7.
43     *
44     * @deprecated since Moodle 3.1
45     */
46    public function Moxiecode_JSONReader($data) {
47        debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER);
48        self::__construct($data);
49    }
50
51	function getToken() {
52		return $this->_token;
53	}
54
55	function getLocation() {
56		return $this->_location;
57	}
58
59	function getTokenName() {
60		switch ($this->_token) {
61			case JSON_BOOL:
62				return 'JSON_BOOL';
63
64			case JSON_INT:
65				return 'JSON_INT';
66
67			case JSON_STR:
68				return 'JSON_STR';
69
70			case JSON_FLOAT:
71				return 'JSON_FLOAT';
72
73			case JSON_NULL:
74				return 'JSON_NULL';
75
76			case JSON_START_OBJ:
77				return 'JSON_START_OBJ';
78
79			case JSON_END_OBJ:
80				return 'JSON_END_OBJ';
81
82			case JSON_START_ARRAY:
83				return 'JSON_START_ARRAY';
84
85			case JSON_END_ARRAY:
86				return 'JSON_END_ARRAY';
87
88			case JSON_KEY:
89				return 'JSON_KEY';
90		}
91
92		return 'UNKNOWN';
93	}
94
95	function getValue() {
96		return $this->_value;
97	}
98
99	function readToken() {
100		$chr = $this->read();
101
102		if ($chr != null) {
103			switch ($chr) {
104				case '[':
105					$this->_lastLocation[] = $this->_location;
106					$this->_location = JSON_IN_ARRAY;
107					$this->_token = JSON_START_ARRAY;
108					$this->_value = null;
109					$this->readAway();
110					return true;
111
112				case ']':
113					$this->_location = array_pop($this->_lastLocation);
114					$this->_token = JSON_END_ARRAY;
115					$this->_value = null;
116					$this->readAway();
117
118					if ($this->_location == JSON_IN_OBJECT)
119						$this->_needProp = true;
120
121					return true;
122
123				case '{':
124					$this->_lastLocation[] = $this->_location;
125					$this->_location = JSON_IN_OBJECT;
126					$this->_needProp = true;
127					$this->_token = JSON_START_OBJ;
128					$this->_value = null;
129					$this->readAway();
130					return true;
131
132				case '}':
133					$this->_location = array_pop($this->_lastLocation);
134					$this->_token = JSON_END_OBJ;
135					$this->_value = null;
136					$this->readAway();
137
138					if ($this->_location == JSON_IN_OBJECT)
139						$this->_needProp = true;
140
141					return true;
142
143				// String
144				case '"':
145				case '\'':
146					return $this->_readString($chr);
147
148				// Null
149				case 'n':
150					return $this->_readNull();
151
152				// Bool
153				case 't':
154				case 'f':
155					return $this->_readBool($chr);
156
157				default:
158					// Is number
159					if (is_numeric($chr) || $chr == '-' || $chr == '.')
160						return $this->_readNumber($chr);
161
162					return true;
163			}
164		}
165
166		return false;
167	}
168
169	function _readBool($chr) {
170		$this->_token = JSON_BOOL;
171		$this->_value = $chr == 't';
172
173		if ($chr == 't')
174			$this->skip(3); // rue
175		else
176			$this->skip(4); // alse
177
178		$this->readAway();
179
180		if ($this->_location == JSON_IN_OBJECT && !$this->_needProp)
181			$this->_needProp = true;
182
183		return true;
184	}
185
186	function _readNull() {
187		$this->_token = JSON_NULL;
188		$this->_value = null;
189
190		$this->skip(3); // ull
191		$this->readAway();
192
193		if ($this->_location == JSON_IN_OBJECT && !$this->_needProp)
194			$this->_needProp = true;
195
196		return true;
197	}
198
199	function _readString($quote) {
200		$output = "";
201		$this->_token = JSON_STR;
202		$endString = false;
203
204		while (($chr = $this->peek()) != -1) {
205			switch ($chr) {
206				case '\\':
207					// Read away slash
208					$this->read();
209
210					// Read escape code
211					$chr = $this->read();
212					switch ($chr) {
213							case 't':
214								$output .= "\t";
215								break;
216
217							case 'b':
218								$output .= "\b";
219								break;
220
221							case 'f':
222								$output .= "\f";
223								break;
224
225							case 'r':
226								$output .= "\r";
227								break;
228
229							case 'n':
230								$output .= "\n";
231								break;
232
233							case 'u':
234								$output .= $this->_int2utf8(hexdec($this->read(4)));
235								break;
236
237							default:
238								$output .= $chr;
239								break;
240					}
241
242					break;
243
244					case '\'':
245					case '"':
246						if ($chr == $quote)
247							$endString = true;
248
249						$chr = $this->read();
250						if ($chr != -1 && $chr != $quote)
251							$output .= $chr;
252
253						break;
254
255					default:
256						$output .= $this->read();
257			}
258
259			// String terminated
260			if ($endString)
261				break;
262		}
263
264		$this->readAway();
265		$this->_value = $output;
266
267		// Needed a property
268		if ($this->_needProp) {
269			$this->_token = JSON_KEY;
270			$this->_needProp = false;
271			return true;
272		}
273
274		if ($this->_location == JSON_IN_OBJECT && !$this->_needProp)
275			$this->_needProp = true;
276
277		return true;
278	}
279
280	function _int2utf8($int) {
281		$int = intval($int);
282
283		switch ($int) {
284			case 0:
285				return chr(0);
286
287			case ($int & 0x7F):
288				return chr($int);
289
290			case ($int & 0x7FF):
291				return chr(0xC0 | (($int >> 6) & 0x1F)) . chr(0x80 | ($int & 0x3F));
292
293			case ($int & 0xFFFF):
294				return chr(0xE0 | (($int >> 12) & 0x0F)) . chr(0x80 | (($int >> 6) & 0x3F)) . chr (0x80 | ($int & 0x3F));
295
296			case ($int & 0x1FFFFF):
297				return chr(0xF0 | ($int >> 18)) . chr(0x80 | (($int >> 12) & 0x3F)) . chr(0x80 | (($int >> 6) & 0x3F)) . chr(0x80 | ($int & 0x3F));
298		}
299	}
300
301	function _readNumber($start) {
302		$value = "";
303		$isFloat = false;
304
305		$this->_token = JSON_INT;
306		$value .= $start;
307
308		while (($chr = $this->peek()) != -1) {
309			if (is_numeric($chr) || $chr == '-' || $chr == '.') {
310				if ($chr == '.')
311					$isFloat = true;
312
313				$value .= $this->read();
314			} else
315				break;
316		}
317
318		$this->readAway();
319
320		if ($isFloat) {
321			$this->_token = JSON_FLOAT;
322			$this->_value = floatval($value);
323		} else
324			$this->_value = intval($value);
325
326		if ($this->_location == JSON_IN_OBJECT && !$this->_needProp)
327			$this->_needProp = true;
328
329		return true;
330	}
331
332	function readAway() {
333		while (($chr = $this->peek()) != null) {
334			if ($chr != ':' && $chr != ',' && $chr != ' ')
335				return;
336
337			$this->read();
338		}
339	}
340
341	function read($len = 1) {
342		if ($this->_pos < $this->_len) {
343			if ($len > 1) {
344				$str = substr($this->_data, $this->_pos + 1, $len);
345				$this->_pos += $len;
346
347				return $str;
348			} else
349				return $this->_data[++$this->_pos];
350		}
351
352		return null;
353	}
354
355	function skip($len) {
356		$this->_pos += $len;
357	}
358
359	function peek() {
360		if ($this->_pos < $this->_len)
361			return $this->_data[$this->_pos + 1];
362
363		return null;
364	}
365}
366
367/**
368 * This class handles JSON stuff.
369 *
370 * @package MCManager.utils
371 */
372class Moxiecode_JSON {
373	public function __construct() {
374	}
375
376    /**
377     * Old syntax of class constructor. Deprecated in PHP7.
378     *
379     * @deprecated since Moodle 3.1
380     */
381    public function Moxiecode_JSON() {
382        debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER);
383        self::__construct();
384    }
385
386	function decode($input) {
387		$reader = new Moxiecode_JSONReader($input);
388
389		return $this->readValue($reader);
390	}
391
392	function readValue(&$reader) {
393		$this->data = array();
394		$this->parents = array();
395		$this->cur =& $this->data;
396		$key = null;
397		$loc = JSON_IN_ARRAY;
398
399		while ($reader->readToken()) {
400			switch ($reader->getToken()) {
401				case JSON_STR:
402				case JSON_INT:
403				case JSON_BOOL:
404				case JSON_FLOAT:
405				case JSON_NULL:
406					switch ($reader->getLocation()) {
407						case JSON_IN_OBJECT:
408							$this->cur[$key] = $reader->getValue();
409							break;
410
411						case JSON_IN_ARRAY:
412							$this->cur[] = $reader->getValue();
413							break;
414
415						default:
416							return $reader->getValue();
417					}
418					break;
419
420				case JSON_KEY:
421					$key = $reader->getValue();
422					break;
423
424				case JSON_START_OBJ:
425				case JSON_START_ARRAY:
426					if ($loc == JSON_IN_OBJECT)
427						$this->addArray($key);
428					else
429						$this->addArray(null);
430
431					$cur =& $obj;
432
433					$loc = $reader->getLocation();
434					break;
435
436				case JSON_END_OBJ:
437				case JSON_END_ARRAY:
438					$loc = $reader->getLocation();
439
440					if (count($this->parents) > 0) {
441						$this->cur =& $this->parents[count($this->parents) - 1];
442						array_pop($this->parents);
443					}
444					break;
445			}
446		}
447
448		return $this->data[0];
449	}
450
451	// This method was needed since PHP is crapy and doesn't have pointers/references
452	function addArray($key) {
453		$this->parents[] =& $this->cur;
454		$ar = array();
455
456		if ($key)
457			$this->cur[$key] =& $ar;
458		else
459			$this->cur[] =& $ar;
460
461		$this->cur =& $ar;
462	}
463
464	function getDelim($index, &$reader) {
465		switch ($reader->getLocation()) {
466			case JSON_IN_ARRAY:
467			case JSON_IN_OBJECT:
468				if ($index > 0)
469					return ",";
470				break;
471		}
472
473		return "";
474	}
475
476	function encode($input) {
477		switch (gettype($input)) {
478			case 'boolean':
479				return $input ? 'true' : 'false';
480
481			case 'integer':
482				return (int) $input;
483
484			case 'float':
485			case 'double':
486				return (float) $input;
487
488			case 'NULL':
489				return 'null';
490
491			case 'string':
492				return $this->encodeString($input);
493
494			case 'array':
495				return $this->_encodeArray($input);
496
497			case 'object':
498				return $this->_encodeArray(get_object_vars($input));
499		}
500
501		return '';
502	}
503
504	function encodeString($input) {
505		// Needs to be escaped
506		if (preg_match('/[^a-zA-Z0-9]/', $input)) {
507			$output = '';
508
509			for ($i=0; $i<strlen($input); $i++) {
510				switch ($input[$i]) {
511					case "\b":
512						$output .= "\\b";
513						break;
514
515					case "\t":
516						$output .= "\\t";
517						break;
518
519					case "\f":
520						$output .= "\\f";
521						break;
522
523					case "\r":
524						$output .= "\\r";
525						break;
526
527					case "\n":
528						$output .= "\\n";
529						break;
530
531					case '\\':
532						$output .= "\\\\";
533						break;
534
535					case '\'':
536						$output .= "\\'";
537						break;
538
539					case '"':
540						$output .= '\"';
541						break;
542
543					default:
544						$byte = ord($input[$i]);
545
546						if (($byte & 0xE0) == 0xC0) {
547							$char = pack('C*', $byte, ord($input[$i + 1]));
548							$i += 1;
549							$output .= sprintf('\u%04s', bin2hex($this->_utf82utf16($char)));
550						} if (($byte & 0xF0) == 0xE0) {
551							$char = pack('C*', $byte, ord($input[$i + 1]), ord($input[$i + 2]));
552							$i += 2;
553							$output .= sprintf('\u%04s', bin2hex($this->_utf82utf16($char)));
554						} if (($byte & 0xF8) == 0xF0) {
555							$char = pack('C*', $byte, ord($input[$i + 1]), ord($input[$i + 2]), ord($input[$i + 3]));
556							$i += 3;
557							$output .= sprintf('\u%04s', bin2hex($this->_utf82utf16($char)));
558						} if (($byte & 0xFC) == 0xF8) {
559							$char = pack('C*', $byte, ord($input[$i + 1]), ord($input[$i + 2]), ord($input[$i + 3]), ord($input[$i + 4]));
560							$i += 4;
561							$output .= sprintf('\u%04s', bin2hex($this->_utf82utf16($char)));
562						} if (($byte & 0xFE) == 0xFC) {
563							$char = pack('C*', $byte, ord($input[$i + 1]), ord($input[$i + 2]), ord($input[$i + 3]), ord($input[$i + 4]), ord($input[$i + 5]));
564							$i += 5;
565							$output .= sprintf('\u%04s', bin2hex($this->_utf82utf16($char)));
566						} else if ($byte < 128)
567							$output .= $input[$i];
568				}
569			}
570
571			return '"' . $output . '"';
572		}
573
574		return '"' . $input . '"';
575	}
576
577	function _utf82utf16($utf8) {
578		if (function_exists('mb_convert_encoding'))
579			return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8');
580
581		switch (strlen($utf8)) {
582			case 1:
583				return $utf8;
584
585			case 2:
586				return chr(0x07 & (ord($utf8[0]) >> 2)) . chr((0xC0 & (ord($utf8[0]) << 6)) | (0x3F & ord($utf8[1])));
587
588			case 3:
589				return chr((0xF0 & (ord($utf8[0]) << 4)) | (0x0F & (ord($utf8[1]) >> 2))) . chr((0xC0 & (ord($utf8[1]) << 6)) | (0x7F & ord($utf8[2])));
590		}
591
592		return '';
593	}
594
595	function _encodeArray($input) {
596		$output = '';
597		$isIndexed = true;
598
599		$keys = array_keys($input);
600		for ($i=0; $i<count($keys); $i++) {
601			if (!is_int($keys[$i])) {
602				$output .= $this->encodeString($keys[$i]) . ':' . $this->encode($input[$keys[$i]]);
603				$isIndexed = false;
604			} else
605				$output .= $this->encode($input[$keys[$i]]);
606
607			if ($i != count($keys) - 1)
608				$output .= ',';
609		}
610
611		return $isIndexed ? '[' . $output . ']' : '{' . $output . '}';
612	}
613}
614
615?>
616