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.
Completed
Push — 3.0.x ( 725db6...74e535 )
by Nicolas
04:25
created

XMLElement::copyDOMNode()   B

Complexity

Conditions 9
Paths 14

Size

Total Lines 20
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
eloc 14
c 0
b 0
f 0
nc 14
nop 2
dl 0
loc 20
rs 7.756
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 XML 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 = [
19
        'area',
20
        'base',
21
        'br',
22
        'col',
23
        'hr',
24
        'img',
25
        'input',
26
        'link',
27
        'meta',
28
        'param',
29
    ];
30
31
    /**
32
     * The name of the HTML Element, eg. 'p'
33
     * @var string
34
     */
35
    private $name;
36
37
    /**
38
     * The value of this `XMLElement` as an array or a string
39
     * @var string|array
40
     */
41
    private $value = [];
42
43
    /**
44
     * Any additional attributes can be included in an associative array
45
     * with the key being the name and the value being the value of the
46
     * attribute.
47
     * @var array
48
     */
49
    private $attributes = [];
50
51
    /**
52
     * Children of this `XMLElement`, which will also be `XMLElement`'s
53
     * @var array
54
     */
55
    private $children = [];
56
57
    /**
58
     * The type of element, defaults to 'xml'. Used when determining the style
59
     * of end tag for this element when generated
60
     * @var string
61
     */
62
    private $elementStyle = 'xml';
63
64
    /**
65
     * Specifies whether this HTML element has an closing element, or if
66
     * it self closing. Defaults to `true`.
67
     *  eg. `<p></p>` or `<input />`
68
     * @var boolean
69
     */
70
    private $selfClosing = true;
71
72
    /**
73
     * Specifies whether attributes need to have a value or if they can
74
     * be shorthand. Defaults to `true`. An example of this would be:
75
     *  `<option selected>Value</option>`
76
     * @var boolean
77
     */
78
    private $allowEmptyAttributes = true;
79
80
    /**
81
     * The constructor for the `XMLElement`
82
     *
83
     * @param string $name
84
     *  The name of the `XMLElement`, 'p'.
85
     * @param string|XMLElement $value (optional)
86
     *  The value of this `XMLElement`, it can be a string
87
     *  or another `XMLElement` object.
88
     * @param array $attributes (optional)
89
     *  Any additional attributes can be included in an associative array with
90
     *  the key being the name and the value being the value of the attribute.
91
     *  Attributes set from this array will override existing attributes
92
     *  set by previous params.
93
     * @param boolean $createHandle
94
     *  Whether this function should convert the `$name` to a handle. Defaults to
95
     *  `false`.
96
     */
97
    public function __construct($name, $value = null, array $attributes = [], $createHandle = false)
98
    {
99
        $this->setName($name, $createHandle);
100
        $this->setValue($value);
101
102
        if (is_array($attributes) && !empty($attributes)) {
103
            $this->setAttributeArray($attributes);
104
        }
105
    }
106
107
    /**
108
     * Accessor for `$name`
109
     *
110
     * @return string
111
     */
112
    public function getName()
113
    {
114
        return $this->name;
115
    }
116
117
    /**
118
     * Accessor for `$value`, converted to a string
119
     *
120
     * @return string
121
     */
122
    public function getValue()
123
    {
124
        $value = '';
125
        $values = $this->value;
126
127
        if (!is_array($values)) {
128
            $values = [$values];
129
        }
130
        foreach ($values as $v) {
131
            if ($v instanceof XMLElement) {
132
                $value .= $v->generate();
133
            } elseif ($v) {
134
                $value .= $v;
135
            }
136
        }
137
138
        return $value;
139
    }
140
141
    /**
142
     * Retrieves the value of an attribute by name
143
     *
144
     * @param string $name
145
     * @return string
146
     */
147
    public function getAttribute($name)
148
    {
149
        if (!isset($this->attributes[$name])) {
150
            return null;
151
        }
152
153
        return $this->attributes[$name];
154
    }
155
156
    /**
157
     * Accessor for `$this->attributes`
158
     *
159
     * @return array
160
     */
161
    public function getAttributes()
