Completed
Push — master ( a17ead...35004c )
by Kevin
02:36
created

Element::getName()   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 2
Bugs 0 Features 1
Metric Value
c 2
b 0
f 1
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 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_CI_SSENUM = 'ci_sse'; // case-insensitive space-separated enumeration
13
    const ATTR_JS        = 'cs_jsc';
14
    const ATTR_CI_STRING = 'ci_str'; // case-insensitive string
15
    const ATTR_CS_STRING = 'cs_str'; // case-sensitive string
16
    const ATTR_URI       = 'cs_uri';
17
18
    /** @var array */
19
    protected $attributes;
20
21
    /** @var array[Token] */
22
    protected $children;
23
24
    /** @var string */
25
    private $name;
26
27
    /**
28
     * Constructor
29
     */
30 48
    public function __construct(Configuration $configuration, $name, array $attributes = array())
31
    {
32 48
        parent::__construct(Token::ELEMENT, $configuration);
33
34 48
        $this->attributes = array();
35 48
        foreach ($attributes as $key => $value) {
36 22
            $this->addAttribute($key, $value);
37 48
        }
38
39 48
        $this->children = array();
40 48
        $this->setName($name);
41 48
    }
42
43
    /**
44
     * Getter for 'attributes'.
45
     */
46 2
    public function getAttributes()
47
    {
48 2
        return $this->attributes;
49
    }
50
51
    /**
52
     * Hasser for 'attributes'.
53
     *
54
     * @param string $key
55
     *
56
     * @return boolean True if the attribute is present.
57
     */
58 5
    public function hasAttribute($key)
59
    {
60 5
        return array_key_exists($key, $this->attributes);
61
    }
62
63 24
    public function addAttribute($key, $value)
64
    {
65 24
        $key = trim(strtolower($key));
66 24
        if ($key == '') {
67 1
            throw new \InvalidArgumentException('Invalid empty attribute key.');
68
        }
69
70 23
        $this->attributes[$key] = $value;
71
72 23
        return $this;
73
    }
74
75 1
    public function removeAttribute($key)
76
    {
77 1
        $key = trim(strtolower($key));
78 1
        if (isset($this->attributes[$key])) {
79 1
            unset($this->attributes[$key]);
80
81 1
            return true;
82 1
        }
83
84 1
        return false;
85
    }
86
87
    /**
88
     * Required by ContainsChildren interface.
89
     */
90 2
    public function getChildren()
91
    {
92 2
        return $this->children;
93
    }
94
95
    /**
96
     * Required by ContainsChildren interface.
97
     */
98 1
    public function hasChild(Token $token)
99
    {
100 1
        return array_search($token, $this->children, true) !== false;
101
    }
102
103
    /**
104
     * Required by ContainsChildren interface.
105
     */
106 39
    public function appendChild(Token $token)
107
    {
108 39
        $token->setParent($this);
109 39
        $this->children[] = $token;
110
111 39
        return $this;
112
    }
113
114
    /**
115
     * Required by ContainsChildren interface.
116
     */
117 3
    public function prependChild(Token $token)
118
    {
119 3
        $token->setParent($this);
120 3
        array_unshift($this->children, $token);
121
122 3
        return $this;
123
    }
124
125
    /**
126
     * Required by the ContainsChildren interface.
127
     */
128 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...
129
    {
130 12
        $key = array_search($token, $this->children, true);
131 12
        if ($key !== false) {
132 12
            unset($this->children[$key]);
133
134 12
            return true;
135
        }
136
137 1
        return false;
138
    }
139
140
    /**
141
     * Getter for 'name'.
142
     */
143 28
    public function getName()
144
    {
145 28
        return $this->name;
146
    }
147
148
    /**
149
     * Chainable setter for 'name'.
150
     */
151 48
    public function setName($name)
152
    {
153 48
        if (!is_string($name)) {
154 1
            throw new \InvalidArgumentException('Element name must be string type.');
155
        }
156
157 48
        $this->name = trim(strtolower($name));
158
159 48
        return $this;
160
    }
161
162
    /**
163
     * Required by the Cleanable interface.
164
     */
165 35
    public function clean(LoggerInterface $logger)
