Services_JSON   F
last analyzed

Complexity

Total Complexity 122

Size/Duplication

Total Lines 657
Duplicated Lines 12.79 %

Coupling/Cohesion

Components 1
Dependencies 1

Importance

Changes 0
Metric Value
wmc 122
lcom 1
cbo 1
dl 84
loc 657
rs 1.943
c 0
b 0
f 0

8 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A utf162utf8() 0 32 5
A utf82utf16() 0 32 5
F encode() 25 202 36
A name_value() 0 10 2
A reduce_string() 0 18 1
F decode() 59 257 67
A isError() 0 11 5

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

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
    public function __construct($use = 0)
134
    {
135
        $this->use = $use;
0 ignored issues
show
Bug introduced by
The property use does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
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
    public function utf162utf8($utf16)
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
    public function utf82utf16($utf8)
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
    public function encode($var)
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
                    $ord_var_c = ord($var{$c});
264
265
                    switch (true) {
266
                        case $ord_var_c == 0x08:
267
                            $ascii .= '\b';
268
                            break;
269
                        case $ord_var_c == 0x09:
270
                            $ascii .= '\t';
271
                            break;
272
                        case $ord_var_c == 0x0A:
273
                            $ascii .= '\n';
274
                            break;
275
                        case $ord_var_c == 0x0C:
276
                            $ascii .= '\f';
277
                            break;
278
                        case $ord_var_c == 0x0D:
279
                            $ascii .= '\r';
280
                            break;
281
282
                        case $ord_var_c == 0x22:
283
                        case $ord_var_c == 0x2F:
284
                        case $ord_var_c == 0x5C:
285
                            // double quote, slash, slosh
286
                            $ascii .= '\\'.$var{$c};
287
                            break;
288
289
                        case (($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)):
290
                            // characters U-00000000 - U-0000007F (same as ASCII)
291
                            $ascii .= $var{$c};
292
                            break;
293
294
                        case (($ord_var_c & 0xE0) == 0xC0):
295
                            // characters U-00000080 - U-000007FF, mask 110XXXXX
296
                            // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
297
                            $char = pack('C*', $ord_var_c, ord($var{$c + 1}));
298
                            $c += 1;
299
                            $utf16 = $this->utf82utf16($char);
300
                            $ascii .= sprintf('\u%04s', bin2hex($utf16));
301
                            break;
302
303
                        case (($ord_var_c & 0xF0) == 0xE0):
304
                            // characters U-00000800 - U-0000FFFF, mask 1110XXXX
305
                            // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
306
                            $char = pack(
307
                                'C*',
308
                                $ord_var_c,
309
                                         ord($var{$c + 1}),
310
                                         ord($var{$c + 2})
311
                            );
312
                            $c += 2;
313
                            $utf16 = $this->utf82utf16($char);
314
                            $ascii .= sprintf('\u%04s', bin2hex($utf16));
315
                            break;
316
317
                        case (($ord_var_c & 0xF8) == 0xF0):
318
                            // characters U-00010000 - U-001FFFFF, mask 11110XXX
319
                            // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
320
                            $char = pack(
321
                                'C*',
322
                                $ord_var_c,
323
                                         ord($var{$c + 1}),
324
                                         ord($var{$c + 2}),
325
                                         ord($var{$c + 3})
326
                            );
327
                            $c += 3;
328
                            $utf16 = $this->utf82utf16($char);
329
                            $ascii .= sprintf('\u%04s', bin2hex($utf16));
330
                            break;
331
332 View Code Duplication
                        case (($ord_var_c & 0xFC) == 0xF8):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
333
                            // characters U-00200000 - U-03FFFFFF, mask 111110XX
334
                            // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
335
                            $char = pack(
336
                                'C*',
337
                                $ord_var_c,
338
                                         ord($var{$c + 1}),
339
                                         ord($var{$c + 2}),
340
                                         ord($var{$c + 3}),
341
                                         ord($var{$c + 4})
342
                            );
343
                            $c += 4;
344
                            $utf16 = $this->utf82utf16($char);
345
                            $ascii .= sprintf('\u%04s', bin2hex($utf16));
346
                            break;
347
348 View Code Duplication
                        case (($ord_var_c & 0xFE) == 0xFC):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
349
                            // characters U-04000000 - U-7FFFFFFF, mask 1111110X
350
                            // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
351
                            $char = pack(
352
                                'C*',
353
                                $ord_var_c,
354
                                         ord($var{$c + 1}),
355
                                         ord($var{$c + 2}),
356
                                         ord($var{$c + 3}),
357
                                         ord($var{$c + 4}),
358
                                         ord($var{$c + 5})
359
                            );
360
                            $c += 5;
361
                            $utf16 = $this->utf82utf16($char);
362
                            $ascii .= sprintf('\u%04s', bin2hex($utf16));
363
                            break;
364
                    }
365
                }