162
    {
163
        return $this->attributes;
164
    }
165
166
    /**
167
     * Retrieves a child-element by position
168
     *
169
     * @since Symphony 2.3
170
     * @param integer $position
171
     * @return XMLElement
172
     */
173
    public function getChild($position)
174
    {
175
        if (!isset($this->children[$this->getRealIndex($position)])) {
176
            return null;
177
        }
178
179
        return $this->children[$this->getRealIndex($position)];
180
    }
181
182
    /**
183
     * Accessor for `$this->children`
184
     *
185
     * @return array
186
     */
187
    public function getChildren()
188
    {
189
        return $this->children;
190
    }
191
192
    /**
193
     * Accessor for `$this->children`, returning only `XMLElement` children,
194
     * not text nodes.
195
     *
196
     * @return XMLElementChildrenFilter
197
     */
198
    public function getIterator()
199
    {
200
        return new XMLElementChildrenFilter(new ArrayIterator($this->children));
201
    }
202
203
    /**
204
     * Retrieves child-element by name and position. If no child is found,
205
     * `NULL` will be returned.
206
     *
207
     * @since Symphony 2.3
208
     * @param string $name
209
     * @param integer $position
210
     * @return XMLElement
211
     */
212
    public function getChildByName($name, $position)
213
    {
214
        $result = array_values($this->getChildrenByName($name));
215
216
        if (!isset($result[$position])) {
217
            return null;
218
        }
219
220
        return $result[$position];
221
    }
222
223
    /**
224
     * Accessor to return an associative array of all `$this->children`
225
     * whose's name matches the given `$name`. If no children are found,
226
     * an empty array will be returned.
227
     *
228
     * @since Symphony 2.2.2
229
     * @param string $name
230
     * @return array
231
     *  An associative array where the key is the `$index` of the child
232
     *  in `$this->children`
233
     */
234
    public function getChildrenByName($name)
235
    {
236
        $result = [];
237
238
        foreach ($this as $i => $child) {
239
            if ($child->getName() != $name) {
240
                continue;
241
            }
242
243
            $result[$i] = $child;
244
        }
245
246
        return $result;
247
    }
248
249
    /**
250
     * Accessor for `$elementStyle`
251
     *
252
     * @return string
253
     */
254
    public function getElementStyle()
255
    {
256
        return $this->elementStyle;
257
    }
258
259
    /**
260
     * Sets the style of the `XMLElement`. Used when the
261
     * `XMLElement` is being generated to determine whether
262
     * needs to be closed, is self closing or is standalone.
263
     *
264
     * @param string $style
265
     *  If not 'xml', will trigger the
266
     *  XMLElement to be closed by itself or left standalone.
267
     * @return XMLElement
268
     *  The current instance
269
     */
270
    public function setElementStyle($style)
271
    {
272
        $this->elementStyle = $style;
273
        return $this;
274
    }
275
276
    /**
277
     * Sets whether this `XMLElement` is self closing or not.
278
     *
279
     * @param bool $value
280
     * @return XMLElement
281
     *  The current instance
282
     */
283
    public function setSelfClosingTag($value)
284
    {
285
        $this->selfClosing = $value;
286
        return $this;
287
    }
288
289
    /**
290
     * Makes this `XMLElement` to self close.
291
     *
292
     * @since Symphony 3.0.0
293
     * @uses setSelfClosingTag()
294
     * @return XMLElement
295
     *  The current instance
296
     */
297
    public function renderSelfClosingTag()
298
    {
299
        return $this->setSelfClosingTag(true);
300
    }
301
302
    /**
303
     * Specifies whether attributes need to have a value
304
     * or if they can be shorthand on this `XMLElement`.
305
     *
306
     * @param bool $value
307
     * @return XMLElement
308
     *  The current instance
309
     */
310
    public function setAllowEmptyAttributes($value)
311
    {
312
        $this->allowEmptyAttributes = $value;
313
        return $this;
314
    }
315
316
    /**
317
     * Makes this `XMLElement` render empty attributes.
318
     *
319
     * @since Symphony 3.0.0
320
     * @uses setAllowEmptyAttributes()
321
     * @return XMLElement
322
     *  The current instance
323
     */
