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