366
367
                return '"'.$ascii.'"';
368
369
            case 'array':
370
               /*
371
                * As per JSON spec if any array key is not an integer
372
                * we must treat the the whole array as an object. We
373
                * also try to catch a sparsely populated associative
374
                * array with numeric keys here because some JS engines
375
                * will create an array with empty indexes up to
376
                * max_index which can cause memory issues and because
377
                * the keys, which may be relevant, will be remapped
378
                * otherwise.
379
                *
380
                * As per the ECMA and JSON specification an object may
381
                * have any string as a property. Unfortunately due to
382
                * a hole in the ECMA specification if the key is a
383
                * ECMA reserved word or starts with a digit the
384
                * parameter is only accessible using ECMAScript's
385
                * bracket notation.
386
                */
387
388
                // treat as a JSON object
389
                if (is_array($var) && count($var) && (array_keys($var) !== range(0, sizeof($var) - 1))) {
390
                    $properties = array_map(
391
                        array($this, 'name_value'),
392
                                            array_keys($var),
393
                                            array_values($var)
394
                    );
395
396
                    foreach ($properties as $property) {
397
                        if (Services_JSON::isError($property)) {
398
                            return $property;
399
                        }
400
                    }
401
402
                    return '{' . join(',', $properties) . '}';
403
                }
404
405
                // treat it like a regular array
406
                $elements = array_map(array($this, 'encode'), $var);
407
408
                foreach ($elements as $element) {
409
                    if (Services_JSON::isError($element)) {
410
                        return $element;
411
                    }
412
                }
413
414
                return '[' . join(',', $elements) . ']';
415
416
            case 'object':
417
                $vars = get_object_vars($var);
418
419
                $properties = array_map(
420
                    array($this, 'name_value'),
421
                                        array_keys($vars),
422
                                        array_values($vars)
423
                );
424
425
                foreach ($properties as $property) {
426
                    if (Services_JSON::isError($property)) {
427
                        return $property;
428
                    }
429
                }
430
431
                return '{' . join(',', $properties) . '}';
432
433
            default:
434
                return ($this->use & SERVICES_JSON_SUPPRESS_ERRORS)
435
                    ? 'null'
436
                    : new Services_JSON_Error(gettype($var)." can not be encoded as JSON string");
437
        }
438
    }
439
440
    /**
441
     * array-walking function for use in generating JSON-formatted name-value pairs
442
     *
443
     * @param    string  $name   name of key to use
444
     * @param    mixed   $value  reference to an array element to be encoded
445
     *
446
     * @return   string  JSON-formatted name-value pair, like '"name":value'
447
     * @access   private
448
     */
449
    public function name_value($name, $value)
450
    {
451
        $encoded_value = $this->encode($value);
452
453
        if (Services_JSON::isError($encoded_value)) {
454
            return $encoded_value;
455
        }
456
457
        return $this->encode(strval($name)) . ':' . $encoded_value;
458
    }
459
460
    /**
461
     * reduce a string by removing leading and trailing comments and whitespace
462
     *
463
     * @param    $str    string      string value to strip of comments and whitespace
464
     *
465
     * @return   string  string value stripped of comments and whitespace
466
     * @access   private
467
     */
468
    public function reduce_string($str)
469
    {
470
        $str = preg_replace(array(
471
472
                // eliminate single line comments in '// ...' form
473
                '#^\s*//(.+)$#m',
474
475
                // eliminate multi-line comments in '/* ... */' form, at start of string
476
                '#^\s*/\*(.+)\*/#Us',
477
478
                // eliminate multi-line comments in '/* ... */' form, at end of string
479
                '#/\*(.+)\*/\s*$#Us'
480
481
            ), '', $str);
482
483
        // eliminate extraneous space
484
        return trim($str);
485
    }
486
487
    /**
488
     * decodes a JSON string into appropriate variable
489
     *
490
     * @param    string  $str    JSON-formatted string
491
     *
492
     * @return   mixed   number, boolean, string, array, or object
493
     *                   corresponding to given JSON input string.
494
     *                   See argument 1 to Services_JSON() above for object-output behavior.
495
     *                   Note that decode() always returns strings
496
     *                   in ASCII or UTF-8 format!
497
     * @access   public
498
     */
