Completed
Push — master ( 3f79e5...1ec99b )
by Kevin
02:42
created

Element::getAllowedAttributes()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 151
Code Lines 135

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 134
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 151
ccs 134
cts 134
cp 1
rs 8.2857
cc 1
eloc 135
nc 1
nop 0
crap 1

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace Groundskeeper\Tokens;
4
5
use Groundskeeper\Configuration;
6
use Groundskeeper\Exceptions\ValidationException;
7
use Psr\Log\LoggerInterface;
8
9
class Element extends AbstractToken implements Cleanable, ContainsChildren, Removable
10
{
11
    const ATTR_CI_ENUM   = 'ci_enu'; // case-insensitive enumeration
12
    const ATTR_JS        = 'cs_jsc';
13
    const ATTR_CI_STRING = 'ci_str'; // case-insensitive string
14
    const ATTR_CS_STRING = 'cs_str'; // case-sensitive string
15
    const ATTR_URI       = 'cs_uri';
16
17
    /** @var array */
18
    protected $attributes;
19
20
    /** @var array[Token] */
21
    protected $children;
22
23
    /** @var string */
24
    private $name;
25
26
    /**
27
     * Constructor
28
     */
29 48
    public function __construct(Configuration $configuration, $name, array $attributes = array())
30
    {
31 48
        parent::__construct(Token::ELEMENT, $configuration);
32
33 48
        $this->attributes = array();
34 48
        foreach ($attributes as $key => $value) {
35 22
            $this->addAttribute($key, $value);
36 48
        }
37
38 48
        $this->children = array();
39 48
        $this->setName($name);
40 48
    }
41
42
    /**
43
     * Getter for 'attributes'.
44
     */
45 3
    public function getAttributes()
46
    {
47 3
        return $this->attributes;
48
    }
49
50
    /**
51
     * Hasser for 'attributes'.
52
     *
53
     * @param string $key
54
     *
55
     * @return boolean True if the attribute is present.
56
     */
57 5
    public function hasAttribute($key)
58
    {
59 5
        return array_key_exists($key, $this->attributes);
60
    }
61
62 24
    public function addAttribute($key, $value)
63
    {
64 24
        $key = trim(strtolower($key));
65 24
        if ($key == '') {
66 2
            throw new \InvalidArgumentException('Invalid empty attribute key.');
67
        }
68
69 23
        $this->attributes[$key] = $value;
70
71 23
        return $this;
72
    }
73
74 1
    public function removeAttribute($key)
75
    {
76 1
        $key = trim(strtolower($key));
77 1
        if (isset($this->attributes[$key])) {
78 1
            unset($this->attributes[$key]);
79
80 1
            return true;
81
        }
82
83 1
        return false;
84
    }
85
86
    /**
87
     * Required by ContainsChildren interface.
88
     */
89 2
    public function getChildren()
90
    {
91 2
        return $this->children;
92
    }
93
94
    /**
95
     * Required by ContainsChildren interface.
96
     */
97 1
    public function hasChild(Token $token)
98
    {
99 1
        return array_search($token, $this->children, true) !== false;
100
    }
101
102
    /**
103
     * Required by ContainsChildren interface.
104
     */
105 39
    public function appendChild(Token $token)
106
    {
107 39
        $token->setParent($this);
108 39
        $this->children[] = $token;
109
110 39
        return $this;
111
    }
112
113
    /**
114
     * Required by ContainsChildren interface.
115
     */
116 3
    public function prependChild(Token $token)
117
    {
118 3
        $token->setParent($this);
119 3
        array_unshift($this->children, $token);
120
121 3
        return $this;
122
    }
123
124
    /**
125
     * Required by the ContainsChildren interface.
126
     */
127 12 View Code Duplication
    public function removeChild(Token $token)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
128
    {
129 12
        $key = array_search($token, $this->children, true);
130 12
        if ($key !== false) {
131 12
            unset($this->children[$key]);
132
133 12
            return true;
134
        }
135
136 1
        return false;
137
    }
138
139
    /**
140
     * Getter for 'name'.
141
     */
142 28
    public function getName()
143
    {
144 28
        return $this->name;
145
    }
146
147
    /**
148
     * Chainable setter for 'name'.
149
     */
150 48
    public function setName($name)
151
    {
152 48
        if (!is_string($name)) {
153 1
            throw new \InvalidArgumentException('Element name must be string type.');
154
        }
155
156 48
        $this->name = trim(strtolower($name));
157
158 48
        return $this;
159
    }
160
161
    /**
162
     * Required by the Cleanable interface.
163
     */
164 35
    public function clean(LoggerInterface $logger = null)
165
    {
166 35
        if ($this->configuration->get('clean-strategy') == Configuration::CLEAN_STRATEGY_NONE) {
167 1
            return true;
168
        }
169
170
        // Remove non-standard attributes.
171 34
        if ($this->configuration->get('clean-strategy') != Configuration::CLEAN_STRATEGY_LENIENT) {
172 34
            foreach ($this->attributes as $name => $value) {
173 15
                $attributeParameters = $this->getAttributeParameters($name);
174 15 View Code Duplication
                if (empty($attributeParameters)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
175 4
                    if ($logger !== null) {
176 4
                        $logger->debug('Removing non-standard attribute "' . $name . '" from ' . $this);
177 4
                    }
178
179 4
                    unset($this->attributes[$name]);
180 4
                }
181 34
            }
182 34
        }
183
184 34
        foreach ($this->attributes as $name => $value) {
185
            // Validate attribute value.
186 15
            $attributeParameters = $this->getAttributeParameters($name);
187 15
            if (empty($attributeParameters)) {
188
                $attributeParameters = array(
189 2
                    'name' => $name,
190 2
                    'regex' => '/\S*/i',
191
                    'valueType' => self::ATTR_CS_STRING
192 2
                );
193 2
            }
194
195
            list($caseSensitivity, $attributeType) =
196 15
                explode('_', $attributeParameters['valueType']);
197
198
            // Handle case-insensitivity.
199
            // Standard is case-insensitive attribute values should be lower case.
200 15
            if ($caseSensitivity == 'ci') {
201 1
                $newValue = strtolower($value);
202 1 View Code Duplication
                if ($newValue !== $value) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
203 1
                    if ($logger !== null) {
204 1
                        $logger->debug('The value for the attribute "' . $name . '" is case-insensitive.  The value has been converted to lower case.  Element: ' . $this);
205 1
                    }
206
207 1
                    $this->attributes[$name] = $newValue;
208 1
                }
209 1
            }
210
211 15
            switch (substr($attributeType, 0, 3)) {
212 15
            case 'enu': // enumeration
213
                /// @todo
214
                break;
215
216 15
            case 'uri': // URI
217
                /// @todo
218 1
                break;
219 15
            }
220 34
        }
221
222 34
        $doCleanResult = $this->doClean($logger);
223 34
        if (!$doCleanResult && $this->configuration->get('clean-strategy') !== Configuration::CLEAN_STRATEGY_LENIENT) {
224 1
            return false;
225
        }
226
227
        // Clean children.
228 34
        return AbstractToken::cleanChildTokens(
229 34
            $this->configuration,
230 34
            $this->children,
231
            $logger
232 34
        );
233
    }