166
    {
167 35
        if ($this->configuration->get('clean-strategy') == Configuration::CLEAN_STRATEGY_NONE) {
168 1
            return true;
169
        }
170
171
        // Remove non-standard attributes.
172 34
        if ($this->configuration->get('clean-strategy') != Configuration::CLEAN_STRATEGY_LENIENT) {
173 34
            foreach ($this->attributes as $name => $value) {
174 15
                $attributeParameters = $this->getAttributeParameters($name);
175 15
                if (empty($attributeParameters)) {
176 4
                    $logger->debug('Removing non-standard attribute "' . $name . '" from ' . $this);
177 4
                    unset($this->attributes[$name]);
178 4
                }
179
            }
180 4
        }
181 4
182 34
        foreach ($this->attributes as $name => $value) {
183 34
            // Validate attribute value.
184
            $attributeParameters = $this->getAttributeParameters($name);
185 34
            if (empty($attributeParameters)) {
186
                $attributeParameters = array(
187 15
                    'name' => $name,
188 15
                    'regex' => '/\S*/i',
189
                    'valueType' => self::ATTR_CS_STRING
190 2
                );
191 2
            }
192
193 2
            list($caseSensitivity, $attributeType) =
194 2
                explode('_', $attributeParameters['valueType']);
195
196
            // Handle case-insensitivity.
197 15
            // Standard is case-insensitive attribute values should be lower case.
198
            if ($caseSensitivity == 'ci') {
199
                $newValue = strtolower($value);
200
                if ($newValue !== $value) {
201 15
                    $logger->debug('The value for the attribute "' . $name . '" is case-insensitive.  The value has been converted to lower case.  Element: ' . $this);
202 1
                    $this->attributes[$name] = $newValue;
203 1
                }
204 1
            }
205 1
206 1
            switch (substr($attributeType, 0, 3)) {
207
            case 'enu': // enumeration
208 1
                /// @todo
209 1
                break;
210 1
211
            case 'uri': // URI
212 15
                /// @todo
213 15
                break;
214
            }
215
        }
216
217 15
        $doCleanResult = $this->doClean($logger);
218
        if (!$doCleanResult && $this->configuration->get('clean-strategy') !== Configuration::CLEAN_STRATEGY_LENIENT) {
219 1
            return false;
220 15
        }
221 34
222
        // Clean children.
223 34
        return AbstractToken::cleanChildTokens(
224 34
            $this->configuration,
225 1
            $this->children,
226
            $logger
227
        );
228
    }
229 34
230 34
    protected function doClean(LoggerInterface $logger = null)
231 34
    {
232
        return true;
233 34
    }
234
235
    protected function getAllowedAttributes()
