Passed
Pull Request — master (#374)
by Tim
02:36
created

UIInfo   C

Complexity

Total Complexity 57

Size/Duplication

Total Lines 458
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 158
dl 0
loc 458
rs 5.04
c 0
b 0
f 0
wmc 57

17 Methods

Rating   Name   Duplication   Size   Complexity  
A getDescription() 0 3 1
A fromXML() 0 21 1
A getInformationURL() 0 3 1
B isEmptyElement() 0 9 7
F toArray() 0 42 14
A addKeyword() 0 9 1
A getLogo() 0 3 1
A getDisplayName() 0 3 1
A getPrivacyStatementURL() 0 3 1
A fromArray() 0 12 1
A testLocalizedElements() 0 17 2
A getKeywords() 0 3 1
A addLogo() 0 3 1
A addChild() 0 3 1
C processArrayContents() 0 68 14
B toXML() 0 34 8
A __construct() 0 53 1

How to fix   Complexity   

Complex Class

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

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