Passed
Branch master (f2d2e3)
by Michael
18:45
created

Services_JSON   F

Complexity

Total Complexity 122

Size/Duplication

Total Lines 661
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 314
c 1
b 0
f 0
dl 0
loc 661
rs 2
wmc 122

8 Methods

Rating   Name   Duplication   Size   Complexity  
F encode() 0 185 36
A utf162utf8() 0 31 5
A name_value() 0 9 2
A isError() 0 10 5
A Services_JSON() 0 3 1
A reduce_string() 0 17 1
A utf82utf16() 0 31 5
F decode() 0 272 67

How to fix   Complexity   

Complex Class

Complex classes like Services_JSON often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Services_JSON, and based on these observations, apply Extract Interface, too.

1
<?php
2
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
3
4
/**
5
 * Converts to and from JSON format.
6
 *
7
 * JSON (JavaScript Object Notation) is a lightweight data-interchange
8
 * format. It is easy for humans to read and write. It is easy for machines
9
 * to parse and generate. It is based on a subset of the JavaScript
10
 * Programming Language, Standard ECMA-262 3rd Edition - December 1999.
11
 * This feature can also be found in  Python. JSON is a text format that is
12
 * completely language independent but uses conventions that are familiar
13
 * to programmers of the C-family of languages, including C, C++, C#, Java,
14
 * JavaScript, Perl, TCL, and many others. These properties make JSON an
15
 * ideal data-interchange language.
16
 *
17
 * This package provides a simple encoder and decoder for JSON notation. It
18
 * is intended for use with client-side Javascript applications that make
19
 * use of HTTPRequest to perform server communication functions - data can
20
 * be encoded into JSON notation for use in a client-side javascript, or
21
 * decoded from incoming Javascript requests. JSON format is native to
22
 * Javascript, and can be directly eval()'ed with no further parsing
23
 * overhead
24
 *
25
 * All strings should be in ASCII or UTF-8 format!
26
 *
27
 * LICENSE: Redistribution and use in source and binary forms, with or
28
 * without modification, are permitted provided that the following
29
 * conditions are met: Redistributions of source code must retain the
30
 * above copyright notice, this list of conditions and the following
31
 * disclaimer. Redistributions in binary form must reproduce the above
32
 * copyright notice, this list of conditions and the following disclaimer
33
 * in the documentation and/or other materials provided with the
34
 * distribution.
35
 *
36
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
37
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
38
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
39
 * NO EVENT SHALL CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
40
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
41
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
42
 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
43
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
44
 * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
45
 * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
46
 * DAMAGE.
47
 *
48
 * @category
49
 * @package     Services_JSON
50
 * @author      Michal Migurski <[email protected]>
51
 * @author      Matt Knapp <mdknapp[at]gmail[dot]com>
52
 * @author      Brett Stimmerman <brettstimmerman[at]gmail[dot]com>
53
 * @copyright   2005 Michal Migurski
54
 * @version     CVS: $Id: JSON.php,v 1.31 2006/06/28 05:54:17 migurski Exp $
55
 * @license     http://www.opensource.org/licenses/bsd-license.php
56
 * @link        http://pear.php.net/pepr/pepr-proposal-show.php?id=198
57
 */
58
59
/**
60
 * Marker constant for Services_JSON::decode(), used to flag stack state
61
 */
62
define('SERVICES_JSON_SLICE',   1);
63
64
/**
65
 * Marker constant for Services_JSON::decode(), used to flag stack state
66
 */
67
define('SERVICES_JSON_IN_STR',  2);
68
69
/**
70
 * Marker constant for Services_JSON::decode(), used to flag stack state
71
 */
72
define('SERVICES_JSON_IN_ARR',  3);
73
74
/**
75
 * Marker constant for Services_JSON::decode(), used to flag stack state
76
 */
77
define('SERVICES_JSON_IN_OBJ',  4);
78
79
/**
80
 * Marker constant for Services_JSON::decode(), used to flag stack state
81
 */
82
define('SERVICES_JSON_IN_CMT', 5);
83
84
/**
85
 * Behavior switch for Services_JSON::decode()
86
 */
87
define('SERVICES_JSON_LOOSE_TYPE', 16);
88
89
/**
90
 * Behavior switch for Services_JSON::decode()
91
 */