499
    public function decode($str)
500
    {
501
        $str = $this->reduce_string($str);
502
503
        switch (strtolower($str)) {
504
            case 'true':
505
                return true;
506
507
            case 'false':
508
                return false;
509
510
            case 'null':
511
                return null;
512
513
            default:
514
                $m = array();
515
516
                if (is_numeric($str)) {
517
                    // Lookie-loo, it's a number
518
519
                    // This would work on its own, but I'm trying to be
520
                    // good about returning integers where appropriate:
521
                    // return (float)$str;
522
523
                    // Return float or int, as appropriate
524
                    return ((float)$str == (integer)$str)
525
                        ? (integer)$str
526
                        : (float)$str;
527
                } elseif (preg_match('/^("|\').*(\1)$/s', $str, $m) && $m[1] == $m[2]) {
528
                    // STRINGS RETURNED IN UTF-8 FORMAT
529
                    $delim = substr($str, 0, 1);
530
                    $chrs = substr($str, 1, -1);
531
                    $utf8 = '';
532
                    $strlen_chrs = strlen($chrs);
533
534
                    for ($c = 0; $c < $strlen_chrs; ++$c) {
535
                        $substr_chrs_c_2 = substr($chrs, $c, 2);
536
                        $ord_chrs_c = ord($chrs{$c});
537
538
                        switch (true) {
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing preg_match('/\\\\u[0-9A-..., 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...
539
                            case $substr_chrs_c_2 == '\b':
540
                                $utf8 .= chr(0x08);
541
                                ++$c;
542
                                break;
543
                            case $substr_chrs_c_2 == '\t':
544
                                $utf8 .= chr(0x09);
545
                                ++$c;
546
                                break;
547
                            case $substr_chrs_c_2 == '\n':
548
                                $utf8 .= chr(0x0A);
549
                                ++$c;
550
                                break;
551
                            case $substr_chrs_c_2 == '\f':
552
                                $utf8 .= chr(0x0C);
553
                                ++$c;
554
                                break;
555
                            case $substr_chrs_c_2 == '\r':
556
                                $utf8 .= chr(0x0D);
557
                                ++$c;
558
                                break;
559
560
                            case $substr_chrs_c_2 == '\\"':
561
                            case $substr_chrs_c_2 == '\\\'':
562
                            case $substr_chrs_c_2 == '\\\\':
563
                            case $substr_chrs_c_2 == '\\/':
564
                                if (($delim == '"' && $substr_chrs_c_2 != '\\\'') ||
565
                                   ($delim == "'" && $substr_chrs_c_2 != '\\"')) {
566
                                    $utf8 .= $chrs{++$c};
567
                                }
568
                                break;
569
570
                            case preg_match('/\\\u[0-9A-F]{4}/i', substr($chrs, $c, 6)):
571
                                // single, escaped unicode character
572
                                $utf16 = chr(hexdec(substr($chrs, ($c + 2), 2)))
573
                                       . chr(hexdec(substr($chrs, ($c + 4), 2)));
574
                                $utf8 .= $this->utf162utf8($utf16);
575
                                $c += 5;
576
                                break;
577
578
                            case ($ord_chrs_c >= 0x20) && ($ord_chrs_c <= 0x7F):
579
                                $utf8 .= $chrs{$c};
580
                                break;
581
582 View Code Duplication
                            case ($ord_chrs_c & 0xE0) == 0xC0:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
583
                                // characters U-00000080 - U-000007FF, mask 110XXXXX
584
                                //see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
585
                                $utf8 .= substr($chrs, $c, 2);
586
                                ++$c;
587
                                break;
588
589 View Code Duplication
                            case ($ord_chrs_c & 0xF0) == 0xE0:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
590
                                // characters U-00000800 - U-0000FFFF, mask 1110XXXX
591
                                // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
592
                                $utf8 .= substr($chrs, $c, 3);
593
                                $c += 2;
594
                                break;
595
596 View Code Duplication
                            case ($ord_chrs_c & 0xF8) == 0xF0:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
597
                                // characters U-00010000 - U-001FFFFF, mask 11110XXX
598
                                // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
599
                                $utf8 .= substr($chrs, $c, 4);
600
                                $c += 3;
601
                                break;
602
603 View Code Duplication
                            case ($ord_chrs_c & 0xFC) == 0xF8:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
604
                                // characters U-00200000 - U-03FFFFFF, mask 111110XX
605
                                // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
606
                                $utf8 .= substr($chrs, $c, 5);
607
                                $c += 4;
608
                                break;
609
610 View Code Duplication
                            case ($ord_chrs_c & 0xFE) == 0xFC:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
611
                                // characters U-04000000 - U-7FFFFFFF, mask 1111110X
612
                                // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
613
                                $utf8 .= substr($chrs, $c, 6);
614
                                $c += 5;
615
                                break;
616
617
                        }
618
                    }
619
620
                    return $utf8;
621
                } elseif (preg_match('/^\[.*\]$/s', $str) || preg_match('/^\{.*\}$/s', $str)) {
622
                    // array, or object notation
623
624
                    if ($str{0} == '[') {
625
                        $stk = array(SERVICES_JSON_IN_ARR);
626
                        $arr = array();
627
                    } else {
628
                        if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
629
                            $stk = array(SERVICES_JSON_IN_OBJ);
630
                            $obj = array();
631
                        } else {
632
                            $stk = array(SERVICES_JSON_IN_OBJ);
633
                            $obj = new stdClass();
634
                        }
635
                    }
636
637
                    array_push($stk, array('what'  => SERVICES_JSON_SLICE,
638
                                           'where' => 0,
639
                                           'delim' => false));
640
641
                    $chrs = substr($str, 1, -1);
642
                    $chrs = $this->reduce_string($chrs);
643
644
                    if ($chrs == '') {
645
                        if (reset($stk) == SERVICES_JSON_IN_ARR) {
646
                            return $arr;
0 ignored issues
show
Bug introduced by
The variable $arr does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
647
                        } else {
648
                            return $obj;
0 ignored issues
show
Bug introduced by
The variable $obj does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
649
                        }
650
                    }
651
652
                    //print("\nparsing {$chrs}\n");
653
654
                    $strlen_chrs = strlen($chrs);
655
656
                    for ($c = 0; $c <= $strlen_chrs; ++$c) {
657
                        $top = end($stk);
658
                        $substr_chrs_c_2 = substr($chrs, $c, 2);
659
660
                        if (($c == $strlen_chrs) || (($chrs{$c} == ',') && ($top['what'] == SERVICES_JSON_SLICE))) {
661
                            // found a comma that is not inside a string, array, etc.,
662
                            // OR we've reached the end of the character list
663
                            $slice = substr($chrs, $top['where'], ($c - $top['where']));
664
                            array_push($stk, array('what' => SERVICES_JSON_SLICE, 'where' => ($c + 1), 'delim' => false));
665
                            //print("Found split at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
666
667
                            if (reset($stk) == SERVICES_JSON_IN_ARR) {
668
                                // we are in an array, so just push an element onto the stack
669
                                array_push($arr, $this->decode($slice));
670
                            } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) {
671
                                // we are in an object, so figure
672
                                // out the property name and set an
673
                                // element in an associative array,
674
                                // for now
675
                                $parts = array();
676
677
                                if (preg_match('/^\s*(["\'].*[^\\\]["\'])\s*:\s*(\S.*),?$/Uis', $slice, $parts)) {
678
                                    // "name":value pair
679
                                    $key = $this->decode($parts[1]);
680
                                    $val = $this->decode($parts[2]);
681
682 View Code Duplication
                                    if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
683
                                        $obj[$key] = $val;
684
                                    } else {
685
                                        $obj->$key = $val;
686
                                    }
687
                                } elseif (preg_match('/^\s*(\w+)\s*:\s*(\S.*),?$/Uis', $slice, $parts)) {
688
                                    // name:value pair, where name is unquoted
689
                                    $key = $parts[1];
690
                                    $val = $this->decode($parts[2]);
691
692 View Code Duplication
                                    if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
693
                                        $obj[$key] = $val;
694
                                    } else {
695
                                        $obj->$key = $val;
696
                                    }
697
                                }
698
                            }
699
                        } elseif ((($chrs{$c} == '"') || ($chrs{$c} == "'")) && ($top['what'] != SERVICES_JSON_IN_STR)) {
700
                            // found a quote, and we are not inside a string
701
                            array_push($stk, array('what' => SERVICES_JSON_IN_STR, 'where' => $c, 'delim' => $chrs{$c}));
702
                        // print("Found start of string at {$c}\n");
703
                        } elseif (($chrs{$c} == $top['delim']) &&
704
                                 ($top['what'] == SERVICES_JSON_IN_STR) &&
705
                                 ((strlen(substr($chrs, 0, $c)) - strlen(rtrim(substr($chrs, 0, $c), '\\'))) % 2 != 1)) {
706
                            // found a quote, we're in a string, and it's not escaped
707
                            // we know that it's not escaped becase there is _not_ an
708
                            // odd number of backslashes at the end of the string so far
709
                            array_pop($stk);
710
                        // print("Found end of string at {$c}: ".substr($chrs, $top['where'], (1 + 1 + $c - $top['where']))."\n");
711 View Code Duplication
                        } elseif (($chrs{$c} == '[') &&
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
712
                                 in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
713
                            // found a left-bracket, and we are in an array, object, or slice
714
                            array_push($stk, array('what' => SERVICES_JSON_IN_ARR, 'where' => $c, 'delim' => false));
715
                        // print("Found start of array at {$c}\n");
716
                        } elseif (($chrs{$c} == ']') && ($top['what'] == SERVICES_JSON_IN_ARR)) {
717
                            // found a right-bracket, and we're in an array
718
                            array_pop($stk);
719
                        // print("Found end of array at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
720 View Code Duplication
                        } elseif (($chrs{$c} == '{') &&
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
721
                                 in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
722
                            // found a left-brace, and we are in an array, object, or slice
723
                            array_push($stk, array('what' => SERVICES_JSON_IN_OBJ, 'where' => $c, 'delim' => false));
724
                        //print("Found start of object at {$c}\n");
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 View Code Duplication
                        } elseif (($substr_chrs_c_2 == '/*') &&
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
730
                                 in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
731
                            // found a comment start, and we are in an array, object, or slice
732
                            array_push($stk, array('what' => SERVICES_JSON_IN_CMT, 'where' => $c, 'delim' => false));
733
                            $c++;
734
                        // print("Found start of comment at {$c}\n");
735
                        } elseif (($substr_chrs_c_2 == '*/') && ($top['what'] == SERVICES_JSON_IN_CMT)) {
736
                            // found a comment end, and we're in one now
737
                            array_pop($stk);
738
                            $c++;
739
740
                            for ($i = $top['where']; $i <= $c; ++$i) {
741
                                $chrs = substr_replace($chrs, ' ', $i, 1);
742
                            }
743
744
                            //print("Found end of comment at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
745
                        }
746
                    }
