Completed
Push — master ( 817727...d8e3ee )
by Kevin
03:58
created

Element::getType()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 1
Bugs 1 Features 0
Metric Value
c 1
b 1
f 0
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
cc 1
eloc 2
nc 1
nop 0
crap 1
1
<?php
2
3
namespace Groundskeeper\Tokens;
4
5
use Groundskeeper\Configuration;
6
use Psr\Log\LoggerInterface;
7
8
class Element extends AbstractToken implements Cleanable, ContainsChildren, Removable
9
{
10
    const ATTR_BOOL      = 'ci_boo'; // boolean
11
    const ATTR_CI_ENUM   = 'ci_enu'; // case-insensitive enumeration
12
    const ATTR_CI_SSENUM = 'ci_sse'; // case-insensitive space-separated enumeration
13
    const ATTR_INT       = 'ci_int'; // integer
14
    const ATTR_JS        = 'cs_jsc'; // javascript
15
    const ATTR_CI_STRING = 'ci_str'; // case-insensitive string
16
    const ATTR_CS_STRING = 'cs_str'; // case-sensitive string
17
    const ATTR_URI       = 'cs_uri'; // uri
18
19
    /** @var array */
20
    protected $attributes;
21
22
    /** @var array[Token] */
23
    protected $children;
24
25
    /** @var string */
26
    private $name;
27
28
    /**
29
     * Constructor
30
     */
31 95
    public function __construct(Configuration $configuration, $name, array $attributes = array())
32
    {
33 95
        parent::__construct($configuration);
34
35 95
        $this->attributes = array();
36 95
        foreach ($attributes as $key => $value) {
37 51
            $this->addAttribute($key, $value);
38 95
        }
39
40 95
        $this->children = array();
41
42 95
        if (!is_string($name)) {
43 3
            throw new \InvalidArgumentException('Element name must be string type.');
44 1
        }
45
46 94
        $this->name = trim(strtolower($name));
47 94
    }
48
49
    /**
50
     * Getter for 'attributes'.
51
     */
52 2
    public function getAttributes()
53 2
    {
54 2
        return $this->attributes;
55
    }
56
57 3
    public function getAttribute($key)
58
    {
59 3
        if (!$this->hasAttribute($key)) {
60 1
            throw new \InvalidArgumentException('Invalid attribute key: ' . $key);
61
        }
62
63 3
        return $this->attributes[$key];
64 1
    }
65
66
    /**
67
     * Hasser for 'attributes'.
68
     *
69
     * @param string $key
70
     *
71
     * @return bool True if the attribute is present.
72
     */
73 23
    public function hasAttribute($key)
74
    {
75 23
        return array_key_exists($key, $this->attributes);
76
    }
77
78 51
    public function addAttribute($key, $value)
79 1
    {
80 51
        $key = trim(strtolower($key));
81 51
        if ($key == '') {
82 1
            throw new \InvalidArgumentException('Invalid empty attribute key.');
83 1
        }
84
85 50
        $this->attributes[$key] = $value;
86
87 50
        return $this;
88
    }
89
90 2
    public function removeAttribute($key)
91 1
    {
92 2
        $key = trim(strtolower($key));
93 2
        if (isset($this->attributes[$key])) {
94 2
            unset($this->attributes[$key]);
95
96 2
            return true;
97
        }
98
99 2
        return false;
100
    }
101
102
    /**
103
     * Required by ContainsChildren interface.
104
     */
105 5
    public function getChildren()
106
    {
107 5
        return $this->children;
108
    }
109
110
    /**
111
     * Required by ContainsChildren interface.
112
     */
113 1
    public function hasChild(Token $token)
114
    {
115 1
        return array_search($token, $this->children, true) !== false;
116
    }
117
118
    /**
119
     * Required by ContainsChildren interface.
120
     */
121 83
    public function appendChild(Token $token)
122
    {
123 83
        $token->setParent($this);
124 83
        $this->children[] = $token;
125
126 83
        return $this;
127
    }
128
129
    /**
130
     * Required by ContainsChildren interface.
131
     */
132 5
    public function prependChild(Token $token)
133
    {
134 5
        $token->setParent($this);
135 5
        array_unshift($this->children, $token);
136
137 5
        return $this;
138
    }
139
140
    /**
141
     * Required by the ContainsChildren interface.
142
     */
143 26 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...
144
    {
145 26
        $key = array_search($token, $this->children, true);
146 26
        if ($key !== false) {
147 26
            unset($this->children[$key]);
148
149 26
            return true;
150
        }
151
152 1
        return false;
153
    }
154
155
    /**
156
     * Getter for 'name'.
157
     */
158 79
    public function getName()
159
    {
160 79
        return $this->name;
161
    }
162
163
    /**
164
     * Required by the Cleanable interface.
165
     */
166 79
    public function clean(LoggerInterface $logger)
167
    {
168 79
        if ($this->configuration->get('clean-strategy') == Configuration::CLEAN_STRATEGY_NONE) {
169 1
            return true;
170
        }
171
172
        // Remove non-standard attributes.
173 78
        if ($this->configuration->get('clean-strategy') != Configuration::CLEAN_STRATEGY_LENIENT) {
174 78
            foreach ($this->attributes as $name => $value) {
175 41
                $attributeParameters = $this->getAttributeParameters($name);
176 41
                if (empty($attributeParameters)) {
177 7
                    $logger->debug('Removing non-standard attribute "' . $name . '" from ' . $this);
178 7
                    unset($this->attributes[$name]);
179 7
                }
180 78
            }
181 78
        }
182
183 78
        foreach ($this->attributes as $name => $value) {
184
            // Validate attribute value.
185 42
            $attributeParameters = $this->getAttributeParameters($name);
186 42
            if (empty($attributeParameters)) {
187
                $attributeParameters = array(
188 5
                    'name' => $name,
189 5
                    'regex' => '/\S*/i',
190
                    'valueType' => self::ATTR_CS_STRING
191 5
                );
192 5
            }
193
194
            list($caseSensitivity, $attributeType) =
195 42
                explode('_', $attributeParameters['valueType']);
196
197
            // Handle case-insensitivity.
198
            // Standard is case-insensitive attribute values should be lower case.
199 42
            if ($caseSensitivity == 'ci') {
200 10
                $newValue = strtolower($value);
201 10
                if ($newValue !== $value) {
202 2
                    $logger->debug('Within ' . $this . ' the value for the attribute "' . $name . '" is case-insensitive.  The value has been converted to lower case.');
203 2
                    $this->attributes[$name] = $newValue;
204 2
                }
205 10
            }
206
207 42
            switch (substr($attributeType, 0, 3)) {
208 42
            case 'enu': // enumeration
209
                /// @todo
210 1
                break;
211
212 41
            case 'uri': // URI
213
                /// @todo
214 13
                break;
215 42
            }
216 78
        }
217
218
        // Fix self (if possible)
0 ignored issues
show
Unused Code Comprehensibility introduced by
40% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
219 78
        $this->fixSelf($logger);
220
221
        // Remove self or children?
222 78
        if ($this->configuration->get('clean-strategy') !== Configuration::CLEAN_STRATEGY_LENIENT) {
223
            // Remove self?
224 78
            if ($this->removeInvalidSelf($logger)) {
225 18
                return false;
226
            }
227
228
            // Remove children?
229 78
            $this->removeInvalidChildren($logger);
230 78
        }
231
232
        // Clean children.
233 78
        return AbstractToken::cleanChildTokens(
234 78
            $this->configuration,
235 78
            $this->children,
236
            $logger
237 78
        );
238
    }
239
240 73
    protected function fixSelf(LoggerInterface $logger)
241
    {
242 73
    }
243
244 70
    protected function removeInvalidChildren(LoggerInterface $logger)
245
    {
246 70
    }
247
248 73
    protected function removeInvalidSelf(LoggerInterface $logger)
249
    {
250 73
        return false;
251
    }
252
253 42
    protected function getAllowedAttributes()
254
    {
255
        return array(
256
            // Global Attributes
257 42
            '/^accesskey$/i' => self::ATTR_CS_STRING,
258 42
            '/^class$/i' => self::ATTR_CS_STRING,
259 42
            '/^contenteditable$/i' => self::ATTR_CS_STRING,
260 42
            '/^contextmenu$/i' => self::ATTR_CS_STRING,
261 42
            '/^data-\S/i' => self::ATTR_CS_STRING,
262 42
            '/^dir$/i' => self::ATTR_CI_ENUM . '("ltr","rtl"|"ltr")',
263 42
            '/^draggable$/i' => self::ATTR_CS_STRING,
264 42
            '/^dropzone$/i' => self::ATTR_CS_STRING,
265 42
            '/^hidden$/i' => self::ATTR_CS_STRING,
266 42
            '/^id$/i' => self::ATTR_CS_STRING,
267 42
            '/^is$/i' => self::ATTR_CS_STRING,
268 42
            '/^itemid$/i' => self::ATTR_CS_STRING,
269 42
            '/^itemprop$/i' => self::ATTR_CS_STRING,
270 42
            '/^itemref$/i' => self::ATTR_CS_STRING,
271 42
            '/^itemscope$/i' => self::ATTR_CS_STRING,
272 42
            '/^itemtype$/i' => self::ATTR_CS_STRING,
273 42
            '/^lang$/i' => self::ATTR_CI_STRING,
274 42
            '/^slot$/i' => self::ATTR_CS_STRING,
275 42
            '/^spellcheck$/i' => self::ATTR_CS_STRING,
276 42
            '/^style$/i' => self::ATTR_CS_STRING,
277 42
            '/^tabindex$/i' => self::ATTR_CS_STRING,
278 42
            '/^title$/i' => self::ATTR_CS_STRING,
279 42
            '/^translate$/i' => self::ATTR_CI_ENUM . '("yes","no",""|"yes")',
280
281
            // Event Handler Content Attributes
282
            // https://html.spec.whatwg.org/multipage/webappapis.html#event-handler-content-attributes
283 42
            '/^onabort$/i' => self::ATTR_JS,
284 42
            '/^onautocomplete$/i' => self::ATTR_JS,
285 42
            '/^onautocompleteerror$/i' => self::ATTR_JS,
286 42
            '/^onblur$/i' => self::ATTR_JS,
287 42
            '/^oncancel$/i' => self::ATTR_JS,
288 42
            '/^oncanplay$/i' => self::ATTR_JS,
289 42
            '/^oncanplaythrough$/i' => self::ATTR_JS,
290 42
            '/^onchange$/i' => self::ATTR_JS,
291 42
            '/^onclick$/i' => self::ATTR_JS,
292 42
            '/^onclose$/i' => self::ATTR_JS,
293 42
            '/^oncontextmenu$/i' => self::ATTR_JS,
294 42
            '/^oncuechange$/i' => self::ATTR_JS,
295 42
            '/^ondblclick$/i' => self::ATTR_JS,
296 42
            '/^ondrag$/i' => self::ATTR_JS,
297 42
            '/^ondragend$/i' => self::ATTR_JS,
298 42
            '/^ondragenter$/i' => self::ATTR_JS,
299 42
            '/^ondragexit$/i' => self::ATTR_JS,
300 42
            '/^ondragleave$/i' => self::ATTR_JS,
301 42
            '/^ondragover$/i' => self::ATTR_JS,
302 42
            '/^ondragstart$/i' => self::ATTR_JS,
303 42
            '/^ondrop$/i' => self::ATTR_JS,
304 42
            '/^ondurationchange$/i' => self::ATTR_JS,
305 42
            '/^onemptied$/i' => self::ATTR_JS,
306 42
            '/^onended$/i' => self::ATTR_JS,
307 42
            '/^onerror$/i' => self::ATTR_JS,
308 42
            '/^onfocus$/i' => self::ATTR_JS,
309 42
            '/^oninput$/i' => self::ATTR_JS,
310 42
            '/^oninvalid$/i' => self::ATTR_JS,
311 42
            '/^onkeydown$/i' => self::ATTR_JS,
312 42
            '/^onkeypress$/i' => self::ATTR_JS,
313 42
            '/^onkeyup$/i' => self::ATTR_JS,
314 42
            '/^onload$/i' => self::ATTR_JS,
315 42
            '/^onloadeddata$/i' => self::ATTR_JS,
316 42
            '/^onloadedmetadata$/i' => self::ATTR_JS,
317 42
            '/^onloadstart$/i' => self::ATTR_JS,
318 42
            '/^onmousedown$/i' => self::ATTR_JS,
319 42
            '/^onmouseenter$/i' => self::ATTR_JS,
320 42
            '/^onmouseleave$/i' => self::ATTR_JS,
321 42
            '/^onmousemove$/i' => self::ATTR_JS,
322 42
            '/^onmouseout$/i' => self::ATTR_JS,
323 42
            '/^onmouseover$/i' => self::ATTR_JS,
324 42
            '/^onmouseup$/i' => self::ATTR_JS,
325 42
            '/^onwheel$/i' => self::ATTR_JS,
326 42
            '/^onpause$/i' => self::ATTR_JS,
327 42
            '/^onplay$/i' => self::ATTR_JS,
328 42
            '/^onplaying$/i' => self::ATTR_JS,
329 42
            '/^onprogress$/i' => self::ATTR_JS,
330 42
            '/^onratechange$/i' => self::ATTR_JS,
331 42
            '/^onreset$/i' => self::ATTR_JS,
332 42
            '/^onresize$/i' => self::ATTR_JS,
333 42
            '/^onscroll$/i' => self::ATTR_JS,
334 42
            '/^onseeked$/i' => self::ATTR_JS,
335 42
            '/^onseeking$/i' => self::ATTR_JS,
336 42
            '/^onselect$/i' => self::ATTR_JS,
337 42
            '/^onshow$/i' => self::ATTR_JS,
338 42
            '/^onstalled$/i' => self::ATTR_JS,
339 42
            '/^onsubmit$/i' => self::ATTR_JS,
340 42
            '/^onsuspend$/i' => self::ATTR_JS,
341 42
            '/^ontimeupdate$/i' => self::ATTR_JS,
342 42
            '/^ontoggle$/i' => self::ATTR_JS,
343 42
            '/^onvolumechange$/i' => self::ATTR_JS,
344 42
            '/^onwaiting$/i' => self::ATTR_JS,
345
346
            // WAI-ARIA
347
            // https://w3c.github.io/aria/aria/aria.html
348 42
            '/^role$/i' => self::ATTR_CI_STRING,
349
350
            // ARIA global states and properties
351 42
            '/^aria-atomic$/i' => self::ATTR_CS_STRING,
352 42
            '/^aria-busy$/i' => self::ATTR_CS_STRING,
353 42
            '/^aria-controls$/i' => self::ATTR_CS_STRING,
354 42
            '/^aria-current$/i' => self::ATTR_CS_STRING,
355 42
            '/^aria-describedby$/i' => self::ATTR_CS_STRING,
356 42
            '/^aria-details$/i' => self::ATTR_CS_STRING,
357 42
            '/^aria-disabled$/i' => self::ATTR_CS_STRING,
358 42
            '/^aria-dropeffect$/i' => self::ATTR_CS_STRING,
359 42
            '/^aria-errormessage$/i' => self::ATTR_CS_STRING,
360 42
            '/^aria-flowto$/i' => self::ATTR_CS_STRING,
361 42
            '/^aria-grabbed$/i' => self::ATTR_CS_STRING,
362 42
            '/^aria-haspopup$/i' => self::ATTR_CS_STRING,
363 42
            '/^aria-hidden$/i' => self::ATTR_CS_STRING,
364 42
            '/^aria-invalid$/i' => self::ATTR_CS_STRING,
365 42
            '/^aria-label$/i' => self::ATTR_CS_STRING,
366 42
            '/^aria-labelledby$/i' => self::ATTR_CS_STRING,
367 42
            '/^aria-live$/i' => self::ATTR_CS_STRING,
368 42
            '/^aria-owns$/i' => self::ATTR_CS_STRING,
369 42
            '/^aria-relevant$/i' => self::ATTR_CS_STRING,
370 42
            '/^aria-roledescription$/i' => self::ATTR_CS_STRING,
371
372
            // ARIA widget attributes
373 42
            '/^aria-autocomplete$/i' => self::ATTR_CS_STRING,
374 42
            '/^aria-checked$/i' => self::ATTR_CS_STRING,
375 42
            '/^aria-expanded$/i' => self::ATTR_CS_STRING,
376 42
            '/^aria-level$/i' => self::ATTR_CS_STRING,
377 42
            '/^aria-modal$/i' => self::ATTR_CS_STRING,
378 42
            '/^aria-multiline$/i' => self::ATTR_CS_STRING,
379 42
            '/^aria-multiselectable$/i' => self::ATTR_CS_STRING,
380 42
            '/^aria-orientation$/i' => self::ATTR_CS_STRING,
381 42
            '/^aria-placeholder$/i' => self::ATTR_CS_STRING,
382 42
            '/^aria-pressed$/i' => self::ATTR_CS_STRING,
383 42
            '/^aria-readonly$/i' => self::ATTR_CS_STRING,
384 42
            '/^aria-required$/i' => self::ATTR_CS_STRING,
385 42
            '/^aria-selected$/i' => self::ATTR_CS_STRING,
386 42
            '/^aria-sort$/i' => self::ATTR_CS_STRING,
387 42
            '/^aria-valuemax$/i' => self::ATTR_CS_STRING,
388 42
            '/^aria-valuemin$/i' => self::ATTR_CS_STRING,
389 42
            '/^aria-valuenow$/i' => self::ATTR_CS_STRING,
390 42
            '/^aria-valuetext$/i' => self::ATTR_CS_STRING,
391
392
            // ARIA relationship attributes
393 42
            '/^aria-activedescendant$/i' => self::ATTR_CS_STRING,
394 42
            '/^aria-colcount$/i' => self::ATTR_CS_STRING,
395 42
            '/^aria-colindex$/i' => self::ATTR_CS_STRING,
396 42
            '/^aria-colspan$/i' => self::ATTR_CS_STRING,
397 42
            '/^aria-posinset$/i' => self::ATTR_CS_STRING,
398 42
            '/^aria-rowcount$/i' => self::ATTR_CS_STRING,
399 42
            '/^aria-rowindex$/i' => self::ATTR_CS_STRING,
400 42
            '/^aria-rowspan$/i' => self::ATTR_CS_STRING,
401
            '/^aria-setsize$/i' => self::ATTR_CS_STRING
402 42
        );
403
    }
404
405 42
    protected function getAttributeParameters($name)
406
    {
407 42
        $allowedAttributes = $this->getAllowedAttributes();
408 42
        foreach ($allowedAttributes as $attrRegex => $valueType) {
409 42
            if (preg_match($attrRegex, $name) === 1) {
410
                return array(
411 42
                    'name' => $name,
412 42
                    'regex' => $attrRegex,
413
                    'valueType' => $valueType
414 42
                );
415
            }
416 33
        }
417
418 7
        return array();
419
    }
420
421
    /**
422
     * Required by the Removable interface.
423
     */
424 76 View Code Duplication
    public function remove(LoggerInterface $logger)
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...
425
    {
426 76
        $hasRemovableElements = $this->configuration->get('element-blacklist') != '';
427 76
        $hasRemovableTypes = $this->configuration->get('type-blacklist') != '';
428 76
        foreach ($this->children as $child) {
429
            // Check types.
430 74
            if ($hasRemovableTypes &&
431 74
                !$this->configuration->isAllowedType($child->getType())) {
432 2
                $logger->debug('Removing ' . $child);
433 2
                $this->removeChild($child);
434
435 2
                continue;
436
            }
437
438
            // Check elements.
439 72
            if ($hasRemovableElements &&
440 72
                $child instanceof self &&
441 72
                !$this->configuration->isAllowedElement($child->getName())) {
442 3
                $logger->debug('Removing ' . $child);
443 3
                $this->removeChild($child);
444
445 3
                continue;
446
            }
447
448
            // Check children.
449 72
            if ($child instanceof Removable) {
450 63
                $child->remove($logger);
451 63
            }
452 76
        }
453 76
    }
454
455
    /**
456
     * Required by the Token interface.
457
     */
458 7
    public function toHtml($prefix, $suffix)
459
    {
460 7
        $output = $this->buildStartTag($prefix, $suffix);
461 7
        if (empty($this->children)) {
462 6
            return $output;
463
        }
464
465 5
        $output .= $this->buildChildrenHtml($prefix, $suffix);
466
467 5
        return $output . $prefix . '</' . $this->name . '>' . $suffix;
468
    }
469
470 82
    protected function buildStartTag($prefix, $suffix, $forceOpen = false)
471
    {
472 82
        $output = $prefix . '<' . $this->name;
473 82
        foreach ($this->attributes as $key => $value) {
474 44
            $output .= ' ' . strtolower($key);
475 44
            if (is_string($value)) {
476
                /// @todo Escape double quotes in value.
477 44
                $output .= '="' . $value . '"';
478 44
            }
479 82
        }
480
481 82
        if (!$forceOpen && empty($this->children)) {
482 27
            return $output . '/>' . $suffix;
483
        }
484
485 80
        return $output . '>' . $suffix;
486
    }
487
488 80
    protected function buildChildrenHtml($prefix, $suffix)
489
    {
490 80
        $output = '';
491 80
        foreach ($this->children as $child) {
492
            $newPrefix = $prefix .
493 78
                str_repeat(
494 78
                    ' ',
495 78
                    $this->configuration->get('indent-spaces')
496 78
                );
497 78
            $output .= $child->toHtml($newPrefix, $suffix);
498 80
        }
499
500 80
        return $output;
501
    }
502
503 16
    public function getType()
504
    {
505 16
        return Token::ELEMENT;
506
    }
507
508 50
    public function __toString()
509
    {
510 50
        return '"' . $this->name . '" element';
511
    }
512
}
513