92
define('SERVICES_JSON_SUPPRESS_ERRORS', 32);
93
94
/**
95
 * Converts to and from JSON format.
96
 *
97
 * Brief example of use:
98
 *
99
 * <code>
100
 * // create a new instance of Services_JSON
101
 * $json = new Services_JSON();
102
 *
103
 * // convert a complexe value to JSON notation, and send it to the browser
104
 * $value = array('foo', 'bar', array(1, 2, 'baz'), array(3, array(4)));
105
 * $output = $json->encode($value);
106
 *
107
 * print($output);
108
 * // prints: ["foo","bar",[1,2,"baz"],[3,[4]]]
109
 *
110
 * // accept incoming POST data, assumed to be in JSON notation
111
 * $input = file_get_contents('php://input', 1000000);
112
 * $value = $json->decode($input);
113
 * </code>
114
 */
115
class Services_JSON
116
{
117
   /**
118
    * constructs a new JSON instance
119
    *
120
    * @param    int     $use    object behavior flags; combine with boolean-OR
121
    *
122
    *                           possible values:
123
    *                           - SERVICES_JSON_LOOSE_TYPE:  loose typing.
124
    *                                   "{...}" syntax creates associative arrays
125
    *                                   instead of objects in decode().
126
    *                           - SERVICES_JSON_SUPPRESS_ERRORS:  error suppression.
127
    *                                   Values which can't be encoded (e.g. resources)
128
    *                                   appear as NULL instead of throwing errors.
129
    *                                   By default, a deeply-nested resource will
130
    *                                   bubble up with an error, so all return values
131
    *                                   from encode() should be checked with isError()
132
    */
133
    function Services_JSON($use = 0)
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
134
    {
135
        $this->use = $use;
0 ignored issues
show
Bug Best Practice introduced by
The property use does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
136
    }
137
138
   /**
139
    * convert a string from one UTF-16 char to one UTF-8 char
140
    *
141
    * Normally should be handled by mb_convert_encoding, but
142
    * provides a slower PHP-only method for installations
143
    * that lack the multibye string extension.
144
    *
145
    * @param    string  $utf16  UTF-16 character
146
    * @return   string  UTF-8 character
147
    * @access   private
148
    */
149
    function utf162utf8($utf16)
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
150
    {
151
        // oh please oh please oh please oh please oh please
152
        if(function_exists('mb_convert_encoding')) {
153
            return mb_convert_encoding($utf16, 'UTF-8', 'UTF-16');
154
        }
155
156
        $bytes = (ord($utf16{0}) << 8) | ord($utf16{1});
157
158
        switch(true) {
159
            case ((0x7F & $bytes) == $bytes):
160
                // this case should never be reached, because we are in ASCII range
161
                // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
162
                return chr(0x7F & $bytes);
163
164
            case (0x07FF & $bytes) == $bytes:
165
                // return a 2-byte UTF-8 character
166
                // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
167
                return chr(0xC0 | (($bytes >> 6) & 0x1F))
168
                     . chr(0x80 | ($bytes & 0x3F));
169
170
            case (0xFFFF & $bytes) == $bytes:
171
                // return a 3-byte UTF-8 character
172
                // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
173
                return chr(0xE0 | (($bytes >> 12) & 0x0F))
174
                     . chr(0x80 | (($bytes >> 6) & 0x3F))
175
                     . chr(0x80 | ($bytes & 0x3F));
176
        }
177
178
        // ignoring UTF-32 for now, sorry
179
        return '';
180
    }
181
182
   /**
183
    * convert a string from one UTF-8 char to one UTF-16 char
184
    *
185
    * Normally should be handled by mb_convert_encoding, but
186
    * provides a slower PHP-only method for installations
187
    * that lack the multibye string extension.
188
    *
189
    * @param    string  $utf8   UTF-8 character
190
    * @return   string  UTF-16 character
191
    * @access   private
192
    */
193
    function utf82utf16($utf8)
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
194
    {
195
        // oh please oh please oh please oh please oh please
196
        if(function_exists('mb_convert_encoding')) {
197
            return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8');
198
        }
199
200
        switch(strlen($utf8)) {
201
            case 1:
202
                // this case should never be reached, because we are in ASCII range
203
                // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
204
                return $utf8;
205
206
            case 2:
207
                // return a UTF-16 character from a 2-byte UTF-8 char
208
                // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
209
                return chr(0x07 & (ord($utf8{0}) >> 2))
210
                     . chr((0xC0 & (ord($utf8{0}) << 6))
211
                         | (0x3F & ord($utf8{1})));
212
213
            case 3:
214
                // return a UTF-16 character from a 3-byte UTF-8 char
215
                // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
216
                return chr((0xF0 & (ord($utf8{0}) << 4))
217
                         | (0x0F & (ord($utf8{1}) >> 2)))
218
                     . chr((0xC0 & (ord($utf8{1}) << 6))
219
                         | (0x7F & ord($utf8{2})));
220
        }
221
222
        // ignoring UTF-32 for now, sorry
223
        return '';
224
    }
225
226
   /**
227
    * encodes an arbitrary variable into JSON format
228
    *
229
    * @param    mixed   $var    any number, boolean, string, array, or object to be encoded.
230
    *                           see argument 1 to Services_JSON() above for array-parsing behavior.
231
    *                           if var is a strng, note that encode() always expects it
232
    *                           to be in ASCII or UTF-8 format!
233
    *
234
    * @return   mixed   JSON string representation of input var or an error if a problem occurs
235
    * @access   public
236
    */
237
    function encode($var)
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
238
    {
239
        switch (gettype($var)) {
240
            case 'boolean':
241
                return $var ? 'true' : 'false';
242
243
            case 'NULL':
244
                return 'null';
245
246
            case 'integer':
247
                return (int) $var;
248
249
            case 'double':
250
            case 'float':
251
                return (float) $var;
252
253
            case 'string':
254
                // STRINGS ARE EXPECTED TO BE IN ASCII OR UTF-8 FORMAT
255
                $ascii = '';
256
                $strlen_var = strlen($var);
257
258
               /*
259
                * Iterate over every character in the string,
260
                * escaping with a slash or encoding to UTF-8 where necessary
261
                */
262
                for ($c = 0; $c < $strlen_var; ++$c) {
263
264
                    $ord_var_c = ord($var{$c});
265
266
                    switch (true) {
267
                        case $ord_var_c == 0x08:
268
                            $ascii .= '\b';
269
                            break;
270
                        case $ord_var_c == 0x09:
271
                            $ascii .= '\t';
272
                            break;
273
                        case $ord_var_c == 0x0A:
274
                            $ascii .= '\n';
275
                            break;
276
                        case $ord_var_c == 0x0C:
277
                            $ascii .= '\f';
278
                            break;
279
                        case $ord_var_c == 0x0D:
280
                            $ascii .= '\r';
281
                            break;
282
283
                        case $ord_var_c == 0x22:
284
                        case $ord_var_c == 0x2F:
285
                        case $ord_var_c == 0x5C:
286
                            // double quote, slash, slosh
287
                            $ascii .= '\\'.$var{$c};
288
                            break;
289
290
                        case (($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)):
291
                            // characters U-00000000 - U-0000007F (same as ASCII)
292
                            $ascii .= $var{$c};
293
                            break;
294
295
                        case (($ord_var_c & 0xE0) == 0xC0):
296
                            // characters U-00000080 - U-000007FF, mask 110XXXXX
297
                            // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
298
                            $char = pack('C*', $ord_var_c, ord($var{$c + 1}));
299
                            $c += 1;
300
                            $utf16 = $this->utf82utf16($char);
301
                            $ascii .= sprintf('\u%04s', bin2hex($utf16));
302
                            break;
303
304
                        case (($ord_var_c & 0xF0) == 0xE0):
305
                            // characters U-00000800 - U-0000FFFF, mask 1110XXXX
306
                            // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
307
                            $char = pack('C*', $ord_var_c,
308
                                         ord($var{$c + 1}),
309
                                         ord($var{$c + 2}));
310
                            $c += 2;
311
                            $utf16 = $this->utf82utf16($char);
312
                            $ascii .= sprintf('\u%04s', bin2hex($utf16));
313
                            break;
314
315
                        case (($ord_var_c & 0xF8) == 0xF0):
316
                            // characters U-00010000 - U-001FFFFF, mask 11110XXX
317
                            // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
318
                            $char = pack('C*', $ord_var_c,
319
                                         ord($var{$c + 1}),
320
                                         ord($var{$c + 2}),
321
                                         ord($var{$c + 3}));
322
                            $c += 3;
323
                            $utf16 = $this->utf82utf16($char);
324
                            $ascii .= sprintf('\u%04s', bin2hex($utf16));
325
                            break;
326
327
                        case (($ord_var_c & 0xFC) == 0xF8):
328
                            // characters U-00200000 - U-03FFFFFF, mask 111110XX
329
                            // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
330
                            $char = pack('C*', $ord_var_c,
331
                                         ord($var{$c + 1}),
332
                                         ord($var{$c + 2}),
333
                                         ord($var{$c + 3}),
334
                                         ord($var{$c + 4}));
335
                            $c += 4;
336
                            $utf16 = $this->utf82utf16($char);
337
                            $ascii .= sprintf('\u%04s', bin2hex($utf16));
338
                            break;
339
340
                        case (($ord_var_c & 0xFE) == 0xFC):
341
                            // characters U-04000000 - U-7FFFFFFF, mask 1111110X
342
                            // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
343
                            $char = pack('C*', $ord_var_c,
344
                                         ord($var{$c + 1}),
345
                                         ord($var{$c + 2}),
346
                                         ord($var{$c + 3}),
347
                                         ord($var{$c + 4}),
348
                                         ord($var{$c + 5}));
349
                            $c += 5;
350
                            $utf16 = $this->utf82utf16($char);
351
                            $ascii .= sprintf('\u%04s', bin2hex($utf16));
352
                            break;
353
                    }
354
                }
355
356
                return '"'.$ascii.'"';
357
358
            case 'array':
359
               /*
360
                * As per JSON spec if any array key is not an integer
361
                * we must treat the the whole array as an object. We
362
                * also try to catch a sparsely populated associative
363
                * array with numeric keys here because some JS engines
364
                * will create an array with empty indexes up to
365
                * max_index which can cause memory issues and because
366
                * the keys, which may be relevant, will be remapped
367
                * otherwise.
368
                *
369
                * As per the ECMA and JSON specification an object may
370
                * have any string as a property. Unfortunately due to
371
                * a hole in the ECMA specification if the key is a
372
                * ECMA reserved word or starts with a digit the
373
                * parameter is only accessible using ECMAScript's
374
                * bracket notation.
375
                */
376
377
                // treat as a JSON object
378
                if (is_array($var) && count($var) && (array_keys($var) !== range(0, sizeof($var) - 1))) {
379
                    $properties = array_map(array($this, 'name_value'),
380
                                            array_keys($var),
381
                                            array_values($var));
382
383
                    foreach($properties as $property) {
384
                        if(Services_JSON::isError($property)) {
0 ignored issues
show
Bug Best Practice introduced by
The method Services_JSON::isError() is not static, but was called statically. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

384
                        if(Services_JSON::/** @scrutinizer ignore-call */ isError($property)) {
Loading history...
385
                            return $property;
386
                        }
387
                    }
388
389
                    return '{' . join(',', $properties) . '}';
390
                }
391
392
                // treat it like a regular array
393
                $elements = array_map(array($this, 'encode'), $var);
394
395
                foreach($elements as $element) {
396
                    if(Services_JSON::isError($element)) {
397
                        return $element;
398
                    }
399
                }
400
401
                return '[' . join(',', $elements) . ']';
402
403
            case 'object':
404
                $vars = get_object_vars($var);
405
406
                $properties = array_map(array($this, 'name_value'),
407
                                        array_keys($vars),
408
                                        array_values($vars));
409
410
                foreach($properties as $property) {
411
                    if(Services_JSON::isError($property)) {
412
                        return $property;
413
                    }
414
                }
415
416
                return '{' . join(',', $properties) . '}';
417
418
            default:
419
                return ($this->use & SERVICES_JSON_SUPPRESS_ERRORS)
420
                    ? 'null'
421
                    : new Services_JSON_Error(gettype($var)." can not be encoded as JSON string");
422
        }
423
    }
424
425
   /**
426
    * array-walking function for use in generating JSON-formatted name-value pairs
427
    *
428
    * @param    string  $name   name of key to use
429
    * @param    mixed   $value  reference to an array element to be encoded
430
    *
431
    * @return   string  JSON-formatted name-value pair, like '"name":value'
432
    * @access   private
433
    */
434
    function name_value($name, $value)
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
435
    {
436
        $encoded_value = $this->encode($value);
437
438
        if(Services_JSON::isError($encoded_value)) {
0 ignored issues
show
Bug Best Practice introduced by
The method Services_JSON::isError() is not static, but was called statically. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

438
        if(Services_JSON::/** @scrutinizer ignore-call */ isError($encoded_value)) {
Loading history...
439
            return $encoded_value;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $encoded_value also could return the type Services_JSON_Error which is incompatible with the documented return type string.
Loading history...
440
        }
441
442
        return $this->encode(strval($name)) . ':' . $encoded_value;
443
    }
444
445
   /**
446
    * reduce a string by removing leading and trailing comments and whitespace
447
    *
448
    * @param    $str    string      string value to strip of comments and whitespace
449
    *
450
    * @return   string  string value stripped of comments and whitespace
451
    * @access   private
452
    */
453
    function reduce_string($str)
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
454
    {
455
        $str = preg_replace(array(
456
457
                // eliminate single line comments in '// ...' form
458
                '#^\s*//(.+)$#m',
459
460
                // eliminate multi-line comments in '/* ... */' form, at start of string
461
                '#^\s*/\*(.+)\*/#Us',
462
463
                // eliminate multi-line comments in '/* ... */' form, at end of string
464
                '#/\*(.+)\*/\s*$#Us'
465
466
            ), '', $str);
467
468
        // eliminate extraneous space
469
        return trim($str);
470
    }
471
472
   /**
473
    * decodes a JSON string into appropriate variable
474
    *
475
    * @param    string  $str    JSON-formatted string
476
    *
477
    * @return   mixed   number, boolean, string, array, or object
478
    *                   corresponding to given JSON input string.
479
    *                   See argument 1 to Services_JSON() above for object-output behavior.
480
    *                   Note that decode() always returns strings
481
    *                   in ASCII or UTF-8 format!
482
    * @access   public
483
    */
484
    function decode($str)
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
485
    {
486
        $str = $this->reduce_string($str);
487
488
        switch (strtolower($str)) {
489
            case 'true':
490
                return true;
491
492
            case 'false':
493
                return false;
494
495
            case 'null':
496
                return null;
497
498
            default:
499
                $m = array();
500
501
                if (is_numeric($str)) {
502
                    // Lookie-loo, it's a number
503
504
                    // This would work on its own, but I'm trying to be
505
                    // good about returning integers where appropriate:
506
                    // return (float)$str;
507
508
                    // Return float or int, as appropriate
509
                    return ((float)$str == (integer)$str)
510
                        ? (integer)$str
511
                        : (float)$str;
512
513
                } elseif (preg_match('/^("|\').*(\1)$/s', $str, $m) && $m[1] == $m[2]) {
514
                    // STRINGS RETURNED IN UTF-8 FORMAT
515
                    $delim = substr($str, 0, 1);
516
                    $chrs = substr($str, 1, -1);
517
                    $utf8 = '';
518
                    $strlen_chrs = strlen($chrs);
519
520
                    for ($c = 0; $c < $strlen_chrs; ++$c) {
521
522
                        $substr_chrs_c_2 = substr($chrs, $c, 2);
523
                        $ord_chrs_c = ord($chrs{$c});
524
525
                        switch (true) {
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing preg_match('/\\u[0-9A-F]..., substr($chrs, $c, 6)) of type integer to the boolean true. If you are specifically checking for non-zero, consider using something more explicit like > 0 or !== 0 instead.
Loading history...
526
                            case $substr_chrs_c_2 == '\b':
527
                                $utf8 .= chr(0x08);
528
                                ++$c;
529
                                break;
530
                            case $substr_chrs_c_2 == '\t':
531
                                $utf8 .= chr(0x09);
532
                                ++$c;
533
                                break;
534
                            case $substr_chrs_c_2 == '\n':
535
                                $utf8 .= chr(0x0A);
536
                                ++$c;
537
                                break;
538
                            case $substr_chrs_c_2 == '\f':
539
                                $utf8 .= chr(0x0C);
540
                                ++$c;
541
                                break;
542
                            case $substr_chrs_c_2 == '\r':
543
                                $utf8 .= chr(0x0D);
544
                                ++$c;
545
                                break;
546
547
                            case $substr_chrs_c_2 == '\\"':
548
                            case $substr_chrs_c_2 == '\\\'':
549
                            case $substr_chrs_c_2 == '\\\\':
550
                            case $substr_chrs_c_2 == '\\/':
551
                                if (($delim == '"' && $substr_chrs_c_2 != '\\\'') ||
552
                                   ($delim == "'" && $substr_chrs_c_2 != '\\"')) {
553
                                    $utf8 .= $chrs{++$c};
554
                                }
555
                                break;
556
557
                            case preg_match('/\\\u[0-9A-F]{4}/i', substr($chrs, $c, 6)):
558
                                // single, escaped unicode character
559
                                $utf16 = chr(hexdec(substr($chrs, ($c + 2), 2)))
0 ignored issues
show
Bug introduced by
It seems like hexdec(substr($chrs, $c + 2, 2)) can also be of type double; however, parameter $ascii of chr() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

559
                                $utf16 = chr(/** @scrutinizer ignore-type */ hexdec(substr($chrs, ($c + 2), 2)))
Loading history...
560
                                       . chr(hexdec(substr($chrs, ($c + 4), 2)));
561
                                $utf8 .= $this->utf162utf8($utf16);
562
                                $c += 5;
563
                                break;
564
565
                            case ($ord_chrs_c >= 0x20) && ($ord_chrs_c <= 0x7F):
566
                                $utf8 .= $chrs{$c};
567
                                break;
568
569
                            case ($ord_chrs_c & 0xE0) == 0xC0:
570
                                // characters U-00000080 - U-000007FF, mask 110XXXXX
571
                                //see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
572
                                $utf8 .= substr($chrs, $c, 2);
573
                                ++$c;
574
                                break;
575
576
                            case ($ord_chrs_c & 0xF0) == 0xE0:
577
                                // characters U-00000800 - U-0000FFFF, mask 1110XXXX
578
                                // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
579
                                $utf8 .= substr($chrs, $c, 3);
580
                                $c += 2;
581
                                break;
582
583
                            case ($ord_chrs_c & 0xF8) == 0xF0:
584
                                // characters U-00010000 - U-001FFFFF, mask 11110XXX
585
                                // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
586
                                $utf8 .= substr($chrs, $c, 4);
587
                                $c += 3;
588
                                break;
589
590
                            case ($ord_chrs_c & 0xFC) == 0xF8:
591
                                // characters U-00200000 - U-03FFFFFF, mask 111110XX
592
                                // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
593
                                $utf8 .= substr($chrs, $c, 5);
594
                                $c += 4;
595
                                break;
596
597
                            case ($ord_chrs_c & 0xFE) == 0xFC:
598
                                // characters U-04000000 - U-7FFFFFFF, mask 1111110X
599
                                // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
600
                                $utf8 .= substr($chrs, $c, 6);
601
                                $c += 5;
602
                                break;
603
604
                        }
605
606
                    }
607
608
                    return $utf8;
609
610
                } elseif (preg_match('/^\[.*\]$/s', $str) || preg_match('/^\{.*\}$/s', $str)) {
611
                    // array, or object notation
612
613
                    if ($str{0} == '[') {
614
                        $stk = array(SERVICES_JSON_IN_ARR);
615
                        $arr = array();
616
                    } else {
617
                        if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
618
                            $stk = array(SERVICES_JSON_IN_OBJ);
619
                            $obj = array();
620
                        } else {
621
                            $stk = array(SERVICES_JSON_IN_OBJ);
622
                            $obj = new stdClass();
623
                        }
624
                    }
625
626
                    array_push($stk, array('what'  => SERVICES_JSON_SLICE,
627
                                           'where' => 0,
628
                                           'delim' => false));
629
630
                    $chrs = substr($str, 1, -1);
631
                    $chrs = $this->reduce_string($chrs);
632
633
                    if ($chrs == '') {
634
                        if (reset($stk) == SERVICES_JSON_IN_ARR) {
635
                            return $arr;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $arr does not seem to be defined for all execution paths leading up to this point.
Loading history...
636
637
                        } else {
638
                            return $obj;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $obj does not seem to be defined for all execution paths leading up to this point.
Loading history...
639
640
                        }
641
                    }
642
643
                    //print("\nparsing {$chrs}\n");
644
645
                    $strlen_chrs = strlen($chrs);
646
647
                    for ($c = 0; $c <= $strlen_chrs; ++$c) {
648
649
                        $top = end($stk);
650
                        $substr_chrs_c_2 = substr($chrs, $c, 2);
651
652
                        if (($c == $strlen_chrs) || (($chrs{$c} == ',') && ($top['what'] == SERVICES_JSON_SLICE))) {
653
                            // found a comma that is not inside a string, array, etc.,
654
                            // OR we've reached the end of the character list
655
                            $slice = substr($chrs, $top['where'], ($c - $top['where']));
656
                            array_push($stk, array('what' => SERVICES_JSON_SLICE, 'where' => ($c + 1), 'delim' => false));
657
                            //print("Found split at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
658
659
                            if (reset($stk) == SERVICES_JSON_IN_ARR) {
660
                                // we are in an array, so just push an element onto the stack
661
                                array_push($arr, $this->decode($slice));
662
663
                            } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) {
664
                                // we are in an object, so figure
665
                                // out the property name and set an
666
                                // element in an associative array,
667
                                // for now
668
                                $parts = array();
669
                                
670
                                if (preg_match('/^\s*(["\'].*[^\\\]["\'])\s*:\s*(\S.*),?$/Uis', $slice, $parts)) {
671
                                    // "name":value pair
672
                                    $key = $this->decode($parts[1]);
673
                                    $val = $this->decode($parts[2]);
674
675
                                    if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
676
                                        $obj[$key] = $val;
677
                                    } else {
678
                                        $obj->$key = $val;
679
                                    }
680
                                } elseif (preg_match('/^\s*(\w+)\s*:\s*(\S.*),?$/Uis', $slice, $parts)) {
681
                                    // name:value pair, where name is unquoted
682
                                    $key = $parts[1];
683
                                    $val = $this->decode($parts[2]);
684
685
                                    if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
686
                                        $obj[$key] = $val;
687
                                    } else {
688
                                        $obj->$key = $val;
689
                                    }
690
                                }
691
692
                            }
693
694
                        } elseif ((($chrs{$c} == '"') || ($chrs{$c} == "'")) && ($top['what'] != SERVICES_JSON_IN_STR)) {
695
                            // found a quote, and we are not inside a string
696
                            array_push($stk, array('what' => SERVICES_JSON_IN_STR, 'where' => $c, 'delim' => $chrs{$c}));
697
                            //print("Found start of string at {$c}\n");
698
699
                        } elseif (($chrs{$c} == $top['delim']) &&
700
                                 ($top['what'] == SERVICES_JSON_IN_STR) &&
701
                                 ((strlen(substr($chrs, 0, $c)) - strlen(rtrim(substr($chrs, 0, $c), '\\'))) % 2 != 1)) {
702
                            // found a quote, we're in a string, and it's not escaped
703
                            // we know that it's not escaped becase there is _not_ an
704
                            // odd number of backslashes at the end of the string so far
705
                            array_pop($stk);
706
                            //print("Found end of string at {$c}: ".substr($chrs, $top['where'], (1 + 1 + $c - $top['where']))."\n");
707
708
                        } elseif (($chrs{$c} == '[') &&
709
                                 in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
710
                            // found a left-bracket, and we are in an array, object, or slice
711
                            array_push($stk, array('what' => SERVICES_JSON_IN_ARR, 'where' => $c, 'delim' => false));
712
                            //print("Found start of array at {$c}\n");
713
714
                        } elseif (($chrs{$c} == ']') && ($top['what'] == SERVICES_JSON_IN_ARR)) {
715
                            // found a right-bracket, and we're in an array
716
                            array_pop($stk);
717
                            //print("Found end of array at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
718
719
                        } elseif (($chrs{$c} == '{') &&
720
                                 in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
721
                            // found a left-brace, and we are in an array, object, or slice
722
                            array_push($stk, array('what' => SERVICES_JSON_IN_OBJ, 'where' => $c, 'delim' => false));
723
                            //print("Found start of object at {$c}\n");
724
725
                        } elseif (($chrs{$c} == '}') && ($top['what'] == SERVICES_JSON_IN_OBJ)) {
726
                            // found a right-brace, and we're in an object
727
                            array_pop($stk);
728
                            //print("Found end of object at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
729
730
                        } elseif (($substr_chrs_c_2 == '/*') &&
731
                                 in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
732
                            // found a comment start, and we are in an array, object, or slice
733
                            array_push($stk, array('what' => SERVICES_JSON_IN_CMT, 'where' => $c, 'delim' => false));
734
                            $c++;
735
                            //print("Found start of comment at {$c}\n");
736
737
                        } elseif (($substr_chrs_c_2 == '*/') && ($top['what'] == SERVICES_JSON_IN_CMT)) {
738
                            // found a comment end, and we're in one now
739
                            array_pop($stk);
740
                            $c++;
741
742
                            for ($i = $top['where']; $i <= $c; ++$i)
743
                                $chrs = substr_replace($chrs, ' ', $i, 1);
744
745
                            //print("Found end of comment at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
746
747
                        }
748
749
                    }
750
751
                    if (reset($stk) == SERVICES_JSON_IN_ARR) {
752
                        return $arr;
753
754
                    } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) {
755
                        return $obj;
756
757
                    }
758
759
                }
760
        }
