GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

XMLElement   F
last analyzed

Complexity

Total Complexity 117

Size/Duplication

Total Lines 905
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 216
c 0
b 0
f 0
dl 0
loc 905
rs 2
wmc 117

41 Methods

Rating   Name   Duplication   Size   Complexity  
A setElementStyle() 0 3 1
A setDTD() 0 3 1
A setIncludeHeader() 0 3 1
A getNumberOfChildren() 0 3 1
A setSelfClosingTag() 0 3 1
A prependChild() 0 3 1
A setName() 0 3 2
A getIterator() 0 3 1
A addProcessingInstruction() 0 3 1
A setAllowEmptyAttributes() 0 3 1
A setVersion() 0 3 1
A getName() 0 3 1
A validateChild() 0 4 2
A getChildren() 0 3 1
A setEncoding() 0 3 1
A getAttributes() 0 3 1
A setAttribute() 0 3 1
A removeChildAt() 0 15 3
A addClass() 0 8 1
A getValue() 0 17 5
A replaceValue() 0 11 3
A appendChildArray() 0 5 4
C stripInvalidXMLCharacters() 0 26 13
A replaceChildAt() 0 17 3
A getChildrenByName() 0 13 3
B convertNode() 0 18 9
A getRealIndex() 0 7 2
A setAttributeArray() 0 8 4
A getAttribute() 0 7 2
A appendChild() 0 6 1
F generate() 0 78 24
A setValue() 0 9 3
A getChild() 0 7 2
A getChildByName() 0 9 2
A removeClass() 0 8 1
A __construct() 0 7 3
A setChildren() 0 8 2
A convertFromXMLString() 0 6 1
A convert() 0 10 2
A convertFromDOMDocument() 0 10 2
A insertChildAt() 0 20 3

How to fix   Complexity   

Complex Class

Complex classes like XMLElement 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 XMLElement, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * @package toolkit
4
 */
5
/**
6
 * `XMLElement` is a class used to simulate PHP's `DOMElement`
7
 * class. Each object is a representation of a HTML element
8
 * and can store it's children in an array. When an `XMLElement`
9
 * is generated, it is output as an XML string.
10
 */