236 21
    {
237
        return array(
238 21
            // Global Attributes
239
            '/^accesskey$/i' => self::ATTR_CS_STRING,
240
            '/^class$/i' => self::ATTR_CS_STRING,
241 15
            '/^contenteditable$/i' => self::ATTR_CS_STRING,
242
            '/^contextmenu$/i' => self::ATTR_CS_STRING,
243
            '/^data-\S/i' => self::ATTR_CS_STRING,
244
            '/^dir$/i' => self::ATTR_CI_ENUM . '("ltr","rtl"|"ltr")',
245 15
            '/^draggable$/i' => self::ATTR_CS_STRING,
246 15
            '/^dropzone$/i' => self::ATTR_CS_STRING,
247 15
            '/^hidden$/i' => self::ATTR_CS_STRING,
248 15
            '/^id$/i' => self::ATTR_CS_STRING,
249 15
            '/^is$/i' => self::ATTR_CS_STRING,
250 15
            '/^itemid$/i' => self::ATTR_CS_STRING,
251 15
            '/^itemprop$/i' => self::ATTR_CS_STRING,
252 15
            '/^itemref$/i' => self::ATTR_CS_STRING,
253 15
            '/^itemscope$/i' => self::ATTR_CS_STRING,
254 15
            '/^itemtype$/i' => self::ATTR_CS_STRING,
255 15
            '/^lang$/i' => self::ATTR_CI_STRING,
256 15
            '/^slot$/i' => self::ATTR_CS_STRING,
257 15
            '/^spellcheck$/i' => self::ATTR_CS_STRING,
258 15
            '/^style$/i' => self::ATTR_CS_STRING,
259 15
            '/^tabindex$/i' => self::ATTR_CS_STRING,
260 15
            '/^title$/i' => self::ATTR_CS_STRING,
261 15
            '/^translate$/i' => self::ATTR_CI_ENUM . '("yes","no",""|"yes")',
262 15
263 15
            // Event Handler Content Attributes
264 15
            // https://html.spec.whatwg.org/multipage/webappapis.html#event-handler-content-attributes
265 15
            '/^onabort$/i' => self::ATTR_JS,
266 15
            '/^onautocomplete$/i' => self::ATTR_JS,
267 15
            '/^onautocompleteerror$/i' => self::ATTR_JS,
268
            '/^onblur$/i' => self::ATTR_JS,
269
            '/^oncancel$/i' => self::ATTR_JS,
270
            '/^oncanplay$/i' => self::ATTR_JS,
271 15
            '/^oncanplaythrough$/i' => self::ATTR_JS,
272 15
            '/^onchange$/i' => self::ATTR_JS,
273 15
            '/^onclick$/i' => self::ATTR_JS,
274 15
            '/^onclose$/i' => self::ATTR_JS,
275 15
            '/^oncontextmenu$/i' => self::ATTR_JS,
276 15
            '/^oncuechange$/i' => self::ATTR_JS,
277 15
            '/^ondblclick$/i' => self::ATTR_JS,
278 15
            '/^ondrag$/i' => self::ATTR_JS,
279 15
            '/^ondragend$/i' => self::ATTR_JS,
280 15
            '/^ondragenter$/i' => self::ATTR_JS,
281 15
            '/^ondragexit$/i' => self::ATTR_JS,
282 15
            '/^ondragleave$/i' => self::ATTR_JS,
283 15
            '/^ondragover$/i' => self::ATTR_JS,
284 15
            '/^ondragstart$/i' => self::ATTR_JS,
285 15
            '/^ondrop$/i' => self::ATTR_JS,
286 15
            '/^ondurationchange$/i' => self::ATTR_JS,
287 15
            '/^onemptied$/i' => self::ATTR_JS,
288 15
            '/^onended$/i' => self::ATTR_JS,
289 15
            '/^onerror$/i' => self::ATTR_JS,
290 15
            '/^onfocus$/i' => self::ATTR_JS,
291 15
            '/^oninput$/i' => self::ATTR_JS,
292 15
            '/^oninvalid$/i' => self::ATTR_JS,
293 15
            '/^onkeydown$/i' => self::ATTR_JS,
294 15
            '/^onkeypress$/i' => self::ATTR_JS,
295 15
            '/^onkeyup$/i' => self::ATTR_JS,
296 15
            '/^onload$/i' => self::ATTR_JS,
297 15
            '/^onloadeddata$/i' => self::ATTR_JS,
298 15
            '/^onloadedmetadata$/i' => self::ATTR_JS,
299 15
            '/^onloadstart$/i' => self::ATTR_JS,
300 15
            '/^onmousedown$/i' => self::ATTR_JS,
301 15
            '/^onmouseenter$/i' => self::ATTR_JS,
302 15
            '/^onmouseleave$/i' => self::ATTR_JS,
303 15
            '/^onmousemove$/i' => self::ATTR_JS,
304 15
            '/^onmouseout$/i' => self::ATTR_JS,
305 15
            '/^onmouseover$/i' => self::ATTR_JS,
306 15
            '/^onmouseup$/i' => self::ATTR_JS,
307 15
            '/^onwheel$/i' => self::ATTR_JS,
308 15
            '/^onpause$/i' => self::ATTR_JS,
309 15
            '/^onplay$/i' => self::ATTR_JS,
310 15
            '/^onplaying$/i' => self::ATTR_JS,
311 15
            '/^onprogress$/i' => self::ATTR_JS,
312 15
            '/^onratechange$/i' => self::ATTR_JS,
313 15
            '/^onreset$/i' => self::ATTR_JS,
314 15
            '/^onresize$/i' => self::ATTR_JS,
315 15
            '/^onscroll$/i' => self::ATTR_JS,
316 15
            '/^onseeked$/i' => self::ATTR_JS,
317 15
            '/^onseeking$/i' => self::ATTR_JS,
318 15
            '/^onselect$/i' => self::ATTR_JS,
319 15
            '/^onshow$/i' => self::ATTR_JS,
320 15
            '/^onstalled$/i' => self::ATTR_JS,
321 15
            '/^onsubmit$/i' => self::ATTR_JS,
322 15
            '/^onsuspend$/i' => self::ATTR_JS,
323 15
            '/^ontimeupdate$/i' => self::ATTR_JS,
324 15
            '/^ontoggle$/i' => self::ATTR_JS,
325 15
            '/^onvolumechange$/i' => self::ATTR_JS,
326 15
            '/^onwaiting$/i' => self::ATTR_JS,
327 15
328 15
            // WAI-ARIA
329 15
            // https://w3c.github.io/aria/aria/aria.html
330 15
            '/^role$/i' => self::ATTR_CI_STRING,
331 15
332 15
            // ARIA global states and properties
333
            '/^aria-atomic$/i' => self::ATTR_CS_STRING,
334
            '/^aria-busy$/i' => self::ATTR_CS_STRING,
335
            '/^aria-controls$/i' => self::ATTR_CS_STRING,
336 15
            '/^aria-current$/i' => self::ATTR_CS_STRING,
337
            '/^aria-describedby$/i' => self::ATTR_CS_STRING,
338
            '/^aria-details$/i' => self::ATTR_CS_STRING,
339 15
            '/^aria-disabled$/i' => self::ATTR_CS_STRING,
340 15
            '/^aria-dropeffect$/i' => self::ATTR_CS_STRING,
341 15
            '/^aria-errormessage$/i' => self::ATTR_CS_STRING,
342 15
            '/^aria-flowto$/i' => self::ATTR_CS_STRING,
343 15
            '/^aria-grabbed$/i' => self::ATTR_CS_STRING,
344 15
            '/^aria-haspopup$/i' => self::ATTR_CS_STRING,
345 15
            '/^aria-hidden$/i' => self::ATTR_CS_STRING,
346 15
            '/^aria-invalid$/i' => self::ATTR_CS_STRING,
347 15
            '/^aria-label$/i' => self::ATTR_CS_STRING,
348 15
            '/^aria-labelledby$/i' => self::ATTR_CS_STRING,
349 15
            '/^aria-live$/i' => self::ATTR_CS_STRING,
350 15
            '/^aria-owns$/i' => self::ATTR_CS_STRING,
351 15
            '/^aria-relevant$/i' => self::ATTR_CS_STRING,
352 15
            '/^aria-roledescription$/i' => self::ATTR_CS_STRING,
353 15
354 15
            // ARIA widget attributes
355 15
            '/^aria-autocomplete$/i' => self::ATTR_CS_STRING,
356 15
            '/^aria-checked$/i' => self::ATTR_CS_STRING,
357 15
            '/^aria-expanded$/i' => self::ATTR_CS_STRING,
358 15
            '/^aria-level$/i' => self::ATTR_CS_STRING,
359
            '/^aria-modal$/i' => self::ATTR_CS_STRING,
360
            '/^aria-multiline$/i' => self::ATTR_CS_STRING,
361 15
            '/^aria-multiselectable$/i' => self::ATTR_CS_STRING,
362 15
            '/^aria-orientation$/i' => self::ATTR_CS_STRING,
363 15
            '/^aria-placeholder$/i' => self::ATTR_CS_STRING,
364 15
            '/^aria-pressed$/i' => self::ATTR_CS_STRING,
365 15
            '/^aria-readonly$/i' => self::ATTR_CS_STRING,
366 15
            '/^aria-required$/i' => self::ATTR_CS_STRING,
367 15
            '/^aria-selected$/i' => self::ATTR_CS_STRING,
368 15
            '/^aria-sort$/i' => self::ATTR_CS_STRING,
369 15
            '/^aria-valuemax$/i' => self::ATTR_CS_STRING,
370 15
            '/^aria-valuemin$/i' => self::ATTR_CS_STRING,
371 15
            '/^aria-valuenow$/i' => self::ATTR_CS_STRING,
372 15
            '/^aria-valuetext$/i' => self::ATTR_CS_STRING,
373 15
374 15
            // ARIA relationship attributes
375 15
            '/^aria-activedescendant$/i' => self::ATTR_CS_STRING,
376 15
            '/^aria-colcount$/i' => self::ATTR_CS_STRING,
377 15
            '/^aria-colindex$/i' => self::ATTR_CS_STRING,
378 15
            '/^aria-colspan$/i' => self::ATTR_CS_STRING,
379
            '/^aria-posinset$/i' => self::ATTR_CS_STRING,
380
            '/^aria-rowcount$/i' => self::ATTR_CS_STRING,
381 15
            '/^aria-rowindex$/i' => self::ATTR_CS_STRING,
382 15
            '/^aria-rowspan$/i' => self::ATTR_CS_STRING,
383 15
            '/^aria-setsize$/i' => self::ATTR_CS_STRING
384 15
        );
385 15
    }