324
    public function renderEmptyAttributes()
325
    {
326
        return $this->setAllowEmptyAttributes(true);
327
    }
328
329
    /**
330
     * Sets the name of this `XMLElement`, ie. 'p' => <p />
331
     *
332
     * @since Symphony 2.3.2
333
     * @param string $name
334
     *  The name of the `XMLElement`, 'p'.
335
     * @param boolean $createHandle
336
     *  Whether this function should convert the `$name` to a handle.
337
     *  Defaults to `false`.
338
     * @return XMLElement
339
     *  The current instance
340
     */
341
    public function setName($name, $createHandle = false)
342
    {
343
        $this->name = ($createHandle) ? Lang::createHandle($name) : $name;
344
        return $this;
345
    }
346
347
    /**
348
     * Sets and appends the value of the `XMLElement`.
349
     *
350
     * @param string|XMLElement|array $value
351
     * @return XMLElement
352
     *  The current instance
353
     */
354
    public function setValue($value)
355
    {
356
        if (is_array($value)) {
357
            $value = implode(', ', $value);
358
        }
359
360
        if (!is_null($value)) {
361
            $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|array. 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...
362
            $this->appendChild($value);
363
        }
364
365
        return $this;
366
    }
367
368
    /**
369
     * This function will remove all text attributes from the `XMLElement` node
370
     * and replace them with the given value.
371
     *
372
     * @since Symphony 2.4
373
     * @param string|XMLElement|array $value
374
     * @return XMLElement
375
     *  The current instance
376
     */
377
    public function replaceValue($value)
378
    {
379
        foreach ($this->children as $i => $child) {
380
            if ($child instanceof XMLElement) {
381
                continue;
382
            }
383
384
            unset($this->children[$i]);
385
        }
386
387
        $this->setValue($value);
388
389
        return $this;
390
    }
391
392
    /**
393
     * Sets an attribute
394
     *
395
     * @param string $name
396
     *  The name of the attribute
397
     * @param string $value
398
     *  The value of the attribute
399
     * @return XMLElement
400
     *  The current instance
401
     */
402
    public function setAttribute($name, $value)
403
    {
404
        $this->attributes[$name] = $value;
405
        return $this;
406
    }
407
408
    /**
409
     * A convenience method to quickly add multiple attributes to
410
     * an `XMLElement`
411
     *
412
     * @param array $attributes
413
     *  Associative array with the key being the name and
414
     *  the value being the value of the attribute.
415
     * @return XMLElement
416
     *  The current instance
417
     */
418
    public function setAttributeArray(array $attributes)
419
    {
420
        foreach ($attributes as $name => $value) {
421
            $this->setAttribute($name, $value);
422
        }
423
424
        return $this;
425
    }
426
427
    /**
428
     * A convenience method that encapsulate validation of a child node.
429
     * This should prevent generate errors by catching them earlier.
430
     *
431
     * @since Symphony 2.5.0
432
     * @param XMLElement $child
433
     *  The child to validate
434
     * @return XMLElement
435
     *  The current instance
436
     * @throws Exception
437
     *  If the child is not valid
438
     */
439
    protected function validateChild($child)
440
    {
441
        if ($this === $child) {
442
            throw new Exception(__('Can not add the element itself as one of its child'));
443
        } elseif ($child instanceof XMLDocument) {
444
            throw new Exception(__('Can not add an `XMLDocument` object as a child'));
445
        }
446
        return $this;
447
    }
448
449
    /**
450
     * This function expects an array of `XMLElement` that will completely
451
     * replace the contents of `$this->children`. Take care when using
452
     * this function.
453
     *
454
     * @since Symphony 2.2.2
455
     * @uses validateChild()
456
     * @param array $children
457
     *  An array of XMLElement's to act as the children for the current
458
     *  XMLElement instance
459
     * @return XMLElement
460
     *  The current instance
461
     */
462
    public function setChildren(array $children)
463
    {
464
        foreach ($children as $child) {
465
            $this->validateChild($child);
466
        }
467
        $this->children = $children;
468
        return $this;
469
    }
