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