11
12
class XMLElement implements IteratorAggregate
13
{
14
    /**
15
     * This is an array of HTML elements that are self closing.
16
     * @var array
17
     */
18
    protected static $no_end_tags = array(
19
        'area', 'base', 'br', 'col', 'hr', 'img', 'input', 'link', 'meta', 'param'
20
    );
21
22
    /**
23
     * The name of the HTML Element, eg. 'p'
24
     * @var string
25
     */
26
    private $_name;
27
28
    /**
29
     * The value of this `XMLElement` as a string
30
     * @var string
31
     */
32
    private $_value = array();
33
34
    /**
35
     * Any additional attributes can be included in an associative array
36
     * with the key being the name and the value being the value of the
37
     * attribute.
38
     * @var array
39
     */
40
    private $_attributes = array();
41
42
    /**
43
     * Children of this `XMLElement`, which will also be `XMLElement`'s
44
     * @var array
45
     */
46
    private $_children = array();
47
48
    /**
49
     * Any processing instructions that the XSLT should know about when a
50
     * `XMLElement` is generated
51
     * @var array
52
     */
53
    private $_processingInstructions = array();
54
55
    /**
56
     * The DTD the should be output when a `XMLElement` is generated, defaults to null.
57
     * @var string
58
     */
59
    private $_dtd = null;
60
61
    /**
62
     * The encoding of the `XMLElement`, defaults to 'utf-8'
63
     * @var string
64
     */
65
    private $_encoding = 'utf-8';
66
67
    /**
68
     * The version of the XML that is used for generation, defaults to '1.0'
69
     * @var string
70
     */
71
    private $_version = '1.0';
72
73
    /**
74
     * The type of element, defaults to 'xml'. Used when determining the style
75
     * of end tag for this element when generated
76
     * @var string
77
     */
78
    private $_elementStyle = 'xml';
79
80
    /**
81
     * When set to true this will include the XML declaration will be
82
     * output when the `XMLElement` is generated. Defaults to `false`.
83
     * @var boolean
84
     */
85
    private $_includeHeader = false;
86
87
    /**
88
     * Specifies whether this HTML element has an closing element, or if
89
     * it self closing. Defaults to `true`.
90
     *  eg. `<p></p>` or `<input />`
91
     * @var boolean
92
     */
93
    private $_selfclosing = true;
94
95
    /**
96
     * Specifies whether attributes need to have a value or if they can
97
     * be shorthand. Defaults to `true`. An example of this would be:
98
     *  `<option selected>Value</option>`
99
     * @var boolean
100
     */
101
    private $_allowEmptyAttributes = true;
102
103
    /**
104
     * The constructor for the `XMLElement`
105
     *
106
     * @param string $name
107
     *  The name of the `XMLElement`, 'p'.
108
     * @param string|XMLElement $value (optional)
109
     *  The value of this `XMLElement`, it can be a string
110
     *  or another `XMLElement` object.
111
     * @param array $attributes (optional)
112
     *  Any additional attributes can be included in an associative array with
113
     *  the key being the name and the value being the value of the attribute.
114
     *  Attributes set from this array will override existing attributes
115
     *  set by previous params.
116
     * @param boolean $createHandle
117
     *  Whether this function should convert the `$name` to a handle. Defaults to
118
     *  `false`.
119
     */
120
    public function __construct($name, $value = null, array $attributes = array(), $createHandle = false)
0 ignored issues
show
Coding Style introduced by
Incorrect spacing between argument "$value" and equals sign; expected 0 but found 1
Loading history...
Coding Style introduced by
Incorrect spacing between default value and equals sign for argument "$value"; expected 0 but found 1
Loading history...
Coding Style introduced by
Incorrect spacing between argument "$attributes" and equals sign; expected 0 but found 1
Loading history...
Coding Style introduced by
Incorrect spacing between default value and equals sign for argument "$attributes"; expected 0 but found 1
Loading history...
Coding Style introduced by
Incorrect spacing between argument "$createHandle" and equals sign; expected 0 but found 1
Loading history...
Coding Style introduced by
Incorrect spacing between default value and equals sign for argument "$createHandle"; expected 0 but found 1
Loading history...
121
    {
122
        $this->setName($name, $createHandle);
123
        $this->setValue($value);
124
125
        if (is_array($attributes) && !empty($attributes)) {
126
            $this->setAttributeArray($attributes);
127
        }
128
    }
129
130
    /**
131
     * Accessor for `$_name`
132
     *
133
     * @return string
134
     */
135
    public function getName()
136
    {
137
        return $this->_name;
138
    }
139
140
    /**
141
     * Accessor for `$_value`
142
     *
143
     * @return string|XMLElement
144
     */
145
    public function getValue()
146
    {
147
        $value = '';
148
149
        if (is_array($this->_value)) {
0 ignored issues
show
introduced by
The condition is_array($this->_value) is always false.
Loading history...
150
            foreach ($this->_value as $v) {
151
                if ($v instanceof XMLElement) {
152
                    $value .= $v->generate();
153
                } else {
154
                    $value .= $v;
155
                }
156
            }
157
        } elseif (!is_null($this->_value)) {
0 ignored issues
show
introduced by
The condition is_null($this->_value) is always false.
Loading history...
158
            $value = $this->_value;
159
        }
160
161
        return $value;
162
    }
163
164
    /**
165
     * Retrieves the value of an attribute by name
166
     *
167
     * @param string $name
168
     * @return string
169
     */
170
    public function getAttribute($name)
171
    {
172
        if (!isset($this->_attributes[$name])) {
173
            return null;
174
        }
175
176
        return $this->_attributes[$name];
177
    }
178
179
    /**
180
     * Accessor for `$this->_attributes`
181
     *
182
     * @return array
183
     */
184
    public function getAttributes()
185
    {
186
        return $this->_attributes;
187
    }
188
189
    /**
190
     * Retrieves a child-element by position
191
     *
192
     * @since Symphony 2.3
193
     * @param integer $position
194
     * @return XMLElement
195
     */
196
    public function getChild($position)
197
    {
198
        if (!isset($this->_children[$this->getRealIndex($position)])) {
199
            return null;
200
        }
201
202
        return $this->_children[$this->getRealIndex($position)];
203
    }
204
205
    /**
206
     * Accessor for `$this->_children`
207
     *
208
     * @return array
209
     */
210
    public function getChildren()
211
    {
212
        return $this->_children;
213
    }
214
215
    /**
216
     * Accessor for `$this->_children`, returning only `XMLElement` children,
217
     * not text nodes.
218
     *
219
     * @return XMLElementChildrenFilter
220
     */
221
    public function getIterator()
222
    {
223
        return new XMLElementChildrenFilter(new ArrayIterator($this->_children));
224
    }
225
226
    /**
227
     * Retrieves child-element by name and position. If no child is found,
228
     * `NULL` will be returned.
229
     *
230
     * @since Symphony 2.3
231
     * @param string $name
232
     * @param integer $position
233
     * @return XMLElement
234
     */
235
    public function getChildByName($name, $position)
236
    {
237
        $result = array_values($this->getChildrenByName($name));
238
239
        if (!isset($result[$position])) {
240
            return null;
241
        }
242
243
        return $result[$position];
244
    }
245
246
    /**
247
     * Accessor to return an associative array of all `$this->_children`
248
     * whose's name matches the given `$name`. If no children are found,
249
     * an empty array will be returned.
250
     *
251
     * @since Symphony 2.2.2
252
     * @param string $name
253
     * @return array
254
     *  An associative array where the key is the `$index` of the child
255
     *  in `$this->_children`
256
     */
257
    public function getChildrenByName($name)
258
    {
259
        $result = array();
260
261
        foreach ($this as $i => $child) {
262
            if ($child->getName() != $name) {
263
                continue;
264
            }
265
266
            $result[$i] = $child;
267
        }
268
269
        return $result;
270
    }
271
272
    /**
273
     * Adds processing instructions to this `XMLElement`
274
     *
275
     * @param string $pi
276
     */
277
    public function addProcessingInstruction($pi)
278
    {
279
        $this->_processingInstructions[] = $pi;
280
    }
281
282
    /**
283
     * Sets the DTD for this `XMLElement`
284
     *
285
     * @param string $dtd
286
     */
287
    public function setDTD($dtd)
288
    {
289
        $this->_dtd = $dtd;
290
    }
291
292
    /**
293
     * Sets the encoding for this `XMLElement` for when
294
     * it's generated.
295
     *
296
     * @param string $value
297
     */
298
    public function setEncoding($value)
299
    {
300
        $this->_encoding = $value;
301
    }
302
303
    /**
304
     * Sets the version for the XML declaration of this
305
     * `XMLElement`
306
     *
307
     * @param string $value
308
     */
309
    public function setVersion($value)
310
    {
311
        $this->_version = $value;
312
    }
313
314
    /**
315
     * Sets the style of the `XMLElement`. Used when the
316
     * `XMLElement` is being generated to determine whether
317
     * needs to be closed, is self closing or is standalone.
318
     *
319
     * @param string $style (optional)
320
     *  Defaults to 'xml', any other value will trigger the
321
     *  XMLElement to be closed by itself or left standalone
322
     *  if it is in the `XMLElement::no_end_tags`.
323
     */
324
    public function setElementStyle($style = 'xml')
0 ignored issues
show
Coding Style introduced by
Incorrect spacing between argument "$style" and equals sign; expected 0 but found 1
Loading history...
Coding Style introduced by
Incorrect spacing between default value and equals sign for argument "$style"; expected 0 but found 1
Loading history...
325
    {
326
        $this->_elementStyle = $style;
327
    }
328
329
    /**
330
     * Sets whether this `XMLElement` needs to output an
331
     * XML declaration or not. This normally is only set to
332
     * true for the parent `XMLElement`, eg. 'html'.
333
     *
334
     * @param bool|string $value (optional)
335
     *  Defaults to false
336
     */
337
    public function setIncludeHeader($value = false)
0 ignored issues
show
Coding Style introduced by
Incorrect spacing between argument "$value" and equals sign; expected 0 but found 1
Loading history...
Coding Style introduced by
Incorrect spacing between default value and equals sign for argument "$value"; expected 0 but found 1
Loading history...
338
    {
339
        $this->_includeHeader = $value;
0 ignored issues
show
Documentation Bug introduced by
It seems like $value can also be of type string. However, the property $_includeHeader is declared as type boolean. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
340
    }
341
342
    /**
343
     * Sets whether this `XMLElement` is self closing or not.
344
     *
345
     * @param bool|string $value (optional)
346
     *  Defaults to true
347
     */
348
    public function setSelfClosingTag($value = true)
0 ignored issues
show
Coding Style introduced by
Incorrect spacing between argument "$value" and equals sign; expected 0 but found 1
Loading history...
Coding Style introduced by
Incorrect spacing between default value and equals sign for argument "$value"; expected 0 but found 1
Loading history...
349
    {
350
        $this->_selfclosing = $value;
0 ignored issues
show
Documentation Bug introduced by
It seems like $value can also be of type string. However, the property $_selfclosing is declared as type boolean. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
351
    }
352
353
    /**
354
     * Specifies whether attributes need to have a value
355
     * or if they can be shorthand on this `XMLElement`.
356
     *
357
     * @param bool|string $value (optional)
358
     *  Defaults to true
359
     */
360
    public function setAllowEmptyAttributes($value = true)
0 ignored issues
show
Coding Style introduced by
Incorrect spacing between argument "$value" and equals sign; expected 0 but found 1
Loading history...
Coding Style introduced by
Incorrect spacing between default value and equals sign for argument "$value"; expected 0 but found 1
Loading history...
361
    {
362
        $this->_allowEmptyAttributes = $value;
0 ignored issues
show
Documentation Bug introduced by
It seems like $value can also be of type string. However, the property $_allowEmptyAttributes is declared as type boolean. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
363
    }
364
365
    /**
366
     * Sets the name of this `XMLElement`, ie. 'p' => <p />
367
     *
368
     * @since Symphony 2.3.2
369
     * @param string $name
370
     *  The name of the `XMLElement`, 'p'.
371
     * @param boolean $createHandle
372
     *  Whether this function should convert the `$name` to a handle. Defaults to
373
     *  `false`.
374
     */
375
    public function setName($name, $createHandle = false)
0 ignored issues
show
Coding Style introduced by
Incorrect spacing between argument "$createHandle" and equals sign; expected 0 but found 1
Loading history...
Coding Style introduced by
Incorrect spacing between default value and equals sign for argument "$createHandle"; expected 0 but found 1
Loading history...
376
    {
377
        $this->_name = ($createHandle) ? Lang::createHandle($name) : $name;
378
    }
379
380
    /**
381
     * Sets the value of the `XMLElement`. Checks to see
382
     * whether the value should be prepended or appended
383
     * to the children.
384
     *
385
     * @param string|XMLElement|array $value
386
     *  Defaults to true.
387
     */
388
    public function setValue($value)
389
    {
390
        if (is_array($value)) {
391
            $value = implode(', ', $value);
392
        }
393
394
        if (!is_null($value)) {
395
            $this->_value = $value;
0 ignored issues
show
Documentation Bug introduced by
It seems like $value can also be of type XMLElement. However, the property $_value is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
396
            $this->appendChild($value);
397
        }
398
    }
399
400
    /**
401
     * This function will remove all text attributes from the `XMLElement` node
402
     * and replace them with the given value.
403
     *
404
     * @since Symphony 2.4
405
     * @param string|XMLElement|array $value
406
     */
407
    public function replaceValue($value)
408
    {
409
        foreach ($this->_children as $i => $child) {
410
            if ($child instanceof XMLElement) {
411
                continue;
412
            }
413
414
            unset($this->_children[$i]);
415
        }
416
417
        $this->setValue($value);
418
    }
419
420
    /**
421
     * Sets an attribute
422
     *
423
     * @param string $name
424
     *  The name of the attribute
425
     * @param string $value
426
     *  The value of the attribute
427
     */
428
    public function setAttribute($name, $value)
429
    {
430
        $this->_attributes[$name] = $value;
431
    }
432
433
    /**
434
     * A convenience method to quickly add multiple attributes to
435
     * an `XMLElement`
436
     *
437
     * @param array $attributes
438
     *  Associative array with the key being the name and
439
     *  the value being the value of the attribute.
440
     */
441
    public function setAttributeArray(array $attributes = null)
0 ignored issues
show
Coding Style introduced by
Incorrect spacing between argument "$attributes" and equals sign; expected 0 but found 1
Loading history...
Coding Style introduced by
Incorrect spacing between default value and equals sign for argument "$attributes"; expected 0 but found 1
Loading history...
442
    {
443
        if (!is_array($attributes) || empty($attributes)) {
444
            return;
445
        }
446
447
        foreach ($attributes as $name => $value) {
448
            $this->setAttribute($name, $value);
449
        }
450
    }
451
452
    /**
453
     * A convenience method that encapsulate validation of a child node.
454
     * This should prevent generate errors by catching them earlier.
455
     *
456
     * @since Symphony 2.5.0
457
     * @param XMLElement $child
458
     *  The child to validate
459
     *
460
     */
461
    private function validateChild($child)
462
    {
463
        if ($this === $child) {
464
            throw new Exception(__('Can not add the element itself as one of its child'));
465
        }
466
    }
467
468
    /**
469
     * This function expects an array of `XMLElement` that will completely
470
     * replace the contents of `$this->_children`. Take care when using
471
     * this function.
472
     *
473
     * @since Symphony 2.2.2
474
     * @param array $children
475
     *  An array of XMLElement's to act as the children for the current
476
     *  XMLElement instance
477
     * @return boolean
478
     */
479
    public function setChildren(array $children = null)
0 ignored issues
show
Coding Style introduced by
Incorrect spacing between argument "$children" and equals sign; expected 0 but found 1
Loading history...
Coding Style introduced by
Incorrect spacing between default value and equals sign for argument "$children"; expected 0 but found 1
Loading history...
480
    {
481
        foreach ($children as $child) {
482
            $this->validateChild($child);
483
        }
0 ignored issues
show
Coding Style introduced by
No blank line found after control structure
Loading history...
484
        $this->_children = $children;
485
486
        return true;
487
    }
488
489
    /**
490
     * Adds an `XMLElement` to the children array
491
     *
492
     * @param XMLElement|string $child
493
     * @return boolean
494
     */
495
    public function appendChild($child)
496
    {
497
        $this->validateChild($child);
0 ignored issues
show
Bug introduced by
It seems like $child can also be of type string; however, parameter $child of XMLElement::validateChild() does only seem to accept XMLElement, 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

497
        $this->validateChild(/** @scrutinizer ignore-type */ $child);
Loading history...
498
        $this->_children[] = $child;
499
500
        return true;
501
    }
502
503
    /**
504
     * A convenience method to add children to an `XMLElement`
505
     * quickly.
506
     *
507
     * @param array $children
508
     */
509
    public function appendChildArray(array $children = null)
0 ignored issues
show
Coding Style introduced by
Incorrect spacing between argument "$children" and equals sign; expected 0 but found 1
Loading history...
Coding Style introduced by
Incorrect spacing between default value and equals sign for argument "$children"; expected 0 but found 1
Loading history...
510
    {
511
        if (is_array($children) && !empty($children)) {
512
            foreach ($children as $child) {
513
                $this->appendChild($child);
514
            }
515
        }
516
    }
517
518
    /**
519
     * Adds an `XMLElement` to the start of the children
520
     * array, this will mean it is output before any other
521
     * children when the `XMLElement` is generated
522
     *
523
     * @param XMLElement $child
524
     */
525
    public function prependChild(XMLElement $child)
526
    {
527
        array_unshift($this->_children, $child);
528
    }
529
530
    /**
531
     * A convenience method to quickly add a CSS class to this `XMLElement`'s
532
     * existing class attribute. If the attribute does not exist, it will
533
     * be created.
534
     *
535
     * @since Symphony 2.2.2
536
     * @param string $class
537
     *  The CSS classname to add to this `XMLElement`
538
     */
539
    public function addClass($class)
540
    {
541
        $current = preg_split('%\s+%', $this->getAttribute('class'), 0, PREG_SPLIT_NO_EMPTY);
542
        $added = preg_split('%\s+%', $class, 0, PREG_SPLIT_NO_EMPTY);
543
        $current = array_merge($current, $added);
0 ignored issues
show
Bug introduced by
It seems like $current can also be of type false; however, parameter $array1 of array_merge() does only seem to accept array, 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

543
        $current = array_merge(/** @scrutinizer ignore-type */ $current, $added);
Loading history...
Bug introduced by
It seems like $added can also be of type false; however, parameter $array2 of array_merge() does only seem to accept array|null, 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

543
        $current = array_merge($current, /** @scrutinizer ignore-type */ $added);
Loading history...
544
        $classes = implode(' ', $current);
545
546
        $this->setAttribute('class', $classes);
547
    }
548
549
    /**
550
     * A convenience method to quickly remove a CSS class from an
551
     * `XMLElement`'s existing class attribute. If the attribute does not
552
     * exist, this method will do nothing.
553
     *
554
     * @since Symphony 2.2.2
555
     * @param string $class
556
     *  The CSS classname to remove from this `XMLElement`
557
     */
558
    public function removeClass($class)
559
    {
560
        $classes = preg_split('%\s+%', $this->getAttribute('class'), 0, PREG_SPLIT_NO_EMPTY);
561
        $removed = preg_split('%\s+%', $class, 0, PREG_SPLIT_NO_EMPTY);
562
        $classes = array_diff($classes, $removed);
0 ignored issues
show
Bug introduced by
It seems like $classes can also be of type false; however, parameter $array1 of array_diff() does only seem to accept array, 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

562
        $classes = array_diff(/** @scrutinizer ignore-type */ $classes, $removed);
Loading history...
Bug introduced by
It seems like $removed can also be of type false; however, parameter $array2 of array_diff() does only seem to accept array, 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

562
        $classes = array_diff($classes, /** @scrutinizer ignore-type */ $removed);
Loading history...
563
        $classes = implode(' ', $classes);
564
565
        $this->setAttribute('class', $classes);
566
    }
567
568
    /**
569
     * Returns the number of children this `XMLElement` has.
570
     * @return integer
571
     */
572
    public function getNumberOfChildren()
573
    {
574
        return count($this->_children);
575
    }
576
577
    /**
578
     * Given the position of the child in the `$this->_children`,
579
     * this function will unset the child at that position. This function
580
     * is not reversible. This function does not alter the key's of
581
     * `$this->_children` after removing a child
582
     *
583
     * @since Symphony 2.2.2
584
     * @param integer $index
585
     *  The index of the child to be removed. If the index given is negative
586
     *  it will be calculated from the end of `$this->_children`.
587
     * @return boolean
588
     *  true if child was successfully removed, false otherwise.
589
     */
590
    public function removeChildAt($index)
591
    {
592
        if (!is_numeric($index)) {
0 ignored issues
show
introduced by
The condition is_numeric($index) is always true.
Loading history...
593
            return false;
594
        }
595
596
        $index = $this->getRealIndex($index);
597
598
        if (!isset($this->_children[$index])) {
599
            return false;
600
        }
601
602
        unset($this->_children[$index]);
603
604
        return true;
605
    }
606
607
    /**
608
     * Given a desired index, and an `XMLElement`, this function will insert
609
     * the child at that index in `$this->_children` shuffling all children
610
     * greater than `$index` down one. If the `$index` given is greater then
611
     * the number of children for this `XMLElement`, the `$child` will be
612
     * appended to the current `$this->_children` array.
613
     *
614
     * @since Symphony 2.2.2
615
     * @param integer $index
616
     *  The index where the `$child` should be inserted. If this is negative
617
     *  the index will be calculated from the end of `$this->_children`.
618
     * @param XMLElement $child
619
     *  The XMLElement to insert at the desired `$index`
620
     * @return boolean
621
     */
622
    public function insertChildAt($index, XMLElement $child = null)
0 ignored issues
show
Coding Style introduced by
Incorrect spacing between argument "$child" and equals sign; expected 0 but found 1
Loading history...
Coding Style introduced by
Incorrect spacing between default value and equals sign for argument "$child"; expected 0 but found 1
Loading history...
623
    {
624
        if (!is_numeric($index)) {
0 ignored issues
show
introduced by
The condition is_numeric($index) is always true.
Loading history...
625
            return false;
626
        }
627
        
628
        $this->validateChild($child);
629
630
        if ($index >= $this->getNumberOfChildren()) {
631
            return $this->appendChild($child);
632
        }
633
634
        $start = array_slice($this->_children, 0, $index);
635
        $end = array_slice($this->_children, $index);
636
637
        $merge = array_merge($start, array(
638
            $index => $child
639
        ), $end);
640
641
        return $this->setChildren($merge);
642
    }
643
644
    /**
645
     * Given the position of the child to replace, and an `XMLElement`
646
     * of the replacement child, this function will replace one child
647
     * with another
648
     *
649
     * @since Symphony 2.2.2
650
     * @param integer $index
651
     *  The index of the child to be replaced. If the index given is negative
652
     *  it will be calculated from the end of `$this->_children`.
653
     * @param XMLElement $child
654
     *  An XMLElement of the new child
655
     * @return boolean
656
     */
657
    public function replaceChildAt($index, XMLElement $child = null)
0 ignored issues
show
Coding Style introduced by
Incorrect spacing between argument "$child" and equals sign; expected 0 but found 1
Loading history...
Coding Style introduced by
Incorrect spacing between default value and equals sign for argument "$child"; expected 0 but found 1
Loading history...
658
    {
659
        if (!is_numeric($index)) {
0 ignored issues
show
introduced by
The condition is_numeric($index) is always true.
Loading history...
660
            return false;
661
        }
662
663
        $this->validateChild($child);
664
665
        $index = $this->getRealIndex($index);
666
667
        if (!isset($this->_children[$index])) {
668
            return false;
669
        }
670
671
        $this->_children[$index] = $child;
672
673
        return true;
674
    }
675
676
    /**
677
     * Given an `$index`, return the real index in `$this->_children`
678
     * depending on if the value is negative or not. Negative values
679
     * will work from the end of an array.
680
     *
681
     * @since Symphony 2.2.2
682
     * @param integer $index
683
     *  Positive indexes are returned as is, negative indexes are deducted
684
     *  from the end of `$this->_children`
685
     * @return integer
686
     */
687
    private function getRealIndex($index)
688
    {
689
        if ($index >= 0) {
690
            return $index;
691
        }
692
693
        return $this->getNumberOfChildren() + $index;
694
    }
695
696
    /**
697
     * This function strips characters that are not allowed in XML
698
     *
699
     * @since Symphony 2.3
700
     * @link http://www.w3.org/TR/xml/#charsets
701
     * @link http://www.phpedit.net/snippet/Remove-Invalid-XML-Characters
702
     * @param string $value
703
     * @return string
704
     */
705
    public static function stripInvalidXMLCharacters($value)
706
    {
707
        if (Lang::isUnicodeCompiled()) {
708
            return preg_replace('/[^\x{0009}\x{000a}\x{000d}\x{0020}-\x{D7FF}\x{E000}-\x{FFFD}]+/u', ' ', $value);
709
        } else {
710
            $ret = '';
711
712
            if (empty($value)) {
713
                return $ret;
714
            }
715
716
            $length = strlen($value);
717
718
            for ($i=0; $i < $length; $i++) {
719
                $current = ord($value{$i});
720
                if (($current == 0x9) ||
721
                    ($current == 0xA) ||
722
                    ($current == 0xD) ||
723
                    (($current >= 0x20) && ($current <= 0xD7FF)) ||
724
                    (($current >= 0xE000) && ($current <= 0xFFFD)) ||
725
                    (($current >= 0x10000) && ($current <= 0x10FFFF))) {
726
                    $ret .= chr($current);
727
                }
728
            }
729
730
            return $ret;
731
        }
732
    }
733
734
    /**
735
     * This function will turn the `XMLElement` into a string
736
     * representing the element as it would appear in the markup.
737
     * The result is valid XML.
738
     *
739
     * @param boolean $indent
740
     *  Defaults to false
741
     * @param integer $tab_depth
742
     *  Defaults to 0, indicates the number of tabs (\t) that this
743
     *  element should be indented by in the output string
744
     * @param boolean $has_parent
745
     *  Defaults to false, set to true when the children are being
746
     *  generated. Only the parent will output an XML declaration
747
     *  if `$this->_includeHeader` is set to true.
748
     * @return string
749
     */
750
    public function generate($indent = false, $tab_depth = 0, $has_parent = false)
0 ignored issues
show
Coding Style introduced by
Incorrect spacing between argument "$indent" and equals sign; expected 0 but found 1
Loading history...
Coding Style introduced by
Incorrect spacing between default value and equals sign for argument "$indent"; expected 0 but found 1
Loading history...
Coding Style introduced by
Incorrect spacing between argument "$tab_depth" and equals sign; expected 0 but found 1
Loading history...
Coding Style introduced by
Incorrect spacing between default value and equals sign for argument "$tab_depth"; expected 0 but found 1
Loading history...
Coding Style introduced by
Incorrect spacing between argument "$has_parent" and equals sign; expected 0 but found 1
Loading history...
Coding Style introduced by
Incorrect spacing between default value and equals sign for argument "$has_parent"; expected 0 but found 1
Loading history...
751
    {
752
        $result = null;
753
        $newline = ($indent ? PHP_EOL : null);
0 ignored issues
show
Coding Style introduced by
Inline shorthand IF statement requires brackets around comparison
Loading history...
754
755
        if (!$has_parent) {
756
            if ($this->_includeHeader) {
757
                $result .= sprintf(
758
                    '<?xml version="%s" encoding="%s" ?>%s',
759
                    $this->_version,
760
                    $this->_encoding,
761
                    $newline
762
                );
763
            }
764
765
            if ($this->_dtd) {
766
                $result .= $this->_dtd . $newline;
767
            }
768
769
            if (is_array($this->_processingInstructions) && !empty($this->_processingInstructions)) {
770
                $result .= implode(PHP_EOL, $this->_processingInstructions);
771
            }
772
        }
773
774
        $result .= ($indent ? str_repeat("\t", $tab_depth) : null) . '<' . $this->getName();
0 ignored issues
show
Coding Style introduced by
Inline shorthand IF statement requires brackets around comparison
Loading history...
775
776
        $attributes = $this->getAttributes();
777
778
        if (!empty($attributes)) {
779
            foreach ($attributes as $attribute => $value) {
780
                $length = strlen($value);
781
                if ($length !== 0 || $length === 0 && $this->_allowEmptyAttributes) {
782
                    $result .= sprintf(' %s="%s"', $attribute, $value);
783
                }
784
            }
785
        }
786
787
        $value = $this->getValue();
788
        $added_newline = false;
789
790
        if ($this->getNumberOfChildren() > 0 || strlen($value) !== 0 || !$this->_selfclosing) {
791
            $result .= '>';
792
793
            foreach ($this->_children as $i => $child) {
794
                if (!($child instanceof XMLElement)) {
795
                    $result .= $child;
796
                } else {
797
                    if ($added_newline === false) {
798
                        $added_newline = true;
799
                        $result .= $newline;
800
                    }
801
802
                    $child->setElementStyle($this->_elementStyle);
803
                    $result .= $child->generate($indent, $tab_depth + 1, true);
804
                }
805
            }
806
807
            $result .= sprintf(
808
                "%s</%s>%s",
0 ignored issues
show
Coding Style Comprehensibility introduced by
The string literal %s</%s>%s does not require double quotes, as per coding-style, please use single quotes.

PHP provides two ways to mark string literals. Either with single quotes 'literal' or with double quotes "literal". The difference between these is that string literals in double quotes may contain variables with are evaluated at run-time as well as escape sequences.

String literals in single quotes on the other hand are evaluated very literally and the only two characters that needs escaping in the literal are the single quote itself (\') and the backslash (\\). Every other character is displayed as is.

Double quoted string literals may contain other variables or more complex escape sequences.

<?php

$singleQuoted = 'Value';
$doubleQuoted = "\tSingle is $singleQuoted";

print $doubleQuoted;

will print an indented: Single is Value

If your string literal does not contain variables or escape sequences, it should be defined using single quotes to make that fact clear.

For more information on PHP string literals and available escape sequences see the PHP core documentation.

Loading history...
809
                ($indent && $added_newline ? str_repeat("\t", $tab_depth) : null),
0 ignored issues
show
Coding Style introduced by
Inline shorthand IF statement requires brackets around comparison
Loading history...
810
                $this->getName(),
811
                $newline
812
            );
813
814
            // Empty elements:
815
        } else {
816
            if ($this->_elementStyle === 'xml') {
817
                $result .= ' />';
818
            } elseif (in_array($this->_name, XMLElement::$no_end_tags) || (substr($this->getName(), 0, 3) === '!--')) {
819
                $result .= '>';
820
            } else {
821
                $result .= sprintf("></%s>", $this->getName());
0 ignored issues
show
Coding Style Comprehensibility introduced by
The string literal ></%s> does not require double quotes, as per coding-style, please use single quotes.

PHP provides two ways to mark string literals. Either with single quotes 'literal' or with double quotes "literal". The difference between these is that string literals in double quotes may contain variables with are evaluated at run-time as well as escape sequences.

String literals in single quotes on the other hand are evaluated very literally and the only two characters that needs escaping in the literal are the single quote itself (\') and the backslash (\\). Every other character is displayed as is.

Double quoted string literals may contain other variables or more complex escape sequences.

<?php

$singleQuoted = 'Value';
$doubleQuoted = "\tSingle is $singleQuoted";

print $doubleQuoted;

will print an indented: Single is Value

If your string literal does not contain variables or escape sequences, it should be defined using single quotes to make that fact clear.

For more information on PHP string literals and available escape sequences see the PHP core documentation.

Loading history...
822
            }
823
824
            $result .= $newline;
825
        }
826
827
        return $result;
828
    }
829
830
    /**
831
     * Given a string of XML, this function will convert it to an `XMLElement`
832
     * object and return the result.
833
     *
834
     * @since Symphony 2.4
835
     * @param string $root_element
836
     * @param string $xml
837
     *  A string of XML
838
     * @return XMLElement
839
     */
840
    public static function convertFromXMLString($root_element, $xml)
841
    {
842
        $doc = new DOMDocument('1.0', 'utf-8');
843
        $doc->loadXML($xml);
844
845
        return self::convertFromDOMDocument($root_element, $doc);
846
    }
847
848
    /**
849
     * Given a `DOMDocument`, this function will convert it to an `XMLElement`
850
     * object and return the result.
851
     *
852
     * @since Symphony 2.4
853
     * @param string $root_element
854
     * @param DOMDOcument $doc
855
     * @return XMLElement
856
     */
857
    public static function convertFromDOMDocument($root_element, DOMDocument $doc)
858
    {
859
        $xpath = new DOMXPath($doc);
860
        $root = new XMLElement($root_element);
861
862
        foreach ($xpath->query('.') as $node) {
863
            self::convertNode($root, $node);
864
        }
865
866
        return $root;
867
    }
868
869
    /**
870
     * This helper function is used by `XMLElement::convertFromDOMDocument`
871
     * to recursively convert `DOMNode` into an `XMLElement` structure
872
     *
873
     * @since Symphony 2.4
874
     * @param XMLElement $root
875
     * @param DOMNOde $node
876
     * @return XMLElement
877
     */
878
    private static function convert(XMLElement $root = null, DOMNode $node)
0 ignored issues
show
Coding Style introduced by
Incorrect spacing between argument "$root" and equals sign; expected 0 but found 1
Loading history...
Coding Style introduced by
Incorrect spacing between default value and equals sign for argument "$root"; expected 0 but found 1
Loading history...
879
    {
880
        $el = new XMLElement($node->tagName);
881
882
        self::convertNode($el, $node);
883
884
        if (is_null($root)) {
885
            return $el;
886
        } else {
887
            $root->appendChild($el);
888
        }
889
    }
890
891
    /**
892
     * Given a DOMNode, this function will help replicate it as an
893
     * XMLElement object
894
     *
895
     * @since Symphony 2.5.2
896
     * @param XMLElement $element
897
     * @param DOMNode $node
898
     */
899
    private static function convertNode(XMLElement $element, DOMNode $node)
900
    {
901
        if ($node->hasAttributes()) {
902
            foreach ($node->attributes as $name => $attrEl) {
903
                $element->setAttribute($name, General::sanitize($attrEl->value));
904
            }
905
        }
906
907
        if ($node->hasChildNodes()) {
908
            foreach ($node->childNodes as $childNode) {
909
                if ($childNode instanceof DOMCdataSection) {
910
                    $element->setValue(General::wrapInCDATA($childNode->data));
911
                } elseif ($childNode instanceof DOMText) {
912
                    if ($childNode->isWhitespaceInElementContent() === false) {
913
                        $element->setValue(General::sanitize($childNode->data));
914
                    }
915
                } elseif ($childNode instanceof DOMElement) {
916
                    self::convert($element, $childNode);
917
                }
918
            }
919
        }
920
    }
921
}
922
923
/**
924
 * Creates a filter that only returns XMLElement items
925
 */
926
class XMLElementChildrenFilter extends FilterIterator
927
{
928
    public function accept()
929
    {
930
        $item = $this->getInnerIterator()->current();
931
932
        return $item instanceof XMLElement;
933
    }
934
}
935