470
471
    /**
472
     * Adds an `XMLElement` to the children array
473
     *
474
     * @uses validateChild()
475
     * @param XMLElement|string $child
476
     * @return XMLElement
477
     *  The current instance
478
     */
479
    public function appendChild($child)
480
    {
481
        $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

481
        $this->validateChild(/** @scrutinizer ignore-type */ $child);
Loading history...
482
        $this->children[] = $child;
483
        return $this;
484
    }
485
486
    /**
487
     * A convenience method to add children to an `XMLElement`
488
     * quickly.
489
     *
490
     * @uses appendChild()
491
     * @param array $children
492
     * @return XMLElement
493
     *  The current instance
494
     */
495
    public function appendChildArray(array $children)
496
    {
497
        foreach ($children as $child) {
498
            $this->appendChild($child);
499
        }
500
        return $this;
501
    }
502
503
    /**
504
     * Adds an `XMLElement` to the start of the children
505
     * array, this will mean it is output before any other
506
     * children when the `XMLElement` is generated
507
     *
508
     * @uses validateChild()
509
     * @param XMLElement $child
510
     * @return XMLElement
511
     *  The current instance
512
     */
513
    public function prependChild(XMLElement $child)
514
    {
515
        $this->validateChild($child);
516
        array_unshift($this->children, $child);
517
        return $this;
518
    }
519
520
    /**
521
     * A convenience method to quickly add a CSS class to this `XMLElement`'s
522
     * existing class attribute. If the attribute does not exist, it will
523
     * be created.
524
     * It also make sure that classes are separated by a single space.
525
     *
526
     * @since Symphony 2.2.2
527
     * @uses setAttribute()
528
     * @param string $class
529
     *  The CSS class name to add to this `XMLElement`
530
     * @return XMLElement
531
     *  The current instance
532
     */
533
    public function addClass($class)
534
    {
535
        $current = preg_split('%\s+%', $this->getAttribute('class'), 0, PREG_SPLIT_NO_EMPTY);
536
        $added = preg_split('%\s+%', $class, 0, PREG_SPLIT_NO_EMPTY);
537
        $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

537
        $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 null|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

537
        $current = array_merge($current, /** @scrutinizer ignore-type */ $added);
Loading history...
538
        $classes = implode(' ', $current);
539
        return $this->setAttribute('class', $classes);
540
    }
541
542
    /**
543
     * A convenience method to quickly remove a CSS class from an
544
     * `XMLElement`'s existing class attribute. If the attribute does not
545
     * exist, this method will do nothing.
546
     * It also make sure that classes are separated by a single space.
547
     *
548
     * @since Symphony 2.2.2
549
     * @uses setAttribute()
550
     * @param string $class
551
     *  The CSS class name to remove from this `XMLElement`
552
     * @return XMLElement
553
     *  The current instance
554
     */
555
    public function removeClass($class)
556
    {
557
        $classes = preg_split('%\s+%', $this->getAttribute('class'), 0, PREG_SPLIT_NO_EMPTY);
558
        $removed = preg_split('%\s+%', $class, 0, PREG_SPLIT_NO_EMPTY);
559
        $classes = array_diff($classes, $removed);
0 ignored issues
show
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

559
        $classes = array_diff($classes, /** @scrutinizer ignore-type */ $removed);
Loading history...
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

559
        $classes = array_diff(/** @scrutinizer ignore-type */ $classes, $removed);
Loading history...
560
        $classes = implode(' ', $classes);
561
        return $this->setAttribute('class', $classes);
562
    }
563
564
    /**
565
     * Returns the number of children this `XMLElement` has.
566
     * @return integer
567
     */
568
    public function getNumberOfChildren()
569
    {
570
        return count($this->children);
571
    }
572
573
    /**
574
     * Given the position of the child in the `$this->children`,
575
     * this function will unset the child at that position. This function
576
     * is not reversible. This function does not alter the key's of
577
     * `$this->children` after removing a child
578
     *
579
     * @since Symphony 2.2.2
580
     * @param integer $index
581
     *  The index of the child to be removed. If the index given is negative
582
     *  it will be calculated from the end of `$this->children`.
583
     * @return XMLElement
584
     *  The current instance
585
     * @throws Exception
586
     *  If the $index is not an integer or the index is not valid.
587
     */