234
235 34
    protected function doClean(LoggerInterface $logger = null)
236
    {
237 34
        return true;
238
    }
239
240 15
    protected function getAllowedAttributes()
241
    {
242
        return array(
243
            // Global Attributes
244 15
            '/^accesskey$/i' => self::ATTR_CS_STRING,
245 15
            '/^class$/i' => self::ATTR_CS_STRING,
246 15
            '/^contenteditable$/i' => self::ATTR_CS_STRING,
247 15
            '/^contextmenu$/i' => self::ATTR_CS_STRING,
248 15
            '/^data-\S/i' => self::ATTR_CS_STRING,
249 15
            '/^dir$/i' => self::ATTR_CI_ENUM . '("ltr","rtl"|"ltr")',
250 15
            '/^draggable$/i' => self::ATTR_CS_STRING,
251 15
            '/^dropzone$/i' => self::ATTR_CS_STRING,
252 15
            '/^hidden$/i' => self::ATTR_CS_STRING,
253 15
            '/^id$/i' => self::ATTR_CS_STRING,
254 15
            '/^is$/i' => self::ATTR_CS_STRING,
255 15
            '/^itemid$/i' => self::ATTR_CS_STRING,
256 15
            '/^itemprop$/i' => self::ATTR_CS_STRING,
257 15
            '/^itemref$/i' => self::ATTR_CS_STRING,
258 15
            '/^itemscope$/i' => self::ATTR_CS_STRING,
259 15
            '/^itemtype$/i' => self::ATTR_CS_STRING,
260 15
            '/^lang$/i' => self::ATTR_CI_STRING,
261 15
            '/^slot$/i' => self::ATTR_CS_STRING,
262 15
            '/^spellcheck$/i' => self::ATTR_CS_STRING,
263 15
            '/^style$/i' => self::ATTR_CS_STRING,
264 15
            '/^tabindex$/i' => self::ATTR_CS_STRING,
265 15
            '/^title$/i' => self::ATTR_CS_STRING,
266 15
            '/^translate$/i' => self::ATTR_CI_ENUM . '("yes","no",""|"yes")',
267
268
            // Event Handler Content Attributes
269
            // https://html.spec.whatwg.org/multipage/webappapis.html#event-handler-content-attributes
270 15
            '/^onabort$/i' => self::ATTR_JS,
271 15
            '/^onautocomplete$/i' => self::ATTR_JS,
272 15
            '/^onautocompleteerror$/i' => self::ATTR_JS,
273 15
            '/^onblur$/i' => self::ATTR_JS,
274 15
            '/^oncancel$/i' => self::ATTR_JS,
275 15
            '/^oncanplay$/i' => self::ATTR_JS,
276 15
            '/^oncanplaythrough$/i' => self::ATTR_JS,
277 15
            '/^onchange$/i' => self::ATTR_JS,
278 15
            '/^onclick$/i' => self::ATTR_JS,
279 15
            '/^onclose$/i' => self::ATTR_JS,
280 15
            '/^oncontextmenu$/i' => self::ATTR_JS,
281 15
            '/^oncuechange$/i' => self::ATTR_JS,
282 15
            '/^ondblclick$/i' => self::ATTR_JS,
283 15
            '/^ondrag$/i' => self::ATTR_JS,
284 15
            '/^ondragend$/i' => self::ATTR_JS,
285 15
            '/^ondragenter$/i' => self::ATTR_JS,
286 15
            '/^ondragexit$/i' => self::ATTR_JS,
287 15
            '/^ondragleave$/i' => self::ATTR_JS,
288 15
            '/^ondragover$/i' => self::ATTR_JS,
289 15
            '/^ondragstart$/i' => self::ATTR_JS,
290 15
            '/^ondrop$/i' => self::ATTR_JS,
291 15
            '/^ondurationchange$/i' => self::ATTR_JS,
292 15
            '/^onemptied$/i' => self::ATTR_JS,
293 15
            '/^onended$/i' => self::ATTR_JS,
294 15
            '/^onerror$/i' => self::ATTR_JS,
295 15
            '/^onfocus$/i' => self::ATTR_JS,
296 15
            '/^oninput$/i' => self::ATTR_JS,
297 15
            '/^oninvalid$/i' => self::ATTR_JS,
298 15
            '/^onkeydown$/i' => self::ATTR_JS,
299 15
            '/^onkeypress$/i' => self::ATTR_JS,
300 15
            '/^onkeyup$/i' => self::ATTR_JS,
301 15
            '/^onload$/i' => self::ATTR_JS,
302 15
            '/^onloadeddata$/i' => self::ATTR_JS,
303 15
            '/^onloadedmetadata$/i' => self::ATTR_JS,
304 15
            '/^onloadstart$/i' => self::ATTR_JS,
305 15
            '/^onmousedown$/i' => self::ATTR_JS,
306 15
            '/^onmouseenter$/i' => self::ATTR_JS,
307 15
            '/^onmouseleave$/i' => self::ATTR_JS,
308 15
            '/^onmousemove$/i' => self::ATTR_JS,
309 15
            '/^onmouseout$/i' => self::ATTR_JS,
310 15
            '/^onmouseover$/i' => self::ATTR_JS,
311 15
            '/^onmouseup$/i' => self::ATTR_JS,
312 15
            '/^onwheel$/i' => self::ATTR_JS,
313 15
            '/^onpause$/i' => self::ATTR_JS,
314 15
            '/^onplay$/i' => self::ATTR_JS,
315 15
            '/^onplaying$/i' => self::ATTR_JS,
316 15
            '/^onprogress$/i' => self::ATTR_JS,
317 15
            '/^onratechange$/i' => self::ATTR_JS,
318 15
            '/^onreset$/i' => self::ATTR_JS,
319 15
            '/^onresize$/i' => self::ATTR_JS,
320 15
            '/^onscroll$/i' => self::ATTR_JS,
321 15
            '/^onseeked$/i' => self::ATTR_JS,
322 15
            '/^onseeking$/i' => self::ATTR_JS,
323 15
            '/^onselect$/i' => self::ATTR_JS,
324 15
            '/^onshow$/i' => self::ATTR_JS,
325 15
            '/^onstalled$/i' => self::ATTR_JS,
326 15
            '/^onsubmit$/i' => self::ATTR_JS,
327 15
            '/^onsuspend$/i' => self::ATTR_JS,
328 15
            '/^ontimeupdate$/i' => self::ATTR_JS,
329 15
            '/^ontoggle$/i' => self::ATTR_JS,
330 15
            '/^onvolumechange$/i' => self::ATTR_JS,
331 15
            '/^onwaiting$/i' => self::ATTR_JS,
332
333
            // WAI-ARIA
334
            // https://w3c.github.io/aria/aria/aria.html
335 15
            '/^role$/i' => self::ATTR_CI_STRING,
336
337
            // ARIA global states and properties
338 15
            '/^aria-atomic$/i' => self::ATTR_CS_STRING,
339 15
            '/^aria-busy$/i' => self::ATTR_CS_STRING,
340 15
            '/^aria-controls$/i' => self::ATTR_CS_STRING,
341 15
            '/^aria-current$/i' => self::ATTR_CS_STRING,
342 15
            '/^aria-describedby$/i' => self::ATTR_CS_STRING,
343 15
            '/^aria-details$/i' => self::ATTR_CS_STRING,
344 15
            '/^aria-disabled$/i' => self::ATTR_CS_STRING,
345 15
            '/^aria-dropeffect$/i' => self::ATTR_CS_STRING,
346 15
            '/^aria-errormessage$/i' => self::ATTR_CS_STRING,
347 15
            '/^aria-flowto$/i' => self::ATTR_CS_STRING,
348 15
            '/^aria-grabbed$/i' => self::ATTR_CS_STRING,
349 15
            '/^aria-haspopup$/i' => self::ATTR_CS_STRING,
350 15
            '/^aria-hidden$/i' => self::ATTR_CS_STRING,
351 15
            '/^aria-invalid$/i' => self::ATTR_CS_STRING,
352 15
            '/^aria-label$/i' => self::ATTR_CS_STRING,
353 15
            '/^aria-labelledby$/i' => self::ATTR_CS_STRING,
354 15
            '/^aria-live$/i' => self::ATTR_CS_STRING,
355 15
            '/^aria-owns$/i' => self::ATTR_CS_STRING,
356 15
            '/^aria-relevant$/i' => self::ATTR_CS_STRING,
357 15
            '/^aria-roledescription$/i' => self::ATTR_CS_STRING,
358
359
            // ARIA widget attributes
360 15
            '/^aria-autocomplete$/i' => self::ATTR_CS_STRING,
361 15
            '/^aria-checked$/i' => self::ATTR_CS_STRING,
362 15
            '/^aria-expanded$/i' => self::ATTR_CS_STRING,
363 15
            '/^aria-level$/i' => self::ATTR_CS_STRING,
364 15
            '/^aria-modal$/i' => self::ATTR_CS_STRING,
365 15
            '/^aria-multiline$/i' => self::ATTR_CS_STRING,
366 15
            '/^aria-multiselectable$/i' => self::ATTR_CS_STRING,
367 15
            '/^aria-orientation$/i' => self::ATTR_CS_STRING,
368 15
            '/^aria-placeholder$/i' => self::ATTR_CS_STRING,
369 15
            '/^aria-pressed$/i' => self::ATTR_CS_STRING,
370 15
            '/^aria-readonly$/i' => self::ATTR_CS_STRING,
371 15
            '/^aria-required$/i' => self::ATTR_CS_STRING,
372 15
            '/^aria-selected$/i' => self::ATTR_CS_STRING,
373 15
            '/^aria-sort$/i' => self::ATTR_CS_STRING,
374 15
            '/^aria-valuemax$/i' => self::ATTR_CS_STRING,
375 15
            '/^aria-valuemin$/i' => self::ATTR_CS_STRING,
376 15
            '/^aria-valuenow$/i' => self::ATTR_CS_STRING,
377 15
            '/^aria-valuetext$/i' => self::ATTR_CS_STRING,
378
379
            // ARIA relationship attributes
380 15
            '/^aria-activedescendant$/i' => self::ATTR_CS_STRING,
381 15
            '/^aria-colcount$/i' => self::ATTR_CS_STRING,
382 15
            '/^aria-colindex$/i' => self::ATTR_CS_STRING,
383 15
            '/^aria-colspan$/i' => self::ATTR_CS_STRING,
384 15
            '/^aria-posinset$/i' => self::ATTR_CS_STRING,
385 15
            '/^aria-rowcount$/i' => self::ATTR_CS_STRING,
386 15
            '/^aria-rowindex$/i' => self::ATTR_CS_STRING,
387 15
            '/^aria-rowspan$/i' => self::ATTR_CS_STRING,
388
            '/^aria-setsize$/i' => self::ATTR_CS_STRING
389 15
        );
390
    }
