1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Groundskeeper\Tokens\Elements; |
4
|
|
|
|
5
|
|
|
use Groundskeeper\Configuration; |
6
|
|
|
use Groundskeeper\Tokens\Element; |
7
|
|
|
use Groundskeeper\Tokens\ElementTypes\ClosedElement; |
8
|
|
|
use Groundskeeper\Tokens\ElementTypes\MetadataContent; |
9
|
|
|
use Psr\Log\LoggerInterface; |
10
|
|
|
|
11
|
|
|
class Link extends ClosedElement implements MetadataContent |
12
|
|
|
{ |
13
|
6 |
|
protected function getAllowedAttributes() |
14
|
|
|
{ |
15
|
|
|
$linkAllowedAttributes = array( |
16
|
6 |
|
'/^href$/i' => Element::ATTR_URI, |
17
|
6 |
|
'/^crossorigin$/i' => Element::ATTR_CS_STRING, |
18
|
6 |
|
'/^rel$/i' => Element::ATTR_CI_SSENUM . '("alternate","author","help","icon","license","next","pingback","prefetch","prev","search","stylesheet")', |
19
|
6 |
|
'/^media$/i' => Element::ATTR_CI_STRING, |
20
|
6 |
|
'/^hreflang$/i' => Element::ATTR_CS_STRING, |
21
|
6 |
|
'/^type$/i' => Element::ATTR_CI_STRING, |
22
|
|
|
'/^sizes$/i' => Element::ATTR_CI_STRING |
23
|
6 |
|
); |
24
|
|
|
|
25
|
6 |
|
return array_merge( |
26
|
6 |
|
$linkAllowedAttributes, |
27
|
6 |
|
parent::getAllowedAttributes() |
28
|
6 |
|
); |
29
|
|
|
} |
30
|
|
|
|
31
|
6 |
|
protected function removeInvalidSelf(LoggerInterface $logger) |
32
|
|
|
{ |
33
|
|
|
// Must have "href" attribute. |
34
|
6 |
|
if (!$this->hasAttribute('href')) { |
35
|
1 |
|
$logger->debug($this . ' requires "href" attribute.'); |
36
|
|
|
|
37
|
1 |
|
return true; |
38
|
|
|
} |
39
|
|
|
|
40
|
|
|
// Must have either "rel" or "itemprop" attribute, but not both. |
41
|
5 |
|
$attrCount = 0; |
42
|
5 |
|
foreach ($this->attributes as $key => $value) { |
43
|
5 |
|
if ($key == 'rel' || $key == 'itemprop') { |
44
|
4 |
|
++$attrCount; |
45
|
4 |
|
} |
46
|
|
|
|
47
|
5 |
|
if ($attrCount > 1) { |
48
|
|
|
// If both, then we don't know which one should be kept, |
49
|
|
|
// so we recommend to delete the entire element. |
50
|
1 |
|
$logger->debug($this . ' requires either "rel" or "itemprop" attribute, but not both.'); |
51
|
|
|
|
52
|
1 |
|
return true; |
53
|
|
|
} |
54
|
5 |
|
} |
55
|
|
|
|
56
|
4 |
|
if ($attrCount == 0) { |
57
|
1 |
|
$logger->debug($this . ' requires either "rel" or "itemprop" attribute.'); |
58
|
|
|
|
59
|
1 |
|
return true; |
60
|
|
|
} |
61
|
|
|
|
62
|
|
|
// If inside "body" element, then we check if allowed. |
63
|
3 |
|
$body = new Body($this->configuration, 'body'); |
64
|
3 |
|
if ($this->hasAncestor($body) && !$this->isAllowedInBody()) { |
65
|
1 |
|
$logger->debug($this . ' does not have the correct attributes to be allowed inside the "body" element.'); |
66
|
|
|
|
67
|
1 |
|
return true; |
68
|
|
|
} |
69
|
|
|
|
70
|
2 |
|
return false; |
71
|
|
|
} |
72
|
|
|
|
73
|
|
|
/** |
74
|
|
|
* Is this element allowed inside a body element? |
75
|
|
|
* |
76
|
|
|
* https://html.spec.whatwg.org/multipage/semantics.html#allowed-in-the-body |
77
|
|
|
* |
78
|
|
|
* @return bool True if allowed. |
79
|
|
|
*/ |
80
|
2 |
|
public function isAllowedInBody() |
81
|
|
|
{ |
82
|
2 |
|
if ($this->hasAttribute('itemprop')) { |
83
|
1 |
|
return true; |
84
|
|
|
} |
85
|
|
|
|
86
|
2 |
|
if ($this->hasAttribute('rel') && |
87
|
2 |
|
($this->attributes['rel'] == 'pingback' || |
88
|
2 |
|
$this->attributes['rel'] == 'prefetch' || |
89
|
2 |
|
$this->attributes['rel'] == 'stylesheet')) { |
90
|
1 |
|
return true; |
91
|
|
|
} |
92
|
|
|
|
93
|
1 |
|
return false; |
94
|
|
|
} |
95
|
|
|
} |
96
|
|
|
|