588
    public function removeChildAt($index)
589
    {
590
        General::ensureType([
591
            'index' => ['var' => $index, 'type' => 'int'],
592
        ]);
593
594
        $index = $this->getRealIndex($index);
595
596
        if (!isset($this->children[$index])) {
597
            throw new Exception("Index out of range. No child at index `$index`.");
598
        }
599
600
        unset($this->children[$index]);
601
602
        return $this;
603
    }
604
605
    /**
606
     * Given a desired index, and an `XMLElement`, this function will insert
607
     * the child at that index in `$this->children` shuffling all children
608
     * greater than `$index` down one. If the `$index` given is greater then
609
     * the number of children for this `XMLElement`, the `$child` will be
610
     * appended to the current `$this->children` array.
611
     *
612
     * @since Symphony 2.2.2
613
     * @uses validateChild()
614
     * @uses setChildren()
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 XMLElement
621
     *  The current instance
622
     * @throws Exception
623
     *  If the $index is not an integer.
624
     */
625
    public function insertChildAt($index, XMLElement $child)
626
    {
627
        General::ensureType([
628
            'index' => ['var' => $index, 'type' => 'int'],
629
        ]);
630
        
631
        $this->validateChild($child);
632
633
        if ($index >= $this->getNumberOfChildren()) {
634
            return $this->appendChild($child);
635
        }
636
637
        $start = array_slice($this->children, 0, $index);
638
        $end = array_slice($this->children, $index);
639
640
        $merge = array_merge($start, [$index => $child], $end);
641
642
        return $this->setChildren($merge);
643
    }
644
645
    /**
646
     * Given the position of the child to replace, and an `XMLElement`
647
     * of the replacement child, this function will replace one child
648
     * with another
649
     *
650
     * @since Symphony 2.2.2
651
     * @param integer $index
652
     *  The index of the child to be replaced. If the index given is negative
653
     *  it will be calculated from the end of `$this->children`.
654
     * @param XMLElement $child
655
     *  An XMLElement of the new child
656
     * @return XMLElement
657
     *  The current instance
658
     * @throws Exception
659
     *  If the $index is not an integer or the index is not valid.
660
     */
661
    public function replaceChildAt($index, XMLElement $child)
662
    {
663
        General::ensureType([
664
            'index' => ['var' => $index, 'type' => 'int'],
665
        ]);
666
667
        $this->validateChild($child);
668
669
        $index = $this->getRealIndex($index);
670
671
        if (!isset($this->children[$index])) {
672
            throw new Exception("Index out of range. No child at index `$index`.");
673
        }
674
675
        $this->children[$index] = $child;
676
677
        return $this;
678
    }
679
680
    /**
681
     * Given an `$index`, return the real index in `$this->children`
682
     * depending on if the value is negative or not. Negative values
683
     * will work from the end of an array.
684
     *
685
     * @since Symphony 2.2.2
686
     * @param integer $index
687
     *  Positive indexes are returned as is, negative indexes are deducted
688
     *  from the end of `$this->children`
689
     * @return integer
690
     */
691
    private function getRealIndex($index)
692
    {
693
        if ($index >= 0) {
694
            return $index;
695
        }
696
697
        return $this->getNumberOfChildren() + $index;
698
    }
699
700
    /**
701
     * This function strips characters that are not allowed in XML
702
     *
703
     * @since Symphony 2.3
704
     * @link http://www.w3.org/TR/xml/#charsets
705
     * @link http://www.phpedit.net/snippet/Remove-Invalid-XML-Characters
706
     * @param string $value
707
     * @return string
708
     */
709
    public static function stripInvalidXMLCharacters($value)