747
748
                    if (reset($stk) == SERVICES_JSON_IN_ARR) {
749
                        return $arr;
750
                    } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) {
751
                        return $obj;
752
                    }
753
                }
754
        }
755
    }
756
757
    /**
758
     * @todo Ultimately, this should just call PEAR::isError()
759
     */
760
    public function isError($data, $code = null)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
761
    {
762
        if (class_exists('pear')) {
763
            return PEAR::isError($data, $code);
764
        } elseif (is_object($data) && (get_class($data) == 'services_json_error' ||
765
                                 is_subclass_of($data, 'services_json_error'))) {
766
            return true;
767
        }
768
769
        return false;
770
    }
771
}
772
773
if (class_exists('PEAR_Error')) {
774
    class Services_JSON_Error extends PEAR_Error
775
    {
776
        public function __construct(
777
            $message = 'unknown error',
778
            $code = null,
779
                                     $mode = null,
780
            $options = null,
781
            $userinfo = null
782
        ) {
783
            parent::PEAR_Error($message, $code, $mode, $options, $userinfo);
0 ignored issues
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (PEAR_Error() instead of __construct()). Are you sure this is correct? If so, you might want to change this to $this->PEAR_Error().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
784
        }
785
    }
786
} else {
787
788
    /**
789
     * @todo Ultimately, this class shall be descended from PEAR_Error
790
     */
791
    class Services_JSON_Error
0 ignored issues
show
Comprehensibility Best Practice introduced by
The type Services_JSON_Error has been defined more than once; this definition is ignored, only the first definition in this file (L774-785) is considered.

This check looks for classes that have been defined more than once in the same file.

If you can, we would recommend to use standard object-oriented programming techniques. For example, to avoid multiple types, it might make sense to create a common interface, and then multiple, different implementations for that interface.

This also has the side-effect of providing you with better IDE auto-completion, static analysis and also better OPCode caching from PHP.

Loading history...
792
    {
793
        public function __construct(
794
            $message = 'unknown error',
795
            $code = null,
796
                                     $mode = null,
797
            $options = null,
798
            $userinfo = null
799
        ) {
800
        }
801
    }
802
}
803