JPSpan_Unserializer_XML_Array   A
last analyzed

Complexity

Total Complexity 4

Size/Duplication

Total Lines 22
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 8
c 0
b 0
f 0
dl 0
loc 22
rs 10
wmc 4

2 Methods

Rating   Name   Duplication   Size   Complexity  
A add() 0 7 3
A __construct() 0 4 1
1
<?php declare(strict_types=1);
2
3
//---------------------------------------------------------------------------
4
5
/**
6
 * Handles parsing of XML requests
7
 */
8
class JPSpan_Unserializer_XML
9
{
10
    /**
11
     * Dictionary of tag names to data node classes
12
     * @var array
13
     */
14
    public $dict;
15
    /**
16
     * Node stack
17
     * @var array
18
     */
19
    public $stack;
20
    /**
21
     * Root node
22
     * @var JPSpan_Unserializer_XML_Root
23
     */
24
    public $root;
25
    /**
26
     * Instance of the SAX parser
27
     * @var int
28
     */
29
    public $parser;
30
    /**
31
     * Whether there's an error in parsing
32
     * @var bool (default = FALSE)
33
     */
34
    public $isError = false;
35
    /**
36
     * Switch for when we're inside the root node
37
     * @var bool
38
     */
39
    public $inData = false;
40
41
    /**
42
     * Set's up the dictionary
43
     */
44
    public function __construct()
45
    {
46
        $this->dict = [
47
            'r' => 'JPSpan_Unserializer_XML_Root',
48
            'n' => 'JPSpan_Unserializer_XML_Null',
49
            'b' => 'JPSpan_Unserializer_XML_Boolean',
50
            'i' => 'JPSpan_Unserializer_XML_Integer',
51
            'd' => 'JPSpan_Unserializer_XML_Double',
52
            's' => 'JPSpan_Unserializer_XML_String',
53
            'a' => 'JPSpan_Unserializer_XML_Array',
54
            'o' => 'JPSpan_Unserializer_XML_Object',
55
            'e' => 'JPSpan_Unserializer_XML_Element',
56
        ];
57
    }
58
59
    /**
60
     * Sax open tag callback
61
     * @param $parser
62
     * @param $tag
63
     * @param $attrs
64
     */
65
    public function open(&$parser, $tag, $attrs): void
66
    {
67
        if (!array_key_exists($tag, $this->dict)) {
68
            $errorMsg = 'Illegal tag name: ' . $tag;
69
            $this->raiseError($errorMsg);
70
71
            return;
72
        }
73
74
        if ('r' === $tag) {
75
            $this->inData = true;
76
        }
77
78
        if ($this->inData) {
79
            $class = $this->dict[$tag];
80
81
            $current       = new $class($this, $attrs);
82
            $this->stack[] = &$current;
83
84
            if ('r' === $tag) {
85
                $this->root = &$current;
86
            }
87
        }
88
    }
89
90
    /**
91
     * Sax tag cdata callback
92
     * @param $parser
93
     * @param $data
94
     */
95
    public function cdata(&$parser, $data): void
0 ignored issues
show
Unused Code introduced by
The parameter $parser 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

95
    public function cdata(/** @scrutinizer ignore-unused */ &$parser, $data): void

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...
96
    {
97
        $len = count($this->stack);
98
        if ($this->stack[$len - 1]->isString) {
99
            $this->stack[$len - 1]->readString($data);
100
        }
101
    }
102
103
    /**
104
     * Sax close tag callback
105
     * @param $parser
106
     * @param $tag
107
     */
108
    public function close(&$parser, $tag): void
109
    {
110
        if ('r' === $tag) {
111
            $this->inData = false;
112
        }
113
114
        if ($this->inData) {
115
            $len = count($this->stack);
116
117
            $this->stack[$len - 2]->add($this->stack[$len - 1]);
118
119
            array_pop($this->stack);
120
        }
121
    }
122
123
    /**
124
     * Raise an error
125
     * @param mixed $msg
126
     */
127
    public function raiseError($msg): void
128
    {
129
        $this->isError = true;
130
        $msg           .= ' [byte index: ' . xml_get_current_byte_index($this->parser) . ']';
0 ignored issues
show
Bug introduced by
$this->parser of type integer is incompatible with the type XmlParser|resource expected by parameter $parser of xml_get_current_byte_index(). ( Ignorable by Annotation )

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

130
        $msg           .= ' [byte index: ' . xml_get_current_byte_index(/** @scrutinizer ignore-type */ $this->parser) . ']';
Loading history...
131
        trigger_error($msg, E_USER_ERROR);
132
    }
133
134
    /**
135
     * Unserialize some XML. If the provided param is not a string containing
136
     * an XML document, it will be returned as is
137
     * @param mixed $data
138
     * @return mixed unserialized data structure
139
     */
140
    public function unserialize($data)
141
    {
142
        // Return anything that's not XML immediately
143
        if (!is_string($data) || !preg_match('/^\s*<\?xml(.+)\?>/U', $data, $match)) {
144
            return $data;
145
        }
146
147
        $this->parser = xml_parser_create('UTF-8');
0 ignored issues
show
Documentation Bug introduced by
It seems like xml_parser_create('UTF-8') of type XmlParser or resource is incompatible with the declared type integer of property $parser.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
148
        xml_parser_set_option($this->parser, XML_OPTION_CASE_FOLDING, false);
149
        xml_set_object($this->parser, $this);
150
        xml_set_elementHandler($this->parser, 'open', 'close');
0 ignored issues
show
Bug introduced by
The function xml_set_elementHandler was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

150
        /** @scrutinizer ignore-call */ 
151
        xml_set_elementHandler($this->parser, 'open', 'close');
Loading history...
151
        xml_set_character_dataHandler($this->parser, 'cdata');
0 ignored issues
show
Bug introduced by
The function xml_set_character_dataHandler was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

151
        /** @scrutinizer ignore-call */ 
152
        xml_set_character_dataHandler($this->parser, 'cdata');
Loading history...
152
153
        if (!xml_parse($this->parser, trim($data), true)) {
154
            $errorCode = xml_get_error_code($this->parser);
155
            $errorMsg  = 'Badly formed XML: (' . $errorCode . ') ' . xml_error_string($this->parser);
0 ignored issues
show
Bug introduced by
$this->parser of type XmlParser|resource is incompatible with the type integer expected by parameter $error_code of xml_error_string(). ( Ignorable by Annotation )

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

155
            $errorMsg  = 'Badly formed XML: (' . $errorCode . ') ' . xml_error_string(/** @scrutinizer ignore-type */ $this->parser);
Loading history...
156
            $this->raiseError($errorMsg);
157
        }
158
159
        @xml_parser_free($this->parser);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for xml_parser_free(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

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

159
        /** @scrutinizer ignore-unhandled */ @xml_parser_free($this->parser);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
160
161
        if (!$this->isError) {
162
            return $this->root->value;
163
        }
164
165
        return false;
166
    }
167
}
168
169
//---------------------------------------------------------------------------
170
171
/**
172
 * Base class for represented data elements in XML
173
 */
174
class JPSpan_Unserializer_XML_Node
175
{
176
    /**
177
     * @var JPSpan_Unserializer_XML
178
     */
179
    public $Handler;
180
    /**
181
     * @var mixed node value
182
     */
183
    public $value;
184
    /**
185
     * @var bool switch to indentify JPSpan_Unserializer_XML_Element nodes
186
     */
187
    public $isElement = false;
188
    /**
189
     * @var bool switch to identify JPSpan_Unserializer_XML_String nodes
190
     */
191
    public $isString = false;
192
193
    /**
194
     * @param mixed $Handler
195
     */
196
    public function __construct(&$Handler)
197
    {
198
        $this->Handler = &$Handler;
199
    }
200
201
    /**
202
     * @param mixed $child
203
     */
204
    public function add($child): void
205
    {
206
        $errorMsg = 'Scalar nodes cannot have children';
207
        $this->Handler->raiseError($errorMsg);
208
    }
209
}
210
211
//---------------------------------------------------------------------------
212
213
/**
214
 * The root XML tag 'r'. Zero or one child tag allowed
215
 */
216
class JPSpan_Unserializer_XML_Root extends JPSpan_Unserializer_XML_Node
217
{
218
    /**
219
     * Switch to track whether root as single child node
220
     * @var bool
221
     */
222
    public $hasValue = false;
223
224
    /**
225
     * @param mixed $Handler
226
     * @param mixed $attrs
227
     */
228
    public function __construct(&$Handler, $attrs)
0 ignored issues
show
Unused Code introduced by
The parameter $attrs 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

228
    public function __construct(&$Handler, /** @scrutinizer ignore-unused */ $attrs)

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...
229
    {
230
        $this->Handler = &$Handler;
231
        $this->value   = null;
232
    }
233
234
    /**
235
     * @param mixed $child
236
     */
237
    public function add($child): void
238
    {
239
        if ($this->hasValue) {
240
            $errorMsg = 'Root node can only contain a single child node';
241
            $this->Handler->raiseError($errorMsg);
242
        } else {
243
            if (!$child->isElement) {
244
                $this->value    = $child->value;
245
                $this->hasValue = true;
246
            } else {
247
                $errorMsg = 'Element nodes can only be placed inside array or object nodes';
248
                $this->Handler->raiseError($errorMsg);
249
            }
250
        }
251
    }
252
}
253
254
//---------------------------------------------------------------------------
255
256
/**
257
 * Null variable 'n'. No children allowed
258
 */
259
class JPSpan_Unserializer_XML_Null extends JPSpan_Unserializer_XML_Node
260
{
261
    /**
262
     * @param mixed $Handler
263
     * @param mixed $attrs
264
     */
265
    public function __construct(&$Handler, $attrs)
0 ignored issues
show
Unused Code introduced by
The parameter $attrs 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

265
    public function __construct(&$Handler, /** @scrutinizer ignore-unused */ $attrs)

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...
266
    {
267
        $this->Handler = &$Handler;
268
        $this->value   = null;
269
    }
270
}
271
272
//---------------------------------------------------------------------------
273
274
/**
275
 * Boolean variable 'b'. Attribute 'v' required. No children allowed
276
 */
277
class JPSpan_Unserializer_XML_Boolean extends JPSpan_Unserializer_XML_Node
278
{
279
    /**
280
     * @param mixed $Handler
281
     * @param mixed $attrs
282
     */
283
    public function __construct(&$Handler, $attrs)
284
    {
285
        $this->Handler = &$Handler;
286
287
        if (isset($attrs['v'])) {
288
            $this->value = (bool)$attrs['v'];
289
        } else {
290
            $errorMsg = 'Value required for boolean';
291
            $this->Handler->raiseError($errorMsg);
292
        }
293
    }
294
}
295
296
//---------------------------------------------------------------------------
297
298
/**
299
 * Integer variable 'i'. Attribute 'v' required. No children allowed
300
 */
301
class JPSpan_Unserializer_XML_Integer extends JPSpan_Unserializer_XML_Node
302
{
303
    /**
304
     * @param mixed $Handler
305
     * @param mixed $attrs
306
     */
307
    public function __construct(&$Handler, $attrs)
308
    {
309
        $this->Handler = &$Handler;
310
311
        if (isset($attrs['v'])) {
312
            $this->value = (int)$attrs['v'];
313
        } else {
314
            $errorMsg = 'Value required for integer';
315
            $this->Handler->raiseError($errorMsg);
316
        }
317
    }
318
}
319
320
//---------------------------------------------------------------------------
321
322
/**
323
 * Double variable 'd' - 'v' attribute required. No children allowed
324
 */
325
class JPSpan_Unserializer_XML_Double extends JPSpan_Unserializer_XML_Node
326
{
327
    /**
328
     * @param mixed $Handler
329
     * @param mixed $attrs
330
     */
331
    public function __construct(&$Handler, $attrs)
332
    {
333
        $this->Handler = &$Handler;
334
335
        if (isset($attrs['v'])) {
336
            $this->value = (float)$attrs['v'];
337
        } else {
338
            $errorMsg = 'Value required for double';
339
            $this->Handler->raiseError($errorMsg);
340
        }
341
    }
342
}
343
344
//---------------------------------------------------------------------------
345
346
/**
347
 * String variable 's' - value passed from JPSpan_Unserializer_XML::cdata
348
 * No child tags allowed
349
 */
350
class JPSpan_Unserializer_XML_String extends JPSpan_Unserializer_XML_Node
351
{
352
    /**
353
     * Declare it's a string - instructs JPSpan_Unserializer_XML::cdata to
354
     * pass on string values
355
     * @var bool TRUE
356
     */
357
    public $isString = true;
358
359
    /**
360
     * @param mixed $Handler
361
     * @param mixed $attrs
362
     */
363
    public function __construct(&$Handler, $attrs)
0 ignored issues
show
Unused Code introduced by
The parameter $attrs 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

363
    public function __construct(&$Handler, /** @scrutinizer ignore-unused */ $attrs)

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...
364
    {
365
        $this->Handler = &$Handler;
366
        $this->value   = '';
367
    }
368
369
    /**
370
     * Read some more string
371
     * @param mixed $string
372
     */
373
    public function readString($string): void
374
    {
375
        $this->value .= $string;
376
    }
377
}
378
379
//---------------------------------------------------------------------------
380
381
/**
382
 * Array variable 'a' - can only contain 'e' tags - zero or more
383
 */
384
class JPSpan_Unserializer_XML_Array extends JPSpan_Unserializer_XML_Node
385
{
386
    /**
387
     * @param mixed $Handler
388
     * @param mixed $attrs
389
     */
390
    public function __construct(&$Handler, $attrs)
0 ignored issues
show
Unused Code introduced by
The parameter $attrs 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

390
    public function __construct(&$Handler, /** @scrutinizer ignore-unused */ $attrs)

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...
391
    {
392
        $this->Handler = &$Handler;
393
        $this->value   = [];
394
    }
395
396
    /**
397
     * @param mixed $child
398
     */
399
    public function add($child): void
400
    {
401
        if ($child->isElement && null !== $child->key) {
402
            $this->value[$child->key] = $child->value;
403
        } else {
404
            $errorMsg = 'Array nodes can only contain element nodes';
405
            $this->Handler->raiseError($errorMsg);
406
        }
407
    }
408
}
409
410
//---------------------------------------------------------------------------
411
412
/**
413
 * Object variable 'o'. Attribute 'c' (class name) required
414
 * Can only contain 'e' tags - zero or more
415
 */
416
class JPSpan_Unserializer_XML_Object extends JPSpan_Unserializer_XML_Node
417
{
418
    /**
419
     * @param mixed $Handler
420
     * @param mixed $attrs
421
     */
422
    public function __construct(&$Handler, $attrs)
423
    {
424
        $this->Handler = &$Handler;
425
426
        if (isset($attrs['c'])) {
427
            $class = $attrs['c'];
428
429
            if (!array_key_exists(mb_strtolower($class), $GLOBALS['_JPSPAN_UNSERIALIZER_MAP'])) {
430
                $errorMsg = 'Illegal object type: ' . \mb_strtolower($class);
431
                $this->Handler->raiseError($errorMsg);
432
433
                return;
434
            }
435
436
            $this->value = new $class();
437
        } else {
438
            $errorMsg = 'Object node requires class attribute';
439
            $this->Handler->raiseError($errorMsg);
440
        }
441
    }
442
443
    /**
444
     * @param mixed $child
445
     */
446
    public function add($child): void
447
    {
448
        if ($child->isElement && $child->key) {
449
            $this->value->{$child->key} = $child->value;
450
        } else {
451
            $errorMsg = 'Object nodes can only contain element nodes';
452
            $this->Handler->raiseError($errorMsg);
453
        }
454
    }
455
}
456
457
//---------------------------------------------------------------------------
458
459
/**
460
 * Array element or object property variable 'e'. Attribute 'k' (key) required
461
 * Can contain zero or one child tags
462
 */
463
class JPSpan_Unserializer_XML_Element extends JPSpan_Unserializer_XML_Node
464
{
465
    /**
466
     * Value of element - defaults to NULL if no child
467
     * @var mixed value
468
     */
469
    public $value = null;
470
    /**
471
     * Element key (e.g. array index or object property name)
472
     * @var mixed key (string or integer)
473
     */
474
    public $key = null;
475
    /**
476
     * Declare it's an element
477
     * @var bool TRUE
478
     */
479
    public $isElement = true;
480
481
    /**
482
     * @param mixed $Handler
483
     * @param mixed $attrs
484
     */
485
    public function __construct(&$Handler, $attrs)
486
    {
487
        $this->Handler = &$Handler;
488
489
        if (isset($attrs['k'])) {
490
            $this->key = $attrs['k'];
491
        } else {
492
            $errorMsg = 'Element node requires key attribute';
493
            $this->Handler->raiseError($errorMsg);
494
        }
495
    }
496
497
    /**
498
     * @param mixed $child
499
     */
500
    public function add($child): void
501
    {
502
        if ($child->isElement) {
503
            $errorMsg = 'Element nodes can only be placed inside array or object nodes';
504
            $this->Handler->raiseError($errorMsg);
505
        } else {
506
            $this->value = $child->value;
507
        }
508
    }
509
}
510