761
    }
762
763
    /**
764
     * @todo Ultimately, this should just call PEAR::isError()
765
     */
766
    function isError($data, $code = null)
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
767
    {
768
        if (class_exists('pear')) {
769
            return PEAR::isError($data, $code);
0 ignored issues
show
Bug introduced by
The type PEAR was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
770
        } elseif (is_object($data) && (get_class($data) == 'services_json_error' ||
771
                                 is_subclass_of($data, 'services_json_error'))) {
772
            return true;
773
        }
774
775
        return false;
776
    }
777
}
778
779
if (class_exists('PEAR_Error')) {
780
781
    class Services_JSON_Error extends PEAR_Error
0 ignored issues
show
Bug introduced by
The type PEAR_Error was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
782
    {
783
        function Services_JSON_Error($message = 'unknown error', $code = null,
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
784
                                     $mode = null, $options = null, $userinfo = null)
785
        {
786
            parent::PEAR_Error($message, $code, $mode, $options, $userinfo);
787
        }
788
    }
789
790
} else {
791
792
    /**
793
     * @todo Ultimately, this class shall be descended from PEAR_Error
794
     */
795
    class Services_JSON_Error
796
    {
797
        function Services_JSON_Error($message = 'unknown error', $code = null,
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
Unused Code introduced by
The parameter $code is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

797
        function Services_JSON_Error($message = 'unknown error', /** @scrutinizer ignore-unused */ $code = null,

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $message is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

797
        function Services_JSON_Error(/** @scrutinizer ignore-unused */ $message = 'unknown error', $code = null,

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
798
                                     $mode = null, $options = null, $userinfo = null)
0 ignored issues
show
Unused Code introduced by
The parameter $mode is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

798
                                     /** @scrutinizer ignore-unused */ $mode = null, $options = null, $userinfo = null)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $options is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

798
                                     $mode = null, /** @scrutinizer ignore-unused */ $options = null, $userinfo = null)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $userinfo is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

798
                                     $mode = null, $options = null, /** @scrutinizer ignore-unused */ $userinfo = null)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
799
        {
800
801
        }
802
    }
803
804
}
805
    
806
?>
0 ignored issues
show
Best Practice introduced by
It is not recommended to use PHP's closing tag ?> in files other than templates.

Using a closing tag in PHP files that only contain PHP code is not recommended as you might accidentally add whitespace after the closing tag which would then be output by PHP. This can cause severe problems, for example headers cannot be sent anymore.

A simple precaution is to leave off the closing tag as it is not required, and it also has no negative effects whatsoever.

Loading history...