UIInfo::getInformationURL()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace SimpleSAML\SAML2\XML\mdui;
6
7
use DOMElement;
8
use SimpleSAML\Assert\Assert;
9
use SimpleSAML\SAML2\Exception\ArrayValidationException;
10
use SimpleSAML\SAML2\Exception\ProtocolViolationException;
11
use SimpleSAML\SAML2\Utils\XPath;
12
use SimpleSAML\XML\ArrayizableElementInterface;
13
use SimpleSAML\XML\Chunk;
14
use SimpleSAML\XML\Constants as C;
15
use SimpleSAML\XML\Exception\InvalidDOMElementException;
16
use SimpleSAML\XML\ExtendableElementTrait;
17
use SimpleSAML\XML\SchemaValidatableElementInterface;
18
use SimpleSAML\XML\SchemaValidatableElementTrait;
19
use SimpleSAML\XML\SerializableElementInterface;
20
use SimpleSAML\XML\XsNamespace as NS;
21
22
use function array_filter;
23
use function array_key_exists;
24
use function array_keys;
25
use function array_map;
26
use function array_merge;
27
use function array_unique;
28
29
/**
30
 * Class for handling the metadata extensions for login and discovery user interface
31
 *
32
 * @link: http://docs.oasis-open.org/security/saml/Post2.0/sstc-saml-metadata-ui/v1.0/sstc-saml-metadata-ui-v1.0.pdf
33
 * @package simplesamlphp/saml2
34
 */
35
final class UIInfo extends AbstractMduiElement implements
36
    ArrayizableElementInterface,
37
    SchemaValidatableElementInterface
