TagNode   A
last analyzed

Complexity

Total Complexity 13

Size/Duplication

Total Lines 118
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 1
Bugs 1 Features 0
Metric Value
wmc 13
eloc 29
c 1
b 1
f 0
dl 0
loc 118
ccs 31
cts 31
cp 1
rs 10

6 Methods

Rating   Name   Duplication   Size   Complexity  
A renderAttributes() 0 34 6
A getTagName() 0 3 1
A setAttribute() 0 5 2
A getAttribute() 0 3 1
A __construct() 0 7 1
A render() 0 9 2
1
<?php
2
/**
3
 * (c) Steve Nebes <[email protected]>.
4
 *
5
 *  For the full copyright and license information, please view the LICENSE
6
 *  file that was distributed with this source code.
7
 */
8
9
declare(strict_types=1);
10
11
namespace SN\HtmlSanitizer\Node;
12
13
use SN\HtmlSanitizer\Sanitizer\StringSanitizerTrait;
14
15
/**
16
 * A Tag Node.
17
 */
18
class TagNode extends AbstractNode implements TagNodeInterface
19
{
20
    use HasChildrenTrait;
21
    use StringSanitizerTrait;
22
23
    /**
24
     * @var string
25
     */
26
    private $qName;
27
28
    /**
29
     * @var array<string, string|null>
30
     */
31
    private $attributes = [];
32
33
    /**
34
     * @var bool
35
     */
36
    private $isChildless = false;
37
38
    /**
39
     * Default values.
40
     *
41
     * @param NodeInterface $parent
42
     * @param string        $qName
43
     * @param array         $attributes
44
     * @param bool          $isChildless
45
     */
46 15
    public function __construct(NodeInterface $parent, string $qName, array $attributes = [], bool $isChildless = false)
47
    {
48 15
        parent::__construct($parent);
49
50 15
        $this->qName = $qName;
51 15
        $this->attributes = $attributes;
52 15
        $this->isChildless = $isChildless;
53 15
    }
54
55
    /**
56
     * @return string
57
     */
58 12
    public function getTagName(): string
59
    {
60 12
        return $this->qName;
61
    }
62
63
    /**
64
     * @param string $name
65
     *
66
     * @return string|null
67
     */
68 2
    public function getAttribute(string $name): ?string
69
    {
70 2
        return $this->attributes[$name] ?? null;
71
    }
72
73
    /**
74
     * @param string      $name
75
     * @param string|null $value
76
     */
77 5
    public function setAttribute(string $name, ?string $value)
78
    {
79
        // Always use only the first declaration (ease sanitization)
80 5
        if (!\array_key_exists($name, $this->attributes)) {
81 5
            $this->attributes[$name] = $value;
82
        }
83 5
    }
84
85
    /**
86
     * @return string
87
     */
88 11
    public function render(): string
89
    {
90 11
        $tag = $this->getTagName();
91
92 11
        if ($this->isChildless) {
93 1
            return '<'.$tag.$this->renderAttributes().' />';
94
        }
95
96 11
        return '<'.$tag.$this->renderAttributes().'>'.$this->renderChildren().'</'.$tag.'>';
97
    }
98
99
    /**
100
     * @return string
101
     */
102 11
    private function renderAttributes(): string
103
    {
104 11
        $rendered = [];
105
106 11
        foreach ($this->attributes as $name => $value) {
107 6
            if (null === $value) {
108
                // Tag should be removed as a sanitizer found suspect data inside
109 1
                continue;
110
            }
111
112 6
            $attr = $this->encodeHtmlEntities($name);
113 6
            if ('' !== $value) {
114
                // In quirks mode, IE8 does a poor job producing innerHTML values.
115
                // If JavaScript does:
116
                //      nodeA.innerHTML = nodeB.innerHTML;
117
                // and nodeB contains (or even if ` was encoded properly):
118
                //      <div attr="``foo=bar">
119
                // then IE8 will produce:
120
                //      <div attr=``foo=bar>
121
                // as the value of nodeB.innerHTML and assign it to nodeA.
122
                // IE8's HTML parser treats `` as a blank attribute value and foo=bar becomes a separate attribute.
123
                // Adding a space at the end of the attribute prevents this by forcing IE8 to put double
124
                // quotes around the attribute when computing nodeB.innerHTML.
125 6
                if (false !== \mb_strpos($value, '`')) {
126 1
                    $value .= ' ';
127
                }
128
129 6
                $attr .= '="'.$this->encodeHtmlEntities($value).'"';
130
            }
131
132 6
            $rendered[] = $attr;
133
        }
134
135 11
        return $rendered ? ' '.\implode(' ', $rendered) : '';
136
    }
137
}
138