AbstractAgenda::getChildNamespacePrefix()   A
last analyzed

Complexity

Conditions 3
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 3

Importance

Changes 0
Metric Value
eloc 4
c 0
b 0
f 0
dl 0
loc 7
ccs 5
cts 5
cp 1
rs 10
cc 3
nc 2
nop 1
crap 3
1
<?php
2
3
/**
4
 * This file is part of riesenia/pohoda package.
5
 *
6
 * Licensed under the MIT License
7
 * (c) RIESENIA.com
8
 */
9
10
declare(strict_types=1);
11
12
namespace Riesenia\Pohoda;
13
14
use Riesenia\Pohoda;
15
use Riesenia\Pohoda\ValueTransformer\ValueTransformerInterface;
16
use SimpleXMLElement;
17
18
/**
19
 * Base class for Pohoda objects.
20
 *
21
 * @method \void setNamespace(string $namespace)
22
 * @method \void setNodeName(string $nodeName)
23
 */
24
abstract class AbstractAgenda
25
{
26
    use Pohoda\Common\OneDirectionalVariablesTrait;
27
    use Pohoda\Common\ResolveOptionsTrait;
28
    use Pohoda\Common\DirectionAsResponseTrait;
29
30
    /** @var array<string, mixed> */
31
    protected array $data = [];
32
33
    /** @var string[] */
34
    protected array $refElements = [];
35
36
    /** @var string[] */
37
    protected array $directionalRefElements = [];
38
39
    /** @var array<string, Common\ElementAttributes> */
40
    protected array $elementsAttributesMapper = [];
41
42
    /** @var Common\OptionsResolver[] */
43
    private static array $resolvers = [];
44
45
    /**
46
     * Construct agenda using provided data.
47
     *
48
     * @param Pohoda\DI\DependenciesFactory $dependenciesFactory
49
     */
50 178
    public function __construct(
51
        protected readonly Pohoda\DI\DependenciesFactory $dependenciesFactory,
52 178
    ) {}
53
54
    /**
55
     * Import root
56
     *
57
     * string for xml node, null for not existing one
58
     *
59
     * @return string|null
60
     */
61 3
    public function getImportRoot(): ?string
62
    {
63 3
        return null;
64
    }
65
66
    /**
67
     * Can read data recursively
68
     *
69
     * @return bool
70
     */
71 1
    public function canImportRecursive(): bool
72
    {
73 1
        return false;
74
    }
75
76
    /**
77
     * Set & resolve data options
78
     * Necessary for late setting when there is more options available
79
     *
80
     * @param array<string, mixed> $data
81
     *
82
     * @return $this
83
     */
84 156
    public function setData(array $data): self
85
    {
86
        // resolve options
87 156
        $this->data = $this->resolveOptions ? $this->resolveOptions($data) : $data;
88
89 156
        return $this;
90
    }
91
92
    /**
93
     * Get XML.
94
     *
95
     * @return SimpleXMLElement
96
     */
97
    abstract public function getXML(): SimpleXMLElement;
98
99
    /**
100
     * Configure options for options resolver.
101
     *
102
     * @param Common\OptionsResolver $resolver
103
     *
104
     * @return void
105
     */
106
    abstract protected function configureOptions(Common\OptionsResolver $resolver): void;
107
108
    /**
109
     * Create XML.
110
     *
111
     * @return SimpleXMLElement
112
     */
113 128
    protected function createXML(): SimpleXMLElement
114
    {
115 128
        $np = $this->dependenciesFactory->getNamespacePaths()->allNamespaces();
116 128
        return new SimpleXMLElement('<?xml version="1.0" encoding="' . $this->dependenciesFactory->getSanitizeEncoding()->getEncoding() . '"?><root ' . \implode(' ', \array_map(function ($k, $v) {
117 128
            return 'xmlns:' . $k . '="' . $v . '"';
118 128
        }, \array_keys($np), \array_values($np))) . '></root>');
119
    }
120
121
    /**
122
     * Get namespace.
123
     *
124
     * @param string $short
125
     *
126
     * @return string
127
     */
128 128
    protected function namespace(string $short): string
129
    {
130 128
        return $this->dependenciesFactory->getNamespacePaths()->namespace($short);
131
    }
132
133
    /**
134
     * Add batch elements.
135
     *
136
     * @param SimpleXMLElement $xml
137
     * @param string[]         $elements
138
     * @param string|null      $namespace
139
     *
140
     * @return void
141
     */
142 117
    protected function addElements(SimpleXMLElement $xml, array $elements, ?string $namespace = null): void
143
    {
144 117
        foreach ($elements as $element) {
145 117
            if (!isset($this->data[$element])) {
146 117
                continue;
147
            }
148
149
            // ref element
150 98
            if (\in_array($element, $this->refElements)) {
151 58
                $this->addRefElement($xml, ($namespace ? $namespace . ':' : '') . $element, $this->data[$element], $namespace);
152 58
                continue;
153
            }
154 96
            if ($this->useOneDirectionalVariables && \in_array($element, $this->directionalRefElements)) {
155
                $this->addRefElement($xml, ($namespace ? $namespace . ':' : '') . $element, $this->data[$element], $namespace);
156
                continue;
157
            }
158
159
            // element attribute
160 96
            if (isset($this->elementsAttributesMapper[$element])) {
161 12
                $attrs = $this->elementsAttributesMapper[$element];
162
163
                // get element
164 12
                $attrElement = $namespace ? $xml->children($namespace, true)->{$attrs->attrElement} : $xml->{$attrs->attrElement};
165
166 12
                $sanitized = $this->sanitize($this->data[$element]);
167 12
                $attrs->attrNamespace ? $attrElement->addAttribute(
168 12
                    $attrs->attrNamespace . ':' . $attrs->attrName,
169 12
                    $sanitized,
170 12
                    $this->namespace($attrs->attrNamespace),
171 12
                )
172 12
                    : $attrElement->addAttribute($attrs->attrName, $sanitized);
173
174 12
                continue;
175
            }
176
177
            // Agenda object
178 96
            if ($this->data[$element] instanceof self) {
179
                // set namespace
180 94
                if ($namespace && \method_exists($this->data[$element], 'setNamespace')) {
181 59
                    $this->data[$element]->setNamespace($namespace);
182
                }
183
184
                // set node name
185 94
                if (\method_exists($this->data[$element], 'setNodeName')) {
186 59
                    $this->data[$element]->setNodeName($element);
187
                }
188
189 94
                $this->appendNode($xml, $this->data[$element]->getXML());
190
191 94
                continue;
192
            }
193
194
            // array of Agenda objects
195 91
            if (\is_array($this->data[$element])) {
196 36
                $child = $namespace ? $xml->addChild($namespace . ':' . $element, '', $this->namespace($namespace)) : $xml->addChild($element);
197
198 36
                foreach ($this->data[$element] as $node) {
199 36
                    $this->appendNode($child, $node->getXML());
200
                }
201
202 36
                continue;
203
            }
204
205 91
            $sanitized = $this->sanitize($this->data[$element]);
206 91
            $namespace ? $xml->addChild($namespace . ':' . $element, $sanitized, $this->namespace($namespace)) : $xml->addChild($element, $sanitized);
207
        }
208
    }
209
210
    /**
211
     * Add ref element.
212
     *
213
     * @param SimpleXMLElement $xml
214
     * @param string           $name
215
     * @param mixed            $value
216
     * @param string|null      $namespace
217
     *
218
     * @return SimpleXMLElement
219
     */
220 66
    protected function addRefElement(SimpleXMLElement $xml, string $name, mixed $value, ?string $namespace = null): SimpleXMLElement
221
    {
222 66
        $node = $namespace ? $xml->addChild($name, '', $this->namespace($namespace)) : $xml->addChild($name);
223
224 66
        if (!\is_array($value)) {
225 39
            $value = ['ids' => $value];
226
        }
227
228 66
        foreach ($value as $key => $value1) {
229 66
            if (\is_array($value1)) {
230 2
                if (array_is_list($value1)) {
231 1
                    foreach ($value1 as $value2) {
232 1
                        $node->addChild($namespace . ':' . $key, $this->sanitize($value2), $this->namespace(strval($namespace)));
233
                    }
234
                } else {
235 1
                    $node = $node->addChild($namespace . ':' . $key, '', $this->namespace(strval($namespace)));
236
237 2
                    foreach ($value1 as $key2 => $value2) {
238 1
                        $node->addChild('typ:' . $key2, $this->sanitize($value2), $this->namespace('typ'));
239
                    }
240
                }
241
            } else {
242 64
                $node->addChild('typ:' . $key, $this->sanitize($value1), $this->namespace('typ'));
243
            }
244
        }
245
246 66
        return $node;
247
    }
248
249
    /**
250
     * Sanitize value to XML.
251
     *
252
     * @param mixed $value
253
     *
254
     * @return string
255
     */
256 95
    protected function sanitize(mixed $value): string
257
    {
258 95
        $sanitizeEncoding = $this->dependenciesFactory->getSanitizeEncoding();
259 95
        $sanitizeEncoding->listingWithEncoding();
260
261 95
        return \htmlspecialchars(
262 95
            \array_reduce(
263 95
                $sanitizeEncoding->getListing()->getTransformers(),
264 95
                function (string $value, ValueTransformerInterface $transformer): string {
265 5
                    return $transformer->transform($value);
266 95
                },
267 95
                strval($value),
268 95
            ),
269 95
        );
270
    }
271
272
    /**
273
     * Append SimpleXMLElement to another SimpleXMLElement.
274
     *
275
     * @param SimpleXMLElement $xml
276
     * @param SimpleXMLElement $node
277
     *
278
     * @return void
279
     */
280 96
    protected function appendNode(SimpleXMLElement $xml, SimpleXMLElement $node): void
281
    {
282 96
        $dom = \dom_import_simplexml($xml);
283 96
        $dom2 = \dom_import_simplexml($node);
284
285 96
        if (!$dom->ownerDocument) {
286
            // @codeCoverageIgnoreStart
287
            throw new \InvalidArgumentException('Invalid XML.');
288
        }
289
        // @codeCoverageIgnoreEnd
290
291 96
        $dom->appendChild($dom->ownerDocument->importNode($dom2, true));
292
    }
293
294
    /**
295
     * Resolve options.
296
     *
297
     * @param array<string,mixed> $data
298
     *
299
     * @return array<string,mixed>
300
     */
301 151
    protected function resolveOptions(array $data): array
302
    {
303 151
        $class = \get_class($this);
304 151
        $opt = '_' . intval($this->useOneDirectionalVariables);
305
306 151
        if (!isset(self::$resolvers[$class . $opt])) {
307 67
            self::$resolvers[$class . $opt] = new Common\OptionsResolver();
308 67
            $this->configureOptions(self::$resolvers[$class . $opt]);
309
        }
310
311 151
        return self::$resolvers[$class . $opt]->resolve($data);
312
    }
313
314
    /**
315
     * Change key in entry to different one in accordance with import root config
316
     *
317
     * @param string $defaultKey
318
     *
319
     * @return string
320
     */
321 46
    protected function getChildKey(string $defaultKey): string
322
    {
323 46
        if (!empty($this->getImportRoot()) && $this->directionAsResponse) {
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->getImportRoot() targeting Riesenia\Pohoda\AbstractAgenda::getImportRoot() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
324 1
            return $this->getImportRoot();
325
        }
326 45
        return $defaultKey;
327
    }
328
329
    /**
330
     * Change namespace prefix to different one in accordance with import root config
331
     *
332
     * @param string $defaultPrefix
333
     *
334
     * @return string
335
     */
336 46
    protected function getChildNamespacePrefix(string $defaultPrefix): string
337
    {
338 46
        if (!empty($this->getImportRoot()) && $this->directionAsResponse) {
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->getImportRoot() targeting Riesenia\Pohoda\AbstractAgenda::getImportRoot() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
339 1
            list($prefix, ) = explode(':', $this->getImportRoot());
340 1
            return $prefix;
341
        }
342 45
        return $defaultPrefix;
343
    }
344
345
}
346