1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Groundskeeper\Tokens; |
4
|
|
|
|
5
|
|
|
use Groundskeeper\Configuration; |
6
|
|
|
use Psr\Log\LoggerInterface; |
7
|
|
|
|
8
|
|
|
class Attribute |
9
|
|
|
{ |
10
|
|
|
const BOOL = 'ci_boo'; // boolean |
11
|
|
|
const CI_ENUM = 'ci_enu'; // case-insensitive enumeration |
12
|
|
|
const CI_SSENUM = 'ci_sse'; // case-insensitive space-separated enumeration |
13
|
|
|
const INT = 'ci_int'; // integer |
14
|
|
|
const JS = 'cs_jsc'; // javascript |
15
|
|
|
const CI_STRING = 'ci_str'; // case-insensitive string |
16
|
|
|
const CS_STRING = 'cs_str'; // case-sensitive string |
17
|
|
|
const URI = 'cs_uri'; // uri |
18
|
|
|
const UNKNOWN = 'cs_unk'; // unknown |
19
|
|
|
|
20
|
|
|
/** @var string */ |
21
|
|
|
private $name; |
22
|
|
|
|
23
|
|
|
/** @var string */ |
24
|
|
|
private $type; |
25
|
|
|
|
26
|
|
|
/** @var null|mixed */ |
27
|
|
|
private $value; |
28
|
|
|
|
29
|
|
|
/** @var bool */ |
30
|
|
|
private $isStandard; |
31
|
|
|
|
32
|
|
|
/** |
33
|
|
|
* Constructor |
34
|
|
|
*/ |
35
|
117 |
|
public function __construct(string $name, $value = null) |
36
|
|
|
{ |
37
|
117 |
|
$this->name = $name; |
38
|
117 |
|
$this->type = self::UNKNOWN; |
39
|
117 |
|
$this->value = $value; |
40
|
117 |
|
$this->isStandard = false; |
41
|
117 |
|
} |
42
|
|
|
|
43
|
|
|
/** |
44
|
|
|
* Getter for 'name'. |
45
|
|
|
*/ |
46
|
94 |
|
public function getName() : string |
47
|
|
|
{ |
48
|
94 |
|
return $this->name; |
49
|
|
|
} |
50
|
|
|
|
51
|
|
|
/** |
52
|
|
|
* Getter for 'type'. |
53
|
|
|
*/ |
54
|
2 |
|
public function getType() : string |
55
|
|
|
{ |
56
|
2 |
|
return $this->type; |
57
|
|
|
} |
58
|
|
|
|
59
|
|
|
/** |
60
|
|
|
* Chainable setter for 'type'. |
61
|
|
|
*/ |
62
|
106 |
|
public function setType(string $type) |
63
|
|
|
{ |
64
|
106 |
|
$typeEnum = mb_substr($type, 0, 6); |
65
|
106 |
|
if ($typeEnum !== self::BOOL && |
66
|
106 |
|
$typeEnum !== self::CI_ENUM && |
67
|
106 |
|
$typeEnum !== self::CI_SSENUM && |
68
|
106 |
|
$typeEnum !== self::INT && |
69
|
106 |
|
$typeEnum !== self::JS && |
70
|
106 |
|
$typeEnum !== self::CI_STRING && |
71
|
106 |
|
$typeEnum !== self::CS_STRING && |
72
|
106 |
|
$typeEnum !== self::URI && |
73
|
106 |
|
$typeEnum !== self::UNKNOWN) { |
74
|
1 |
|
throw new \InvalidArgumentException('Invalid attribute type: ' . $typeEnum); |
75
|
|
|
} |
76
|
|
|
|
77
|
106 |
|
$this->type = $type; |
78
|
106 |
|
} |
79
|
|
|
|
80
|
|
|
/** |
81
|
|
|
* Getter for 'value'. |
82
|
|
|
*/ |
83
|
19 |
|
public function getValue() |
84
|
|
|
{ |
85
|
19 |
|
return $this->value; |
86
|
|
|
} |
87
|
|
|
|
88
|
|
|
/** |
89
|
|
|
* Getter for 'isStandard'. |
90
|
|
|
*/ |
91
|
2 |
|
public function getIsStandard() : bool |
92
|
|
|
{ |
93
|
2 |
|
return $this->isStandard; |
94
|
|
|
} |
95
|
|
|
|
96
|
|
|
/** |
97
|
|
|
* Chainable setter for 'isStandard'. |
98
|
|
|
*/ |
99
|
106 |
|
public function setIsStandard(bool $isStandard) |
100
|
|
|
{ |
101
|
106 |
|
$this->isStandard = $isStandard; |
102
|
106 |
|
} |
103
|
|
|
|
104
|
105 |
|
public function clean(Configuration $configuration, Element $element, LoggerInterface $logger) |
105
|
|
|
{ |
106
|
105 |
|
if ($configuration->get('clean-strategy') === Configuration::CLEAN_STRATEGY_NONE) { |
107
|
1 |
|
return true; |
108
|
|
|
} |
109
|
|
|
|
110
|
|
|
// Remove non-standard attributes. |
111
|
104 |
View Code Duplication |
if ($configuration->get( |
|
|
|
|
112
|
104 |
|
'clean-strategy' |
113
|
104 |
|
) !== Configuration::CLEAN_STRATEGY_LENIENT && $this->isStandard === false) { |
114
|
10 |
|
$logger->debug('Removing non-standard attribute "' . $this->name . '" from ' . $element); |
115
|
|
|
|
116
|
10 |
|
return false; |
117
|
|
|
} |
118
|
|
|
|
119
|
|
|
// Validate attribute value. |
120
|
103 |
|
list($caseSensitivity, $attributeType) = explode('_', $this->type); |
121
|
|
|
|
122
|
|
|
// Standard is case-insensitive attribute values should be lower case. |
123
|
103 |
|
if ($caseSensitivity === 'ci' && $this->value !== true) { |
124
|
32 |
|
$newValue = strtolower($this->value); |
125
|
32 |
View Code Duplication |
if ($newValue !== $this->value) { |
|
|
|
|
126
|
4 |
|
$logger->debug( |
127
|
4 |
|
'Within ' . $element . ', the value for the attribute "' . $this->name . '" is case-insensitive. The value has been converted to lower case.' |
128
|
|
|
); |
129
|
4 |
|
$this->value = $newValue; |
130
|
|
|
} |
131
|
|
|
} |
132
|
|
|
|
133
|
|
|
// Validate value types. |
134
|
|
|
switch ($attributeType) { |
135
|
103 |
View Code Duplication |
case 'boo': // boolean |
|
|
|
|
136
|
7 |
|
$cleanResult = $this->cleanAttributeBoolean( |
137
|
7 |
|
$configuration, |
138
|
7 |
|
$element, |
139
|
7 |
|
$logger |
140
|
|
|
); |
141
|
7 |
|
if ($configuration->get('clean-strategy') !== Configuration::CLEAN_STRATEGY_LENIENT) { |
142
|
6 |
|
return $cleanResult; |
143
|
|
|
} |
144
|
|
|
|
145
|
5 |
|
break; |
146
|
|
|
|
147
|
98 |
View Code Duplication |
case 'int': // integer |
|
|
|
|
148
|
13 |
|
$cleanResult = $this->cleanAttributeInteger( |
149
|
13 |
|
$configuration, |
150
|
13 |
|
$element, |
151
|
13 |
|
$logger |
152
|
|
|
); |
153
|
13 |
|
if ($configuration->get('clean-strategy') !== Configuration::CLEAN_STRATEGY_LENIENT) { |
154
|
13 |
|
return $cleanResult; |
155
|
|
|
} |
156
|
|
|
|
157
|
9 |
|
break; |
158
|
|
|
|
159
|
90 |
View Code Duplication |
case 'str': // string |
|
|
|
|
160
|
68 |
|
$cleanResult = $this->cleanAttributeString( |
161
|
68 |
|
$configuration, |
162
|
68 |
|
$element, |
163
|
68 |
|
$logger |
164
|
|
|
); |
165
|
68 |
|
if ($configuration->get('clean-strategy') !== Configuration::CLEAN_STRATEGY_LENIENT) { |
166
|
66 |
|
return $cleanResult; |
167
|
|
|
} |
168
|
|
|
|
169
|
55 |
|
break; |
170
|
|
|
|
171
|
45 |
View Code Duplication |
case 'uri': // URI |
|
|
|
|
172
|
36 |
|
$cleanResult = $this->cleanAttributeUri( |
173
|
36 |
|
$configuration, |
174
|
36 |
|
$element, |
175
|
36 |
|
$logger |
176
|
|
|
); |
177
|
36 |
|
if ($configuration->get('clean-strategy') !== Configuration::CLEAN_STRATEGY_LENIENT) { |
178
|
31 |
|
return $cleanResult; |
179
|
|
|
} |
180
|
|
|
|
181
|
34 |
|
break; |
182
|
|
|
} |
183
|
|
|
|
184
|
82 |
|
return true; |
185
|
|
|
} |
186
|
|
|
|
187
|
7 |
View Code Duplication |
private function cleanAttributeBoolean(Configuration $configuration, Element $element, LoggerInterface $logger) |
|
|
|
|
188
|
|
|
{ |
189
|
7 |
|
if ($this->value !== true) { |
190
|
3 |
|
$logger->debug( |
191
|
3 |
|
'Within ' . $element . ', the attribute "' . $this->name . |
192
|
3 |
|
'" is a boolean attribute. The value has been removed.' |
193
|
|
|
); |
194
|
3 |
|
$this->value = true; |
195
|
|
|
} |
196
|
|
|
|
197
|
7 |
|
return true; |
198
|
|
|
} |
199
|
|
|
|
200
|
13 |
|
private function cleanAttributeInteger(Configuration $configuration, Element $element, LoggerInterface $logger) |
201
|
|
|
{ |
202
|
13 |
|
if ($this->value === true || $this->value == '') { |
203
|
2 |
View Code Duplication |
if ($configuration->get('clean-strategy') !== Configuration::CLEAN_STRATEGY_LENIENT) { |
|
|
|
|
204
|
2 |
|
$logger->debug( |
205
|
2 |
|
'Within ' . $element . ', the value for the attribute "' . $this->name . '" is required to be an positive, non-zero integer. The value is invalid, therefore the attribute has been removed.' |
206
|
|
|
); |
207
|
|
|
} |
208
|
|
|
|
209
|
2 |
|
return false; |
210
|
|
|
} |
211
|
|
|
|
212
|
12 |
|
if (!is_int($this->value)) { |
213
|
12 |
|
$originalValue = (string) $this->value; |
214
|
12 |
|
$this->value = (int) $this->value; |
215
|
12 |
View Code Duplication |
if ($originalValue !== ((string) $this->value)) { |
|
|
|
|
216
|
3 |
|
$logger->debug( |
217
|
3 |
|
'Within ' . $element . ', the value for the attribute "' . $this->name . '" is required to be an positive, non-zero integer. The value has been converted to an integer.' |
218
|
|
|
); |
219
|
|
|
} |
220
|
|
|
} |
221
|
|
|
|
222
|
12 |
View Code Duplication |
if ($this->value <= 0 && $configuration->get('clean-strategy') !== Configuration::CLEAN_STRATEGY_LENIENT) { |
|
|
|
|
223
|
3 |
|
$logger->debug( |
224
|
3 |
|
'Within ' . $element . ', the value for the attribute "' . $this->value . '" is required to be an positive, non-zero integer. The value is invalid, therefore the attribute has been removed.' |
225
|
|
|
); |
226
|
|
|
|
227
|
3 |
|
return false; |
228
|
|
|
} |
229
|
|
|
|
230
|
10 |
|
return true; |
231
|
|
|
} |
232
|
|
|
|
233
|
68 |
View Code Duplication |
private function cleanAttributeString(Configuration $configuration, Element $element, LoggerInterface $logger) |
|
|
|
|
234
|
|
|
{ |
235
|
68 |
|
if ($this->value === true) { |
236
|
1 |
|
$logger->debug( |
237
|
1 |
|
'Within ' . $element . ', the attribute "' . $this->name . '" requires a string value. The value is missing, therefore the attribute value is set to the attribute name.' |
238
|
|
|
); |
239
|
|
|
|
240
|
1 |
|
$this->value = $this->name; |
241
|
|
|
} |
242
|
|
|
|
243
|
68 |
|
return true; |
244
|
|
|
} |
245
|
|
|
|
246
|
36 |
View Code Duplication |
private function cleanAttributeUri(Configuration $configuration, Element $element, LoggerInterface $logger) |
|
|
|
|
247
|
|
|
{ |
248
|
|
|
// Check for empty attribute. |
249
|
36 |
|
if ($this->value === true) { |
250
|
1 |
|
$logger->debug( |
251
|
1 |
|
'Within ' . $element . ', the attribute "' . $this->name . '" requires a URI. The value is invalid, therefore the attribute has been removed.' |
252
|
|
|
); |
253
|
|
|
|
254
|
1 |
|
return false; |
255
|
|
|
} |
256
|
|
|
|
257
|
|
|
/// @todo |
258
|
|
|
|
259
|
35 |
|
return true; |
260
|
|
|
} |
261
|
|
|
|
262
|
95 |
|
public function __toString() |
263
|
|
|
{ |
264
|
95 |
|
$output = $this->name; |
265
|
95 |
|
if ($this->value === true) { |
266
|
6 |
|
return $output; |
267
|
|
|
} |
268
|
|
|
|
269
|
|
|
/// @todo Escape double quotes in value. |
270
|
94 |
|
return $output . '="' . $this->value . '"'; |
271
|
|
|
} |
272
|
|
|
} |
273
|
|
|
|
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.