38
{
39
    use ExtendableElementTrait;
40
    use SchemaValidatableElementTrait;
41
42
43
    /** The namespace-attribute for the xs:any element */
44
    public const XS_ANY_ELT_NAMESPACE = NS::OTHER;
45
46
47
    /**
48
     * Create a UIInfo element.
49
     *
50
     * @param \SimpleSAML\SAML2\XML\mdui\DisplayName[] $displayName
51
     * @param \SimpleSAML\SAML2\XML\mdui\Description[] $description
52
     * @param \SimpleSAML\SAML2\XML\mdui\InformationURL[] $informationURL
53
     * @param \SimpleSAML\SAML2\XML\mdui\PrivacyStatementURL[] $privacyStatementURL
54
     * @param \SimpleSAML\SAML2\XML\mdui\Keywords[] $keywords
55
     * @param \SimpleSAML\SAML2\XML\mdui\Logo[] $logo
56
     * @param \SimpleSAML\XML\Chunk[] $children
57
     */
58
    public function __construct(
59
        protected array $displayName = [],
60
        protected array $description = [],
61
        protected array $informationURL = [],
62
        protected array $privacyStatementURL = [],
63
        protected array $keywords = [],
64
        protected array $logo = [],
65
        array $children = [],
66
    ) {
67
        Assert::maxCount($displayName, C::UNBOUNDED_LIMIT);
68
        Assert::allIsInstanceOf($displayName, DisplayName::class);
69
        /**
70
         * 2.1.2:  There MUST NOT be more than one <mdui:DisplayName>,
71
         *         within a given <mdui:UIInfo>, for a given language
72
         */
73
        $this->testLocalizedElements($displayName);
74
75
        Assert::maxCount($description, C::UNBOUNDED_LIMIT);
76
        Assert::allIsInstanceOf($description, Description::class);
77
        /**
78
         * 2.1.3:  There MUST NOT be more than one <mdui:Description>,
79
         *         within a given <mdui:UIInfo>, for a given language
80
         */
81
        $this->testLocalizedElements($description);
82
83
        Assert::maxCount($keywords, C::UNBOUNDED_LIMIT);
84
        Assert::allIsInstanceOf($keywords, Keywords::class);
85
        /**
86
         * 2.1.4:  There MUST NOT be more than one <mdui:Keywords>,
87
         *         within a given <mdui:UIInfo>, for a given language
88
         */
89
        $this->testLocalizedElements($keywords);
90
91
        Assert::maxCount($informationURL, C::UNBOUNDED_LIMIT);
92
        Assert::allIsInstanceOf($informationURL, InformationURL::class);
93
        /**
94
         * 2.1.6:  There MUST NOT be more than one <mdui:InformationURL>,
95
         *         within a given <mdui:UIInfo>, for a given language
96
         */
97
        $this->testLocalizedElements($informationURL);
98
99
        Assert::maxCount($privacyStatementURL, C::UNBOUNDED_LIMIT);
100
        Assert::allIsInstanceOf($privacyStatementURL, PrivacyStatementURL::class);
101
        /**
102
         * 2.1.7:  There MUST NOT be more than one <mdui:PrivacyStatementURL>,
103
         *         within a given <mdui:UIInfo>, for a given language
104
         */
105
        $this->testLocalizedElements($privacyStatementURL);
106
107
        Assert::maxCount($logo, C::UNBOUNDED_LIMIT);
108
        Assert::allIsInstanceOf($logo, Logo::class);
109
110
        $this->setElements($children);
111
    }
112
113
114
    /**
115
     * Collect the value of the Keywords-property
116
     *
117
     * @return \SimpleSAML\SAML2\XML\mdui\Keywords[]
118
     */
119
    public function getKeywords(): array
120
    {
121
        return $this->keywords;
122
    }
123
124
125
    /**
126
     * Add the value to the Keywords-property
127
     *
128
     * @param \SimpleSAML\SAML2\XML\mdui\Keywords $keyword
129
     */
130
    public function addKeyword(Keywords $keyword): void
131
    {
132
        /**
133
         * 2.1.4:  There MUST NOT be more than one <mdui:Keywords>,
134
         *         within a given <mdui:UIInfo>, for a given language
135
         */
136
        $keywords = array_merge($this->keywords, [$keyword]);
137
        $this->testLocalizedElements($keywords);
138
        $this->keywords = $keywords;
139
    }
140
141
142
    /**
143
     * Collect the value of the DisplayName-property
144
     *
145
     * @return \SimpleSAML\SAML2\XML\mdui\DisplayName[]
146
     */
147
    public function getDisplayName(): array
148
    {
149
        return $this->displayName;
150
    }
151
152
153
    /**
154
     * Collect the value of the Description-property
155
     *
156
     * @return \SimpleSAML\SAML2\XML\mdui\Description[]
157
     */
158
    public function getDescription(): array
159
    {
160
        return $this->description;
161
    }
162
163
164
    /**
165
     * Collect the value of the InformationURL-property
166
     * @return \SimpleSAML\SAML2\XML\mdui\InformationURL[]
167
     */
168
    public function getInformationURL(): array
169
    {
170
        return $this->informationURL;
171
    }
172
173
174
    /**
175
     * Collect the value of the PrivacyStatementURL-property
176
     *
177
     * @return \SimpleSAML\SAML2\XML\mdui\PrivacyStatementURL[]
178
     */
179
    public function getPrivacyStatementURL(): array
180
    {
181
        return $this->privacyStatementURL;
182
    }
183
184
185
    /**
186
     * Collect the value of the Logo-property
187
     *
188
     * @return \SimpleSAML\SAML2\XML\mdui\Logo[]
189
     */
190
    public function getLogo(): array
191
    {
192
        return $this->logo;
193
    }
194
195
196
    /**
197
     * Add the value to the Logo-property
198
     *
199
     * @param \SimpleSAML\SAML2\XML\mdui\Logo $logo
200
     */
201
    public function addLogo(Logo $logo): void
202
    {
203
        $this->logo[] = $logo;
204
    }
205
206
207
    /**
208
     * Add the value to the elements-property
209
     *
210
     * @param \SimpleSAML\XML\Chunk $child
211
     */
212
    public function addChild(Chunk $child): void
213
    {
214
        $this->elements[] = $child;
215
    }
216
217
218
    /**
219
     * Test if an object, at the state it's in, would produce an empty XML-element
220
     *
221
     * @return bool
222
     */
223
    public function isEmptyElement(): bool
224
    {
225
        return empty($this->displayName)
226
            && empty($this->description)
227
            && empty($this->informationURL)
228
            && empty($this->privacyStatementURL)
229
            && empty($this->keywords)
230
            && empty($this->logo)
231
            && empty($this->elements);
232
    }
233
234
235
    /**
236
     * Test localized elements for multiple items with the same language
237
     *
238
     * @param (\SimpleSAML\SAML2\XML\md\AbstractLocalizedURL|
239
     *         \SimpleSAML\SAML2\XML\md\AbstractLocalizedName|
240
     *         \SimpleSAML\SAML2\XML\mdui\Keywords)[] $elements
241
     * @return void
242
     */
243
    private function testLocalizedElements(array $elements)
244
    {
245
        if (!empty($elements)) {
246
            $types = array_map('get_class', $elements);
247
            Assert::maxCount(array_unique($types), 1, 'Multiple class types cannot be used.');
248
249
            $languages = array_map(
250
                function ($elt) {
251
                    return $elt->getLanguage();
252
                },
253
                $elements,
254
            );
255
            Assert::uniqueValues(
256
                $languages,
257
                'There MUST NOT be more than one <' . $elements[0]->getQualifiedName() . '>,'
258
                . ' within a given <mdui:UIInfo>, for a given language',
259
                ProtocolViolationException::class,
260
            );
261
        }
262
    }
263
264
265
    /**
266
     * Convert XML into a UIInfo
267
     *
268
     * @param \DOMElement $xml The XML element we should load
269
     * @return static
270
     *
271
     * @throws \SimpleSAML\XML\Exception\InvalidDOMElementException
272
     *   if the qualified name of the supplied element is wrong
273
     */
274
    public static function fromXML(DOMElement $xml): static
275
    {
276
        Assert::same($xml->localName, 'UIInfo', InvalidDOMElementException::class);
277
        Assert::same($xml->namespaceURI, UIInfo::NS, InvalidDOMElementException::class);
278
279
        $DisplayName = DisplayName::getChildrenOfClass($xml);
280
        $Description = Description::getChildrenOfClass($xml);
281
        $InformationURL = InformationURL::getChildrenOfClass($xml);
282
        $PrivacyStatementURL = PrivacyStatementURL::getChildrenOfClass($xml);
283
        $Keywords = Keywords::getChildrenOfClass($xml);
284
        $Logo = Logo::getChildrenOfClass($xml);
285
        $children = [];
286
287
        /** @var \DOMElement $node */
288
        foreach (XPath::xpQuery($xml, './*', XPath::getXPath($xml)) as $node) {
289
            if ($node->namespaceURI !== UIInfo::NS) {
290
                $children[] = new Chunk($node);
291
            }
292
        }
293
294
        return new static(
295
            $DisplayName,
296
            $Description,
297
            $InformationURL,
298
            $PrivacyStatementURL,
299
            $Keywords,
300
            $Logo,
301
            $children,
302
        );
303
    }
304
305
306
    /**
307
     * Convert this UIInfo to XML.
308
     *
309
     * @param \DOMElement|null $parent The element we should append to.
310
     * @return \DOMElement
311
     */
312
    public function toXML(?DOMElement $parent = null): DOMElement
313
    {
314
        $e = $this->instantiateParentElement($parent);
315
316
        foreach ($this->getDisplayName() as $child) {
317
            $child->toXML($e);
318
        }
319
320
        foreach ($this->getDescription() as $child) {
321
            $child->toXML($e);
322
        }
323
324
        foreach ($this->getInformationURL() as $child) {
325
            $child->toXML($e);
326
        }
327
328
        foreach ($this->getPrivacyStatementURL() as $child) {
329
            $child->toXML($e);
330
        }
331
332
        foreach ($this->getKeywords() as $child) {
333
            $child->toXML($e);
334
        }
335
336
        foreach ($this->getLogo() as $child) {
337
            $child->toXML($e);
338
        }
339
340
        /** @var \SimpleSAML\XML\SerializableElementInterface $child */
341
        foreach ($this->getElements() as $child) {
342
            $child->toXML($e);
343
        }
344
345
        return $e;
346
    }
347
348
349
    /**
350
     * Create a class from an array
351
     *
352
     * NOTE: this method does not support passing additional child-objects
353
     *
354
     * @param array $data
355
     * @return static
356
     */
357
    public static function fromArray(array $data): static
358
    {
359
        $data = self::processArrayContents($data);
360
361
        return new static(
362
            $data['DisplayName'] ?? [],
363
            $data['Description'] ?? [],
364
            $data['InformationURL'] ?? [],
365
            $data['PrivacyStatementURL'] ?? [],
366
            $data['Keywords'] ?? [],
367
            $data['Logo'] ?? [],
368
            $data['children'] ?? [],
369
        );
370
    }
371
372
373
    /**
374
     * Validates an array representation of this object and returns the same array with
375
     * rationalized keys (casing) and parsed sub-elements.
376
     *
377
     * @param array $data
378
     * @return array $data
379
     */
380
    private static function processArrayContents(array $data): array
381
    {
382
        $data = array_change_key_case($data, CASE_LOWER);
383
384
        // Make sure the array keys are known for this kind of object
385
        Assert::allOneOf(
386
            array_keys($data),
387
            [
388
                'displayname',
389
                'description',
390
                'informationurl',
391
                'privacystatementurl',
392
                'keywords',
393
                'logo',
394
                'children',
395
            ],
396
            ArrayValidationException::class,
397
        );
398
399
        $retval = [];
400
401
        if (array_key_exists('displayname', $data)) {
402
            foreach ($data['displayname'] as $l => $displayName) {
403
                $retval['DisplayName'][] = DisplayName::fromArray([$l => $displayName]);
404
            }
405
        }
406
407
        if (array_key_exists('description', $data)) {
408
            foreach ($data['description'] as $l => $description) {
409
                $retval['Description'][] = Description::fromArray([$l => $description]);
410
            }
411
        }
412
413
        if (array_key_exists('informationurl', $data)) {
414
            foreach ($data['informationurl'] as $l => $iu) {
415
                $retval['InformationURL'][] = InformationURL::fromArray([$l => $iu]);
416
            }
417
        }
418
419
        if (array_key_exists('privacystatementurl', $data)) {
420
            foreach ($data['privacystatementurl'] as $l => $psu) {
421
                $retval['PrivacyStatementURL'][] = PrivacyStatementURL::fromArray([$l => $psu]);
422
            }
423
        }
424
425
        if (array_key_exists('keywords', $data)) {
426
            foreach ($data['keywords'] as $l => $keywords) {
427
                $retval['Keywords'][] = Keywords::fromArray([$l => $keywords]);
428
            }
429
        }
430
431
        if (array_key_exists('logo', $data)) {
432
            foreach ($data['logo'] as $logo) {
433
                $retval['Logo'][] = Logo::fromArray($logo);
434
            }
435
        }
436
437
        if (array_key_exists('children', $data)) {
438
            Assert::isArray($data['children'], ArrayValidationException::class);
439
            Assert::allIsInstanceOf(
440
                $data['children'],
441
                SerializableElementInterface::class,
442
                ArrayValidationException::class,
443
            );
444
            $retval['children'] = $data['children'];
445
        }
446
447
        return array_filter($retval);
448
    }
449
450
451
    /**
452
     * Create an array from this class
453
     *
454
     * NOTE: this method does not support passing additional child-objects
455
     *
456
     * @return array
457
     */
458
    public function toArray(): array
459
    {
460
        $displayName = [];
461
        foreach ($this->getDisplayName() as $child) {
462
            $displayName = array_merge($displayName, $child->toArray());
463
        }
464
465
        $description = [];
466
        foreach ($this->getDescription() as $child) {
467
            $description = array_merge($description, $child->toArray());
468
        }
469
470
        $infoUrl = [];
471
        foreach ($this->getInformationURL() as $child) {
472
            $infoUrl = array_merge($infoUrl, $child->toArray());
473
        }
474
475
        $privacyUrl = [];
476
        foreach ($this->getPrivacyStatementURL() as $child) {
477
            $privacyUrl = array_merge($privacyUrl, $child->toArray());
478
        }
479
480
        $keywords = [];
481
        foreach ($this->getKeywords() as $child) {
482
            $keywords = array_merge($keywords, $child->toArray());
483
        }
484
485
        $logo = [];
486
        foreach ($this->getLogo() as $child) {
487
            $logo[] = $child->toArray();
488
        }
489
490
        $children = $this->getElements();
491
492
        return [] +
493
            (empty($displayName) ? [] : ['DisplayName' => $displayName]) +
494
            (empty($description) ? [] : ['Description' => $description]) +
495
            (empty($infoUrl) ? [] : ['InformationURL' => $infoUrl]) +
496
            (empty($privacyUrl) ? [] : ['PrivacyStatementURL' => $privacyUrl]) +
497
            (empty($keywords) ? [] : ['Keywords' => $keywords]) +
498
            (empty($logo) ? [] : ['Logo' => $logo]) +
499
            (empty($children) ? [] : ['children' => $children]);
500
    }
501
}
502