386 15
387 15
    protected function getAttributeParameters($name)
388 15
    {
389
        $allowedAttributes = $this->getAllowedAttributes();
390 15
        foreach ($allowedAttributes as $attrRegex => $valueType) {
391
            if (preg_match($attrRegex, $name) === 1) {
392
                return array(
393 15
                    'name' => $name,
394
                    'regex' => $attrRegex,
395 15
                    'valueType' => $valueType
396 15
                );
397 15
            }
398
        }
399 15
400 15
        return array();
401
    }
402 15
403
    /**
404 14
     * Required by the Removable interface.
405
     */
406 4 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...
407
    {
408
        $hasRemovableElements = $this->configuration->get('element-blacklist') != '';
409
        $hasRemovableTypes = $this->configuration->get('type-blacklist') != '';
410
        foreach ($this->children as $child) {
411
            // Check types.
412 32
            if ($hasRemovableTypes &&
413
                !$this->configuration->isAllowedType($child->getType())) {
414 32
                $logger->debug('Removing ' . $child);
415 32
                $this->removeChild($child);
416 32
417
                continue;
418 30
            }
419 30
420 1
            // Check elements.
421 1
            if ($hasRemovableElements &&
422 1
                $child->getType() == Token::ELEMENT &&
423
                !$this->configuration->isAllowedElement($child->getName())) {
424 1
                $logger->debug('Removing ' . $child);
425 1
                $this->removeChild($child);
426
427
                continue;
428
            }
429 29
430 29
            // Check children.
431 29
            if ($child instanceof Removable) {
432 3
                $child->remove($logger);
433 3
            }
434 3
        }
435
    }