710
    {
711
        if (Lang::isUnicodeCompiled()) {
712
            return preg_replace('/[^\x{0009}\x{000a}\x{000d}\x{0020}-\x{D7FF}\x{E000}-\x{FFFD}]+/u', ' ', $value);
713
        } else {
714
            $ret = '';
715
716
            if (empty($value)) {
717
                return $ret;
718
            }
719
720
            $length = strlen($value);
721
722
            for ($i=0; $i < $length; $i++) {
723
                $current = ord($value{$i});
724
                if (($current == 0x9) ||
725
                    ($current == 0xA) ||
726
                    ($current == 0xD) ||
727
                    (($current >= 0x20) && ($current <= 0xD7FF)) ||
728
                    (($current >= 0xE000) && ($current <= 0xFFFD)) ||
729
                    (($current >= 0x10000) && ($current <= 0x10FFFF))) {
730
                    $ret .= chr($current);
731
                }
732
            }
733
734
            return $ret;
735
        }
736
    }
737
738
    /**
739
     * This function will turn the `XMLElement` into a string
740
     * representing the element as it would appear in the markup.
741
     * The result is valid XML.
742
     *
743
     * @param boolean $indent
744
     *  Defaults to false
745
     * @param integer $tabDepth
746
     *  Defaults to 0, indicates the number of tabs (\t) that this
747
     *  element should be indented by in the output string
748
     * @return string
749
     *  The XML string
750
     */
751
    public function generate($indent = false, $tabDepth = 0)
752
    {
753
        $result = null;
754
        $newline = ($indent ? PHP_EOL : null);
755
756
        $result .= ($indent ? str_repeat("\t", $tabDepth) : null) . '<' . $this->getName();
757
758
        foreach ($this->attributes as $attribute => $value) {
759
            if (!empty($value) || $this->allowEmptyAttributes) {
760
                $result .= sprintf(' %s="%s"', $attribute, $value);
761
            }
762
        }
763
764
        $addedNewline = false;
765
766
        if ($this->getNumberOfChildren() > 0 || !empty($this->value) || !$this->selfClosing) {
767
            $result .= '>';
768
769
            foreach ($this->children as $i => $child) {
770
                if (!($child instanceof XMLElement)) {
771
                    $result .= $child;
772
                } else {
773
                    if ($addedNewline === false) {
774
                        $addedNewline = true;
775
                        $result .= $newline;
776
                    }
777
778
                    $child->setElementStyle($this->elementStyle);
779
                    $result .= $child->generate($indent, $tabDepth + 1, true);
0 ignored issues
show
Unused Code introduced by
The call to XMLElement::generate() has too many arguments starting with true. ( Ignorable by Annotation )

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

779
                    $result .= $child->/** @scrutinizer ignore-call */ generate($indent, $tabDepth + 1, true);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
780
                }
781
            }
782
783
            $result .= sprintf(
784
                "%s</%s>%s",
785
                ($indent && $addedNewline ? str_repeat("\t", $tabDepth) : null),
786
                $this->name,
787
                $newline
788
            );
789
790
            // Empty elements:
791
        } else {
792
            if ($this->elementStyle === 'xml') {
793
                $result .= ' />';
794
            } elseif (in_array($this->name, static::$no_end_tags) || (substr($this->name, 0, 3) === '!--')) {
795
                $result .= '>';
796
            } else {
797
                $result .= sprintf("></%s>", $this->name);
798
            }
799
800
            $result .= $newline;
801
        }
802
803
        return $result;
804
    }
805
806
    /**
807
     * Given a string of XML, this function will create a new `XMLElement`
808
     * object, copy all attributes and children and return the result.
809
     *
810
     * @uses fromDOMDocument()
811
     * @since Symphony 3.0.0
812
     * @param string $xml
813
     *  A string of XML
814
     * @return XMLElement
815
     *  The new `XMLElement` derived from `string $xml`.
816
     */
817
    public static function fromXMLString($xml)
818
    {
819
        General::ensureType([
820
            'xml' => ['var' => $xml, 'type' => 'string'],
821
        ]);
822
823
        $doc = new DOMDocument('1.0', 'utf-8');
824
        $doc->loadXML($xml);
825
826
        return static::fromDOMDocument($doc);
827
    }