391
392 15
    protected function getAttributeParameters($name)
393
    {
394 15
        $allowedAttributes = $this->getAllowedAttributes();
395 15
        foreach ($allowedAttributes as $attrRegex => $valueType) {
396 15
            if (preg_match($attrRegex, $name) === 1) {
397
                return array(
398 15
                    'name' => $name,
399 15
                    'regex' => $attrRegex,
400
                    'valueType' => $valueType
401 15
                );
402
            }
403 14
        }
404
405 4
        return array();
406
    }
407
408
    /**
409
     * Required by the Removable interface.
410
     */
411 32 View Code Duplication
    public function remove(LoggerInterface $logger = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
412
    {
413 32
        $hasRemovableElements = $this->configuration->get('element-blacklist') != '';
414 32
        $hasRemovableTypes = $this->configuration->get('type-blacklist') != '';
415 32
        foreach ($this->children as $child) {
416
            // Check types.
417 30
            if ($hasRemovableTypes &&
418 30
                !$this->configuration->isAllowedType($child->getType())) {
419 1
                if ($logger !== null) {
420 1
                    $logger->debug('Removing ' . $child);
421 1
                }
422
423 1
                $this->removeChild($child);
424 1
                continue;
425
            }
426
427
            // Check elements.
428 29
            if ($hasRemovableElements &&
429 29
                $child->getType() == Token::ELEMENT &&
430 29
                !$this->configuration->isAllowedElement($child->getName())) {
431 3
                if ($logger !== null) {
432 3
                    $logger->debug('Removing ' . $child);
433 3
                }
434
435 3
                $this->removeChild($child);
436 3
                continue;
437
            }
438
439
            // Check children.
440 29
            if ($child instanceof Removable) {
441 24
                $child->remove($logger);
442 24
            }
443 32
        }
444 32
    }
445
446
    /**
447
     * Required by the Token interface.
448
     */
449 19
    public function toHtml($prefix, $suffix)
450
    {
451 19
        $output = $this->buildStartTag($prefix, $suffix);
452 19
        if (empty($this->children)) {
453 7
            return $output;
454
        }
455
456 16
        $output .= $this->buildChildrenHtml($prefix, $suffix);
457
458 16
        return $output . $prefix . '</' . $this->name . '>' . $suffix;
459
    }
460
461 38
    protected function buildStartTag($prefix, $suffix, $forceOpen = false)
462
    {
463 38
        $output = $prefix . '<' . $this->name;
464 38
        foreach ($this->attributes as $key => $value) {
465 17
            $output .= ' ' . strtolower($key);
466 17
            if (is_string($value)) {
467
                /// @todo Escape double quotes in value.
468 17
                $output .= '="' . $value . '"';
469 17
            }
470 38
        }
471
472 38
        if (!$forceOpen && empty($this->children)) {
473 14
            return $output . '/>' . $suffix;
474
        }
475
476 35
        return $output . '>' . $suffix;
477
    }
478
479 35
    protected function buildChildrenHtml($prefix, $suffix)
480
    {
481 35
        $output = '';
482 35
        foreach ($this->children as $child) {
483
            $newPrefix = $prefix .
484 35
                str_repeat(
485 35
                    ' ',
486 35
                    $this->configuration->get('indent-spaces')
487 35
                );
488 35
            $output .= $child->toHtml($newPrefix, $suffix);
489 35
        }
490
491 35
        return $output;
492
    }
493
494 17
    public function __toString()
495
    {
496 17
        return '"' . $this->name . '" element';
497
    }
498
}
499