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
Pull Request — master (#103)
by
unknown
01:01
created

ArrayToXml::isArrayAllKeySequential()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 16
rs 9.7333
c 0
b 0
f 0
cc 4
nc 4
nop 1
1
<?php
2
3
namespace Spatie\ArrayToXml;
4
5
use DOMElement;
6
use DOMDocument;
7
use DOMException;
8
9
class ArrayToXml
10
{
11
    /**
12
     * The root DOM Document.
13
     *
14
     * @var DOMDocument
15
     */
16
    protected $document;
17
18
    /**
19
     * Set to enable replacing space with underscore.
20
     *
21
     * @var bool
22
     */
23
    protected $replaceSpacesByUnderScoresInKeyNames = true;
24
25
    /**
26
     * Prefix for the tags with numeric names.
27
     *
28
     * @var string
29
     */
30
    protected $numericTagNamePrefix = 'numeric_';
31
32
    /**
33
     * Construct a new instance.
34
     *
35
     * @param string[] $array
36
     * @param string|array $rootElement
37
     * @param bool $replaceSpacesByUnderScoresInKeyNames
38
     * @param string $xmlEncoding
39
     * @param string $xmlVersion
40
     * @param array $domProperties
41
     *
42
     * @throws DOMException
43
     */
44
    public function __construct(array $array, $rootElement = '', $replaceSpacesByUnderScoresInKeyNames = true, $xmlEncoding = null, $xmlVersion = '1.0', $domProperties = [])
45
    {
46
        $this->document = new DOMDocument($xmlVersion, $xmlEncoding);
47
48
        if (! empty($domProperties)) {
49
            $this->setDomProperties($domProperties);
50
        }
51
52
        $this->replaceSpacesByUnderScoresInKeyNames = $replaceSpacesByUnderScoresInKeyNames;
53
54
        if ($this->isArrayAllKeySequential($array) && ! empty($array)) {
55
            throw new DOMException('Invalid Character Error');
56
        }
57
58
        $root = $this->createRootElement($rootElement);
59
60
        $this->document->appendChild($root);
61
62
        $this->convertElement($root, $array);
63
    }
64
65
    public function setNumericTagNamePrefix(string $prefix)
66
    {
67
        $this->numericTagNamePrefix = $prefix;
68
    }
69
70
    /**
71
     * Convert the given array to an xml string.
72
     *
73
     * @param array $array
74
     * @param string $rootElement
75
     * @param bool $replaceSpacesByUnderScoresInKeyNames
76
     * @param null $xmlEncoding
77
     * @param string $xmlVersion
78
     * @param array $domProperties
79
     * @return string
80
     *
81
     * @throws DOMException
82
     */
83
    public static function convert(array $array, $rootElement = '', $replaceSpacesByUnderScoresInKeyNames = true, $xmlEncoding = null, $xmlVersion = '1.0', $domProperties = [])
84
    {
85
        $converter = new static($array, $rootElement, $replaceSpacesByUnderScoresInKeyNames, $xmlEncoding, $xmlVersion, $domProperties);
86
87
        return $converter->toXml();
88
    }
89
90
    /**
91
     * Return as XML.
92
     *
93
     * @return string
94
     */
95
    public function toXml()
96
    {
97
        return $this->document->saveXML();
98
    }
99
100
    /**
101
     * Return as DOM object.
102
     *
103
     * @return DOMDocument
104
     */
105
    public function toDom()
106
    {
107
        return $this->document;
108
    }
109
110
    /**
111
     * Sets dom properties on $this->document.
112
     *
113
     * @param $domProperties
114
     * @throws \Exception
115
     */
116
    public function setDomProperties($domProperties)
117
    {
118
        foreach ($domProperties as $key => $value) {
119
            if (! property_exists($this->document, $key)) {
120
                throw new \Exception($key.' is not a valid property of DOMDocument', 1561114534);
121
            }
122
            $this->document->{$key} = $value;
123
        }
124
    }
125
126
    /**
127
     * Parse individual element.
128
     *
129
     * @param DOMElement $element
130
     * @param string|string[] $value
131
     */
132
    private function convertElement(DOMElement $element, $value)
133
    {
134
        $sequential = $this->isArrayAllKeySequential($value);
135
136
        if (! is_array($value)) {
137
            $value = htmlspecialchars($value);
138
139
            $value = $this->removeControlCharacters($value);
140
141
            $element->nodeValue = $value;
142
143
            return;
144
        }
145
146
        foreach ($value as $key => $data) {
147
            if (! $sequential) {
148
                if (($key === '_attributes') || ($key === '@attributes')) {
149
                    $this->addAttributes($element, $data);
150
                } elseif ((($key === '_value') || ($key === '@value')) && is_string($data)) {
151
                    $element->nodeValue = htmlspecialchars($data);
152
                } elseif ((($key === '_cdata') || ($key === '@cdata')) && is_string($data)) {
153
                    $element->appendChild($this->document->createCDATASection($data));
154
                } elseif ((($key === '_mixed') || ($key === '@mixed')) && is_string($data)) {
155
                    $fragment = $this->document->createDocumentFragment();
156
                    $fragment->appendXML($data);
157
                    $element->appendChild($fragment);
158
                } elseif ($key === '__numeric') {
159
                    $this->addNumericNode($element, $data);
160
                } else {
161
                    $this->addNode($element, $key, $data);
162
                }
163
            } elseif (is_array($data)) {
164
                $this->addCollectionNode($element, $data);
165
            } else {
166
                $this->addSequentialNode($element, $data);
167
            }
168
        }
169
    }
170
171
    /**
172
     * Add node with numeric keys.
173
     *
174
     * @param DOMElement $element
175
     * @param string|string[] $value
176
     */
177
    protected function addNumericNode(DOMElement $element, $value)
178
    {
179
        foreach ($value as $key => $item) {
0 ignored issues
show
Bug introduced by
The expression $value of type string|array<integer,string> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
180
            $this->convertElement($element, [$this->numericTagNamePrefix.$key => $value]);
0 ignored issues
show
Documentation introduced by
array($this->numericTagN...refix . $key => $value) is of type array<string,string|array<integer,string>>, but the function expects a string|array<integer,string>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
181
        }
182
    }
183
184
    /**
185
     * Add node.
186
     *
187
     * @param DOMElement $element
188
     * @param string $key
189
     * @param string|string[] $value
190
     */
191
    protected function addNode(DOMElement $element, $key, $value)
192
    {
193
        if ($this->replaceSpacesByUnderScoresInKeyNames) {
194
            $key = str_replace(' ', '_', $key);
195
        }
196
197
        $child = $this->document->createElement($key);
198
        $element->appendChild($child);
199
        $this->convertElement($child, $value);
200
    }
201
202
    /**
203
     * Add collection node.
204
     *
205
     * @param DOMElement $element
206
     * @param string|string[] $value
207
     *
208
     * @internal param string $key
209
     */
210
    protected function addCollectionNode(DOMElement $element, $value)
211
    {
212
        if ($element->childNodes->length === 0 && $element->attributes->length === 0) {
0 ignored issues
show
Bug introduced by
The property length does not seem to exist in DOMNamedNodeMap.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
213
            $this->convertElement($element, $value);
214
215
            return;
216
        }
217
218
        $child = $this->document->createElement($element->tagName);
219
        $element->parentNode->appendChild($child);
220
        $this->convertElement($child, $value);
221
    }
222
223
    /**
224
     * Add sequential node.
225
     *
226
     * @param DOMElement $element
227
     * @param string|string[] $value
228
     *
229
     * @internal param string $key
230
     */
231
    protected function addSequentialNode(DOMElement $element, $value)
232
    {
233
        if (empty($element->nodeValue)) {
234
            $element->nodeValue = htmlspecialchars($value);
235
236
            return;
237
        }
238
239
        $child = new DOMElement($element->tagName);
240
        $child->nodeValue = htmlspecialchars($value);
241
        $element->parentNode->appendChild($child);
242
    }
243
244
    /**
245
     * Check if array are all sequential.
246
     *
247
     * @param array|string $value
248
     *
249
     * @return bool
250
     */
251
    protected function isArrayAllKeySequential($value)
252
    {
253
        if (! is_array($value)) {
254
            return false;
255
        }
256
257
        if (count($value) <= 0) {
258
            return true;
259
        }
260
261
        if (\key($value) === '__numeric') {
262
            return false;
263
        }
264
265
        return array_unique(array_map('is_int', array_keys($value))) === [true];
266
    }
267
268
    /**
269
     * Add attributes.
270
     *
271
     * @param DOMElement $element
272
     * @param string[] $data
273
     */
274
    protected function addAttributes($element, $data)
275
    {
276
        foreach ($data as $attrKey => $attrVal) {
277
            $element->setAttribute($attrKey, $attrVal);
278
        }
279
    }
280
281
    /**
282
     * Create the root element.
283
     *
284
     * @param  string|array $rootElement
285
     * @return DOMElement
286
     */
287
    protected function createRootElement($rootElement)
288
    {
289
        if (is_string($rootElement)) {
290
            $rootElementName = $rootElement ?: 'root';
291
292
            return $this->document->createElement($rootElementName);
293
        }
294
295
        $rootElementName = $rootElement['rootElementName'] ?? 'root';
296
297
        $element = $this->document->createElement($rootElementName);
298
299
        foreach ($rootElement as $key => $value) {
300
            if ($key !== '_attributes' && $key !== '@attributes') {
301
                continue;
302
            }
303
304
            $this->addAttributes($element, $rootElement[$key]);
305
        }
306
307
        return $element;
308
    }
309
310
    /**
311
     * @param $valuet
312
     * @return string
313
     */
314
    protected function removeControlCharacters($value)
315
    {
316
        return preg_replace('/[\x00-\x09\x0B\x0C\x0E-\x1F\x7F]/', '', $value);
317
    }
318
}
319