436 3
437 3
    /**
438
     * Required by the Token interface.
439
     */
440
    public function toHtml($prefix, $suffix)
441 29
    {
442 24
        $output = $this->buildStartTag($prefix, $suffix);
443 24
        if (empty($this->children)) {
444 32
            return $output;
445 32
        }
446
447
        $output .= $this->buildChildrenHtml($prefix, $suffix);
448
449
        return $output . $prefix . '</' . $this->name . '>' . $suffix;
450 19
    }
451
452 19
    protected function buildStartTag($prefix, $suffix, $forceOpen = false)
453 19
    {
454 7
        $output = $prefix . '<' . $this->name;
455
        foreach ($this->attributes as $key => $value) {
456
            $output .= ' ' . strtolower($key);
457 16
            if (is_string($value)) {
458
                /// @todo Escape double quotes in value.
459 16
                $output .= '="' . $value . '"';
460
            }
461
        }
462 38
463
        if (!$forceOpen && empty($this->children)) {
464 38
            return $output . '/>' . $suffix;
465 38
        }
466 17
467 17
        return $output . '>' . $suffix;
468
    }
469 17
470 17
    protected function buildChildrenHtml($prefix, $suffix)
471 38
    {
472
        $output = '';
473 38
        foreach ($this->children as $child) {
474 14
            $newPrefix = $prefix .
475
                str_repeat(
476
                    ' ',
477 35
                    $this->configuration->get('indent-spaces')
478
                );
479
            $output .= $child->toHtml($newPrefix, $suffix);
480 35
        }
481
482 35
        return $output;
483 35
    }
484
485 35
    public function __toString()
486 35
    {
487 35
        return '"' . $this->name . '" element';
488 35
    }
489
}
490