1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Groundskeeper\Tokens; |
4
|
|
|
|
5
|
|
|
use Groundskeeper\Configuration; |
6
|
|
|
use Psr\Log\LoggerInterface; |
7
|
|
|
|
8
|
|
|
class Element extends AbstractToken implements Cleanable, ContainsChildren, Removable |
9
|
|
|
{ |
10
|
|
|
/** @var array */ |
11
|
|
|
protected $attributes; |
12
|
|
|
|
13
|
|
|
/** @var array[Token] */ |
14
|
|
|
protected $children; |
15
|
|
|
|
16
|
|
|
/** @var string */ |
17
|
|
|
private $name; |
18
|
|
|
|
19
|
|
|
/** |
20
|
|
|
* Constructor |
21
|
|
|
*/ |
22
|
105 |
|
public function __construct(Configuration $configuration, $name, array $attributes = array()) |
23
|
|
|
{ |
24
|
105 |
|
parent::__construct($configuration); |
25
|
|
|
|
26
|
105 |
|
$this->attributes = array(); |
27
|
105 |
|
foreach ($attributes as $key => $value) { |
28
|
57 |
|
$this->addAttribute($key, $value); |
29
|
105 |
|
} |
30
|
|
|
|
31
|
105 |
|
$this->children = array(); |
32
|
|
|
|
33
|
105 |
|
if (!is_string($name)) { |
34
|
1 |
|
throw new \InvalidArgumentException('Element name must be string type.'); |
35
|
3 |
|
} |
36
|
|
|
|
37
|
104 |
|
$this->name = trim(strtolower($name)); |
38
|
104 |
|
} |
39
|
|
|
|
40
|
|
|
/** |
41
|
|
|
* Getter for 'attributes'. |
42
|
|
|
*/ |
43
|
4 |
|
public function getAttributes() |
44
|
|
|
{ |
45
|
2 |
|
$attributeArray = array(); |
46
|
4 |
|
foreach ($this->attributes as $attribute) { |
47
|
1 |
|
$attributeArray[$attribute->getName()] = $attribute->getValue(); |
48
|
2 |
|
} |
49
|
|
|
|
50
|
2 |
|
return $attributeArray; |
51
|
2 |
|
} |
52
|
|
|
|
53
|
4 |
|
public function getAttribute($key) |
54
|
|
|
{ |
55
|
4 |
|
if (!$this->hasAttribute($key)) { |
56
|
1 |
|
throw new \InvalidArgumentException('Invalid attribute key: ' . $key); |
57
|
|
|
} |
58
|
|
|
|
59
|
3 |
|
$attributeObject = $this->attributes[$key]; |
60
|
|
|
|
61
|
3 |
|
return $attributeObject->getValue(); |
62
|
|
|
} |
63
|
|
|
|
64
|
|
|
/** |
65
|
|
|
* Hasser for 'attributes'. |
66
|
|
|
* |
67
|
|
|
* @param string $key |
68
|
|
|
* |
69
|
|
|
* @return bool True if the attribute is present. |
70
|
|
|
*/ |
71
|
24 |
|
public function hasAttribute($key) |
72
|
|
|
{ |
73
|
24 |
|
return array_key_exists($key, $this->attributes); |
74
|
|
|
} |
75
|
|
|
|
76
|
59 |
|
public function addAttribute($key, $value) |
77
|
|
|
{ |
78
|
59 |
|
$key = trim(strtolower($key)); |
79
|
58 |
|
if ($key == '') { |
80
|
1 |
|
throw new \InvalidArgumentException('Invalid empty attribute key.'); |
81
|
1 |
|
} |
82
|
|
|
|
83
|
57 |
|
$attributeParameters = $this->getAttributeParameters($key); |
84
|
57 |
|
$isStandard = true; |
85
|
57 |
View Code Duplication |
if (empty($attributeParameters)) { |
|
|
|
|
86
|
|
|
$attributeParameters = array( |
87
|
10 |
|
'name' => $key, |
88
|
10 |
|
'regex' => '/\S*/i', |
89
|
|
|
'valueType' => Attribute::CS_STRING |
90
|
11 |
|
); |
91
|
10 |
|
$isStandard = false; |
92
|
10 |
|
} |
93
|
|
|
|
94
|
57 |
|
$this->attributes[$key] = new Attribute( |
95
|
57 |
|
$key, |
96
|
57 |
|
$value, |
97
|
57 |
|
$attributeParameters['valueType'], |
|
|
|
|
98
|
|
|
$isStandard |
99
|
57 |
|
); |
100
|
|
|
|
101
|
57 |
|
return $this; |
102
|
|
|
} |
103
|
|
|
|
104
|
8 |
|
public function removeAttribute($key) |
105
|
|
|
{ |
106
|
5 |
|
$key = trim(strtolower($key)); |
107
|
2 |
|
if (isset($this->attributes[$key])) { |
108
|
2 |
|
unset($this->attributes[$key]); |
109
|
|
|
|
110
|
2 |
|
return true; |
111
|
|
|
} |
112
|
|
|
|
113
|
2 |
|
return false; |
114
|
8 |
|
} |
115
|
|
|
|
116
|
|
|
/** |
117
|
|
|
* Required by ContainsChildren interface. |
118
|
|
|
*/ |
119
|
3 |
|
public function getChildren() |
120
|
|
|
{ |
121
|
3 |
|
return $this->children; |
122
|
|
|
} |
123
|
|
|
|
124
|
|
|
/** |
125
|
|
|
* Required by ContainsChildren interface. |
126
|
|
|
*/ |
127
|
1 |
|
public function hasChild(Token $token) |
128
|
|
|
{ |
129
|
1 |
|
return array_search($token, $this->children, true) !== false; |
130
|
|
|
} |
131
|
|
|
|
132
|
|
|
/** |
133
|
|
|
* Required by ContainsChildren interface. |
134
|
|
|
*/ |
135
|
89 |
|
public function appendChild(Token $token) |
136
|
|
|
{ |
137
|
89 |
|
$token->setParent($this); |
138
|
89 |
|
$this->children[] = $token; |
139
|
|
|
|
140
|
89 |
|
return $this; |
141
|
|
|
} |
142
|
|
|
|
143
|
|
|
/** |
144
|
|
|
* Required by ContainsChildren interface. |
145
|
|
|
*/ |
146
|
5 |
|
public function prependChild(Token $token) |
147
|
|
|
{ |
148
|
5 |
|
$token->setParent($this); |
149
|
5 |
|
array_unshift($this->children, $token); |
150
|
|
|
|
151
|
5 |
|
return $this; |
152
|
|
|
} |
153
|
|
|
|
154
|
|
|
/** |
155
|
|
|
* Required by the ContainsChildren interface. |
156
|
|
|
*/ |
157
|
27 |
View Code Duplication |
public function removeChild(Token $token) |
|
|
|
|
158
|
|
|
{ |
159
|
27 |
|
$key = array_search($token, $this->children, true); |
160
|
27 |
|
if ($key !== false) { |
161
|
27 |
|
unset($this->children[$key]); |
162
|
|
|
|
163
|
27 |
|
return true; |
164
|
|
|
} |
165
|
|
|
|
166
|
1 |
|
return false; |
167
|
|
|
} |
168
|
|
|
|
169
|
|
|
/** |
170
|
|
|
* Getter for 'name'. |
171
|
|
|
*/ |
172
|
85 |
|
public function getName() |
173
|
|
|
{ |
174
|
85 |
|
return $this->name; |
175
|
|
|
} |
176
|
|
|
|
177
|
|
|
/** |
178
|
|
|
* Required by the Cleanable interface. |
179
|
|
|
*/ |
180
|
88 |
|
public function clean(LoggerInterface $logger) |
181
|
|
|
{ |
182
|
88 |
|
if ($this->configuration->get('clean-strategy') == Configuration::CLEAN_STRATEGY_NONE) { |
183
|
1 |
|
return true; |
184
|
|
|
} |
185
|
|
|
|
186
|
|
|
// Assign attributes to the attributes. (Soooo meta ....) |
187
|
87 |
|
foreach ($this->attributes as $attribute) { |
188
|
49 |
|
$attributeParameters = $this->getAttributeParameters( |
189
|
49 |
|
$attribute->getName() |
190
|
49 |
|
); |
191
|
49 |
|
$isStandard = true; |
192
|
49 |
View Code Duplication |
if (empty($attributeParameters)) { |
|
|
|
|
193
|
|
|
$attributeParameters = array( |
194
|
8 |
|
'name' => $attribute->getName(), |
195
|
8 |
|
'regex' => '/\S*/i', |
196
|
|
|
'valueType' => Attribute::CS_STRING |
197
|
8 |
|
); |
198
|
8 |
|
$isStandard = false; |
199
|
8 |
|
} |
200
|
|
|
|
201
|
49 |
|
$attribute->setType($attributeParameters['valueType']); |
202
|
49 |
|
$attribute->setIsStandard($isStandard); |
203
|
87 |
|
} |
204
|
|
|
|
205
|
|
|
// Clean attributes. |
206
|
87 |
|
foreach ($this->attributes as $attribute) { |
207
|
49 |
|
$attributeCleanResult = $attribute->clean( |
208
|
49 |
|
$this->configuration, |
209
|
49 |
|
$this, |
210
|
|
|
$logger |
211
|
49 |
|
); |
212
|
49 |
|
if (!$attributeCleanResult && $this->configuration->get('clean-strategy') !== Configuration::CLEAN_STRATEGY_LENIENT) { |
213
|
9 |
|
unset($this->attributes[$attribute->getName()]); |
214
|
9 |
|
} |
215
|
87 |
|
} |
216
|
|
|
|
217
|
|
|
// Fix self (if possible) |
|
|
|
|
218
|
87 |
|
$this->fixSelf($logger); |
219
|
|
|
|
220
|
|
|
// Remove self or children? |
221
|
87 |
|
if ($this->configuration->get('clean-strategy') !== Configuration::CLEAN_STRATEGY_LENIENT) { |
222
|
|
|
// Remove self? |
223
|
87 |
|
if ($this->removeInvalidSelf($logger)) { |
224
|
19 |
|
return false; |
225
|
|
|
} |
226
|
|
|
|
227
|
|
|
// Remove children? |
228
|
87 |
|
$this->removeInvalidChildren($logger); |
229
|
87 |
|
} |
230
|
|
|
|
231
|
|
|
// Clean children. |
232
|
87 |
|
return AbstractToken::cleanChildTokens( |
233
|
87 |
|
$this->configuration, |
234
|
87 |
|
$this->children, |
235
|
|
|
$logger |
236
|
87 |
|
); |
237
|
|
|
} |
238
|
|
|
|
239
|
81 |
|
protected function fixSelf(LoggerInterface $logger) |
240
|
|
|
{ |
241
|
81 |
|
} |
242
|
|
|
|
243
|
78 |
|
protected function removeInvalidChildren(LoggerInterface $logger) |
244
|
|
|
{ |
245
|
78 |
|
} |
246
|
|
|
|
247
|
80 |
|
protected function removeInvalidSelf(LoggerInterface $logger) |
248
|
|
|
{ |
249
|
80 |
|
return false; |
250
|
|
|
} |
251
|
|
|
|
252
|
57 |
|
protected function getAllowedAttributes() |
253
|
|
|
{ |
254
|
|
|
return array( |
255
|
|
|
// Global Attributes |
256
|
57 |
|
'/^accesskey$/i' => Attribute::CS_STRING, |
257
|
57 |
|
'/^class$/i' => Attribute::CS_STRING, |
258
|
57 |
|
'/^contenteditable$/i' => Attribute::CS_STRING, |
259
|
57 |
|
'/^contextmenu$/i' => Attribute::CS_STRING, |
260
|
57 |
|
'/^data-\S/i' => Attribute::CS_STRING, |
261
|
57 |
|
'/^dir$/i' => Attribute::CI_ENUM . '("ltr","rtl"|"ltr")', |
262
|
57 |
|
'/^draggable$/i' => Attribute::CS_STRING, |
263
|
57 |
|
'/^dropzone$/i' => Attribute::CS_STRING, |
264
|
57 |
|
'/^hidden$/i' => Attribute::CS_STRING, |
265
|
57 |
|
'/^id$/i' => Attribute::CS_STRING, |
266
|
57 |
|
'/^is$/i' => Attribute::CS_STRING, |
267
|
57 |
|
'/^itemid$/i' => Attribute::CS_STRING, |
268
|
57 |
|
'/^itemprop$/i' => Attribute::CS_STRING, |
269
|
57 |
|
'/^itemref$/i' => Attribute::CS_STRING, |
270
|
57 |
|
'/^itemscope$/i' => Attribute::CS_STRING, |
271
|
57 |
|
'/^itemtype$/i' => Attribute::CS_STRING, |
272
|
57 |
|
'/^lang$/i' => Attribute::CI_STRING, |
273
|
57 |
|
'/^slot$/i' => Attribute::CS_STRING, |
274
|
57 |
|
'/^spellcheck$/i' => Attribute::CS_STRING, |
275
|
57 |
|
'/^style$/i' => Attribute::CS_STRING, |
276
|
57 |
|
'/^tabindex$/i' => Attribute::CS_STRING, |
277
|
57 |
|
'/^title$/i' => Attribute::CS_STRING, |
278
|
57 |
|
'/^translate$/i' => Attribute::CI_ENUM . '("yes","no",""|"yes")', |
279
|
|
|
|
280
|
|
|
// Event Handler Content Attributes |
281
|
|
|
// https://html.spec.whatwg.org/multipage/webappapis.html#event-handler-content-attributes |
282
|
57 |
|
'/^onabort$/i' => Attribute::JS, |
283
|
57 |
|
'/^onautocomplete$/i' => Attribute::JS, |
284
|
57 |
|
'/^onautocompleteerror$/i' => Attribute::JS, |
285
|
57 |
|
'/^onblur$/i' => Attribute::JS, |
286
|
57 |
|
'/^oncancel$/i' => Attribute::JS, |
287
|
57 |
|
'/^oncanplay$/i' => Attribute::JS, |
288
|
57 |
|
'/^oncanplaythrough$/i' => Attribute::JS, |
289
|
57 |
|
'/^onchange$/i' => Attribute::JS, |
290
|
57 |
|
'/^onclick$/i' => Attribute::JS, |
291
|
57 |
|
'/^onclose$/i' => Attribute::JS, |
292
|
57 |
|
'/^oncontextmenu$/i' => Attribute::JS, |
293
|
57 |
|
'/^oncuechange$/i' => Attribute::JS, |
294
|
57 |
|
'/^ondblclick$/i' => Attribute::JS, |
295
|
57 |
|
'/^ondrag$/i' => Attribute::JS, |
296
|
57 |
|
'/^ondragend$/i' => Attribute::JS, |
297
|
57 |
|
'/^ondragenter$/i' => Attribute::JS, |
298
|
57 |
|
'/^ondragexit$/i' => Attribute::JS, |
299
|
57 |
|
'/^ondragleave$/i' => Attribute::JS, |
300
|
57 |
|
'/^ondragover$/i' => Attribute::JS, |
301
|
57 |
|
'/^ondragstart$/i' => Attribute::JS, |
302
|
57 |
|
'/^ondrop$/i' => Attribute::JS, |
303
|
57 |
|
'/^ondurationchange$/i' => Attribute::JS, |
304
|
57 |
|
'/^onemptied$/i' => Attribute::JS, |
305
|
57 |
|
'/^onended$/i' => Attribute::JS, |
306
|
57 |
|
'/^onerror$/i' => Attribute::JS, |
307
|
57 |
|
'/^onfocus$/i' => Attribute::JS, |
308
|
57 |
|
'/^oninput$/i' => Attribute::JS, |
309
|
57 |
|
'/^oninvalid$/i' => Attribute::JS, |
310
|
57 |
|
'/^onkeydown$/i' => Attribute::JS, |
311
|
57 |
|
'/^onkeypress$/i' => Attribute::JS, |
312
|
57 |
|
'/^onkeyup$/i' => Attribute::JS, |
313
|
57 |
|
'/^onload$/i' => Attribute::JS, |
314
|
57 |
|
'/^onloadeddata$/i' => Attribute::JS, |
315
|
57 |
|
'/^onloadedmetadata$/i' => Attribute::JS, |
316
|
57 |
|
'/^onloadstart$/i' => Attribute::JS, |
317
|
57 |
|
'/^onmousedown$/i' => Attribute::JS, |
318
|
57 |
|
'/^onmouseenter$/i' => Attribute::JS, |
319
|
57 |
|
'/^onmouseleave$/i' => Attribute::JS, |
320
|
57 |
|
'/^onmousemove$/i' => Attribute::JS, |
321
|
57 |
|
'/^onmouseout$/i' => Attribute::JS, |
322
|
57 |
|
'/^onmouseover$/i' => Attribute::JS, |
323
|
57 |
|
'/^onmouseup$/i' => Attribute::JS, |
324
|
57 |
|
'/^onwheel$/i' => Attribute::JS, |
325
|
57 |
|
'/^onpause$/i' => Attribute::JS, |
326
|
57 |
|
'/^onplay$/i' => Attribute::JS, |
327
|
57 |
|
'/^onplaying$/i' => Attribute::JS, |
328
|
57 |
|
'/^onprogress$/i' => Attribute::JS, |
329
|
57 |
|
'/^onratechange$/i' => Attribute::JS, |
330
|
57 |
|
'/^onreset$/i' => Attribute::JS, |
331
|
57 |
|
'/^onresize$/i' => Attribute::JS, |
332
|
57 |
|
'/^onscroll$/i' => Attribute::JS, |
333
|
57 |
|
'/^onseeked$/i' => Attribute::JS, |
334
|
57 |
|
'/^onseeking$/i' => Attribute::JS, |
335
|
57 |
|
'/^onselect$/i' => Attribute::JS, |
336
|
57 |
|
'/^onshow$/i' => Attribute::JS, |
337
|
57 |
|
'/^onstalled$/i' => Attribute::JS, |
338
|
57 |
|
'/^onsubmit$/i' => Attribute::JS, |
339
|
57 |
|
'/^onsuspend$/i' => Attribute::JS, |
340
|
57 |
|
'/^ontimeupdate$/i' => Attribute::JS, |
341
|
57 |
|
'/^ontoggle$/i' => Attribute::JS, |
342
|
57 |
|
'/^onvolumechange$/i' => Attribute::JS, |
343
|
57 |
|
'/^onwaiting$/i' => Attribute::JS, |
344
|
|
|
|
345
|
|
|
// WAI-ARIA |
346
|
|
|
// https://w3c.github.io/aria/aria/aria.html |
347
|
57 |
|
'/^role$/i' => Attribute::CI_STRING, |
348
|
|
|
|
349
|
|
|
// ARIA global states and properties |
350
|
57 |
|
'/^aria-atomic$/i' => Attribute::CS_STRING, |
351
|
57 |
|
'/^aria-busy$/i' => Attribute::CS_STRING, |
352
|
57 |
|
'/^aria-controls$/i' => Attribute::CS_STRING, |
353
|
57 |
|
'/^aria-current$/i' => Attribute::CS_STRING, |
354
|
57 |
|
'/^aria-describedby$/i' => Attribute::CS_STRING, |
355
|
57 |
|
'/^aria-details$/i' => Attribute::CS_STRING, |
356
|
57 |
|
'/^aria-disabled$/i' => Attribute::CS_STRING, |
357
|
57 |
|
'/^aria-dropeffect$/i' => Attribute::CS_STRING, |
358
|
57 |
|
'/^aria-errormessage$/i' => Attribute::CS_STRING, |
359
|
57 |
|
'/^aria-flowto$/i' => Attribute::CS_STRING, |
360
|
57 |
|
'/^aria-grabbed$/i' => Attribute::CS_STRING, |
361
|
57 |
|
'/^aria-haspopup$/i' => Attribute::CS_STRING, |
362
|
57 |
|
'/^aria-hidden$/i' => Attribute::CS_STRING, |
363
|
57 |
|
'/^aria-invalid$/i' => Attribute::CS_STRING, |
364
|
57 |
|
'/^aria-label$/i' => Attribute::CS_STRING, |
365
|
57 |
|
'/^aria-labelledby$/i' => Attribute::CS_STRING, |
366
|
57 |
|
'/^aria-live$/i' => Attribute::CS_STRING, |
367
|
57 |
|
'/^aria-owns$/i' => Attribute::CS_STRING, |
368
|
57 |
|
'/^aria-relevant$/i' => Attribute::CS_STRING, |
369
|
57 |
|
'/^aria-roledescription$/i' => Attribute::CS_STRING, |
370
|
|
|
|
371
|
|
|
// ARIA widget attributes |
372
|
57 |
|
'/^aria-autocomplete$/i' => Attribute::CS_STRING, |
373
|
57 |
|
'/^aria-checked$/i' => Attribute::CS_STRING, |
374
|
57 |
|
'/^aria-expanded$/i' => Attribute::CS_STRING, |
375
|
57 |
|
'/^aria-level$/i' => Attribute::CS_STRING, |
376
|
57 |
|
'/^aria-modal$/i' => Attribute::CS_STRING, |
377
|
57 |
|
'/^aria-multiline$/i' => Attribute::CS_STRING, |
378
|
57 |
|
'/^aria-multiselectable$/i' => Attribute::CS_STRING, |
379
|
57 |
|
'/^aria-orientation$/i' => Attribute::CS_STRING, |
380
|
57 |
|
'/^aria-placeholder$/i' => Attribute::CS_STRING, |
381
|
57 |
|
'/^aria-pressed$/i' => Attribute::CS_STRING, |
382
|
57 |
|
'/^aria-readonly$/i' => Attribute::CS_STRING, |
383
|
57 |
|
'/^aria-required$/i' => Attribute::CS_STRING, |
384
|
57 |
|
'/^aria-selected$/i' => Attribute::CS_STRING, |
385
|
57 |
|
'/^aria-sort$/i' => Attribute::CS_STRING, |
386
|
57 |
|
'/^aria-valuemax$/i' => Attribute::CS_STRING, |
387
|
57 |
|
'/^aria-valuemin$/i' => Attribute::CS_STRING, |
388
|
57 |
|
'/^aria-valuenow$/i' => Attribute::CS_STRING, |
389
|
57 |
|
'/^aria-valuetext$/i' => Attribute::CS_STRING, |
390
|
|
|
|
391
|
|
|
// ARIA relationship attributes |
392
|
57 |
|
'/^aria-activedescendant$/i' => Attribute::CS_STRING, |
393
|
57 |
|
'/^aria-colcount$/i' => Attribute::CS_STRING, |
394
|
57 |
|
'/^aria-colindex$/i' => Attribute::CS_STRING, |
395
|
57 |
|
'/^aria-colspan$/i' => Attribute::CS_STRING, |
396
|
57 |
|
'/^aria-posinset$/i' => Attribute::CS_STRING, |
397
|
57 |
|
'/^aria-rowcount$/i' => Attribute::CS_STRING, |
398
|
57 |
|
'/^aria-rowindex$/i' => Attribute::CS_STRING, |
399
|
57 |
|
'/^aria-rowspan$/i' => Attribute::CS_STRING, |
400
|
|
|
'/^aria-setsize$/i' => Attribute::CS_STRING |
401
|
57 |
|
); |
402
|
|
|
} |
403
|
|
|
|
404
|
57 |
|
private function getAttributeParameters($key) |
405
|
|
|
{ |
406
|
57 |
|
$allowedAttributes = $this->getAllowedAttributes(); |
407
|
57 |
|
foreach ($allowedAttributes as $attrRegex => $valueType) { |
408
|
57 |
|
if (preg_match($attrRegex, $key) === 1) { |
409
|
|
|
return array( |
410
|
56 |
|
'name' => $key, |
411
|
56 |
|
'regex' => $attrRegex, |
412
|
|
|
'valueType' => $valueType |
413
|
56 |
|
); |
414
|
|
|
} |
415
|
47 |
|
} |
416
|
|
|
|
417
|
10 |
|
return array(); |
418
|
|
|
} |
419
|
|
|
|
420
|
|
|
/** |
421
|
|
|
* Required by the Removable interface. |
422
|
|
|
*/ |
423
|
85 |
View Code Duplication |
public function remove(LoggerInterface $logger) |
|
|
|
|
424
|
|
|
{ |
425
|
85 |
|
$hasRemovableElements = $this->configuration->get('element-blacklist') != ''; |
426
|
85 |
|
$hasRemovableTypes = $this->configuration->get('type-blacklist') != ''; |
427
|
85 |
|
foreach ($this->children as $child) { |
428
|
|
|
// Check types. |
429
|
80 |
|
if ($hasRemovableTypes && |
430
|
80 |
|
!$this->configuration->isAllowedType($child->getType())) { |
431
|
2 |
|
$logger->debug('Removing ' . $child); |
432
|
2 |
|
$this->removeChild($child); |
433
|
|
|
|
434
|
2 |
|
continue; |
435
|
|
|
} |
436
|
|
|
|
437
|
|
|
// Check elements. |
438
|
78 |
|
if ($hasRemovableElements && |
439
|
78 |
|
$child instanceof self && |
440
|
78 |
|
!$this->configuration->isAllowedElement($child->getName())) { |
441
|
3 |
|
$logger->debug('Removing ' . $child); |
442
|
3 |
|
$this->removeChild($child); |
443
|
|
|
|
444
|
3 |
|
continue; |
445
|
|
|
} |
446
|
|
|
|
447
|
|
|
// Check children. |
448
|
78 |
|
if ($child instanceof Removable) { |
449
|
67 |
|
$child->remove($logger); |
450
|
67 |
|
} |
451
|
85 |
|
} |
452
|
85 |
|
} |
453
|
|
|
|
454
|
|
|
/** |
455
|
|
|
* Required by the Token interface. |
456
|
|
|
*/ |
457
|
8 |
|
public function toHtml($prefix, $suffix) |
458
|
|
|
{ |
459
|
8 |
|
$output = $this->buildStartTag($prefix, $suffix); |
460
|
8 |
|
if (empty($this->children)) { |
461
|
6 |
|
return $output; |
462
|
|
|
} |
463
|
|
|
|
464
|
6 |
|
$output .= $this->buildChildrenHtml($prefix, $suffix); |
465
|
|
|
|
466
|
6 |
|
return $output . $prefix . '</' . $this->name . '>' . $suffix; |
467
|
|
|
} |
468
|
|
|
|
469
|
91 |
|
protected function buildStartTag($prefix, $suffix, $forceOpen = false) |
470
|
|
|
{ |
471
|
91 |
|
$output = $prefix . '<' . $this->name; |
472
|
91 |
|
foreach ($this->attributes as $attribute) { |
473
|
51 |
|
$output .= ' ' . (string) $attribute; |
474
|
91 |
|
} |
475
|
|
|
|
476
|
91 |
|
if (!$forceOpen && empty($this->children)) { |
477
|
30 |
|
return $output . '/>' . $suffix; |
478
|
|
|
} |
479
|
|
|
|
480
|
87 |
|
return $output . '>' . $suffix; |
481
|
|
|
} |
482
|
|
|
|
483
|
87 |
|
protected function buildChildrenHtml($prefix, $suffix) |
484
|
|
|
{ |
485
|
87 |
|
$output = ''; |
486
|
87 |
|
foreach ($this->children as $child) { |
487
|
|
|
$newPrefix = $prefix . |
488
|
84 |
|
str_repeat( |
489
|
84 |
|
' ', |
490
|
84 |
|
$this->configuration->get('indent-spaces') |
491
|
84 |
|
); |
492
|
84 |
|
$output .= $child->toHtml($newPrefix, $suffix); |
493
|
87 |
|
} |
494
|
|
|
|
495
|
87 |
|
return $output; |
496
|
|
|
} |
497
|
|
|
|
498
|
17 |
|
public function getType() |
499
|
|
|
{ |
500
|
17 |
|
return Token::ELEMENT; |
501
|
|
|
} |
502
|
|
|
|
503
|
56 |
|
public function __toString() |
504
|
|
|
{ |
505
|
56 |
|
return '"' . $this->name . '" element'; |
506
|
|
|
} |
507
|
|
|
} |
508
|
|
|
|
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.