828
829
    /**
830
     * Given a string of XML, this function will convert it to an `XMLElement`
831
     * object and return the result.
832
     *
833
     * @since Symphony 2.4
834
     * @deprecated @since Symphony 3.0.0
835
     *  Use `fromXMLString()`
836
     * @param string $root_element
837
     * @param string $xml
838
     *  A string of XML
839
     * @return XMLElement
840
     */
841
    public static function convertFromXMLString($root_element, $xml)
842
    {
843
        if (Symphony::Log()) {
844
            Symphony::Log()->pushDeprecateWarningToLog(
845
                'XMLElement::convertFromXMLString()',
846
                'XMLElement::fromXMLString()'
847
            );
848
        }
849
850
        $doc = new DOMDocument('1.0', 'utf-8');
851
        $doc->loadXML($xml);
852
853
        return self::convertFromDOMDocument($root_element, $doc);
0 ignored issues
show
Deprecated Code introduced by
The function XMLElement::convertFromDOMDocument() has been deprecated: @since Symphony 3.0.0 Use `fromDOMDocument()` ( Ignorable by Annotation )

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

853
        return /** @scrutinizer ignore-deprecated */ self::convertFromDOMDocument($root_element, $doc);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
854
    }
855
856
    /**
857
     * Given a `DOMDocument`, this function will create a new `XMLElement`
858
     * object, copy all attributes and children and return the result.
859
     *
860
     * @since Symphony 3.0.0
861
     * @param DOMDocument $doc
862
     *  A DOMDocument to copy from
863
     * @return XMLElement
864
     *  The new `XMLElement` derived from `DOMDocument $doc`.
865
     */
866
    public static function fromDOMDocument(DOMDocument $doc)
867
    {
868
        $root = new XMLElement($doc->documentElement->nodeName);
869
        static::copyDOMNode($root, $doc->documentElement);
870
        return $root;
871
    }
872
873
    /**
874
     * Given a `DOMDocument`, this function will convert it to an `XMLElement`
875
     * object and return the result.
876
     *
877
     * @since Symphony 2.4
878
     * @deprecated @since Symphony 3.0.0
879
     *  Use `fromDOMDocument()`
880
     * @param string $root_element
881
     * @param DOMDocument $doc
882
     * @return XMLElement
883
     */
884
    public static function convertFromDOMDocument($root_element, DOMDocument $doc)
885
    {
886
        if (Symphony::Log()) {
887
            Symphony::Log()->pushDeprecateWarningToLog(
888
                'XMLElement::convertFromDOMDocument()',
889
                'XMLElement::fromDOMDocument()'
890
            );
891
        }
892
893
        $xpath = new DOMXPath($doc);
894
        $root = new XMLElement($root_element);
895
896
        foreach ($xpath->query('.') as $node) {
897
            static::copyDOMNode($root, $node);
898
        }
899
900
        return $root;
901
    }
902
903
    /**
904
     * Given a DOMNode, this function will help replicate it as an
905
     * XMLElement object
906
     *
907
     * @since Symphony 2.5.2
908
     * @param XMLElement $element
909
     * @param DOMNode $node
910
     */
911
    protected static function copyDOMNode(XMLElement $element, DOMNode $node)
912
    {
913
        if ($node->hasAttributes()) {
914
            foreach ($node->attributes as $name => $attrEl) {
915
                $element->setAttribute($name, General::sanitize($attrEl->value));
916
            }
917
        }
918
919
        if ($node->hasChildNodes()) {
920
            foreach ($node->childNodes as $childNode) {
921
                if ($childNode instanceof DOMCdataSection) {
922
                    $element->setValue(General::wrapInCDATA($childNode->data));
923
                } elseif ($childNode instanceof DOMText) {
924
                    if ($childNode->isWhitespaceInElementContent() === false) {
925
                        $element->setValue(General::sanitize($childNode->data));
926
                    }
927
                } elseif ($childNode instanceof DOMElement) {
928
                    $el = new XMLElement($childNode->tagName);
929
                    static::copyDOMNode($el, $childNode);
930
                    $element->appendChild($el);
931
                }
932
            }
933
        }
934
    }
935
}
936