These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | |||
3 | namespace NatePage\EasyHtmlElement; |
||
4 | |||
5 | use NatePage\EasyHtmlElement\Exception\InvalidElementException; |
||
6 | use NatePage\EasyHtmlElement\Exception\InvalidArgumentsNumberException; |
||
7 | use NatePage\EasyHtmlElement\Exception\UndefinedElementException; |
||
8 | |||
9 | class HtmlElement implements HtmlElementInterface |
||
10 | { |
||
11 | /** @var array */ |
||
12 | private $map; |
||
13 | |||
14 | /** @var EscaperInterface */ |
||
15 | private $escaper; |
||
16 | |||
17 | /** @var array The already resolved elements */ |
||
18 | private $resolved = array(); |
||
19 | |||
20 | /** @var array The default values of element options */ |
||
21 | private $defaults = array( |
||
22 | 'parent' => null, |
||
23 | 'children' => array(), |
||
24 | 'extends' => array(), |
||
25 | 'attr' => array(), |
||
26 | 'text' => null, |
||
27 | 'type' => null, |
||
28 | 'class' => Element::class |
||
29 | ); |
||
30 | |||
31 | /** @var array The options to check to valid a branch */ |
||
32 | private $checks = array('parent', 'extends', 'children'); |
||
33 | |||
34 | /** @var array The mergeable attributes */ |
||
35 | private $mergeableAttributes = array('class', 'style'); |
||
36 | |||
37 | /** @var array The special escaping types */ |
||
38 | private $specialEscapingTypes = array('script', 'style'); |
||
39 | |||
40 | /** @var bool Determine if html is escaped or not */ |
||
41 | private $escapeHtml = true; |
||
42 | |||
43 | /** @var bool Determine if html attributes are escaped or not */ |
||
44 | private $escapeHtmlAttr = true; |
||
45 | |||
46 | /** @var bool Determine if javascript is escaped or not */ |
||
47 | private $escapeJs = true; |
||
48 | |||
49 | /** @var bool Determine if css is escaped or not */ |
||
50 | private $escapeCss = true; |
||
51 | |||
52 | /** @var bool Determine if urls are escaped or not */ |
||
53 | private $escapeUrl = true; |
||
54 | |||
55 | /** |
||
56 | * HtmlElement constructor. |
||
57 | * |
||
58 | * @param array $map The elements map |
||
59 | * @param EscaperInterface|null $escaper The escaper, by default ZendFramework/Escaper is used |
||
60 | * @param string $encoding The encoding used for escaping, by default utf-8 is used |
||
61 | */ |
||
62 | public function __construct(array $map = array(), EscaperInterface $escaper = null, $encoding = 'utf-8') |
||
63 | { |
||
64 | $this->map = $map; |
||
65 | $this->escaper = null !== $escaper ? $escaper : new Escaper($encoding); |
||
66 | } |
||
67 | |||
68 | /** |
||
69 | * {@inheritdoc} |
||
70 | */ |
||
71 | public function setMap(array $map) |
||
72 | { |
||
73 | $this->map = $map; |
||
74 | |||
75 | return $this; |
||
76 | } |
||
77 | |||
78 | /** |
||
79 | * {@inheritdoc} |
||
80 | */ |
||
81 | public function getMap() |
||
82 | { |
||
83 | return $this->map; |
||
84 | } |
||
85 | |||
86 | /** |
||
87 | * {@inheritdoc} |
||
88 | */ |
||
89 | public function addOneToMap($name, array $element) |
||
90 | { |
||
91 | $this->map[$name] = $element; |
||
92 | |||
93 | return $this; |
||
94 | } |
||
95 | |||
96 | /** |
||
97 | * {@inheritdoc} |
||
98 | */ |
||
99 | public function addManyToMap(array $elements) |
||
100 | { |
||
101 | foreach($elements as $name => $element){ |
||
102 | $this->addOneToMap($name, $element); |
||
103 | } |
||
104 | |||
105 | return $this; |
||
106 | } |
||
107 | |||
108 | /** |
||
109 | * {@inheritdoc} |
||
110 | */ |
||
111 | public function setEscaper(EscaperInterface $escaper) |
||
112 | { |
||
113 | $this->escaper = $escaper; |
||
114 | |||
115 | return $this; |
||
116 | } |
||
117 | |||
118 | /** |
||
119 | * {@inheritdoc} |
||
120 | */ |
||
121 | public function getEscaper() |
||
122 | { |
||
123 | return $this->escaper; |
||
124 | } |
||
125 | |||
126 | /** |
||
127 | * {@inheritdoc} |
||
128 | */ |
||
129 | public function setEscapeHtml($escapeHtml = true) |
||
130 | { |
||
131 | $this->escapeHtml = $escapeHtml; |
||
132 | |||
133 | return $this; |
||
134 | } |
||
135 | |||
136 | /** |
||
137 | * {@inheritdoc} |
||
138 | */ |
||
139 | public function isEscapeHtml() |
||
140 | { |
||
141 | return $this->escapeHtml; |
||
142 | } |
||
143 | |||
144 | /** |
||
145 | * {@inheritdoc} |
||
146 | */ |
||
147 | public function setEscapeHtmlAttr($escapeHtmlAttr = true) |
||
148 | { |
||
149 | $this->escapeHtmlAttr = $escapeHtmlAttr; |
||
150 | |||
151 | return $this; |
||
152 | } |
||
153 | |||
154 | /** |
||
155 | * {@inheritdoc} |
||
156 | */ |
||
157 | public function isEscapeHtmlAttr() |
||
158 | { |
||
159 | return $this->escapeHtmlAttr; |
||
160 | } |
||
161 | |||
162 | /** |
||
163 | * {@inheritdoc} |
||
164 | */ |
||
165 | public function setEscapeJs($escapeJs = true) |
||
166 | { |
||
167 | $this->escapeJs = $escapeJs; |
||
168 | |||
169 | return $this; |
||
170 | } |
||
171 | |||
172 | /** |
||
173 | * {@inheritdoc} |
||
174 | */ |
||
175 | public function isEscapeJs() |
||
176 | { |
||
177 | return $this->escapeJs; |
||
178 | } |
||
179 | |||
180 | /** |
||
181 | * {@inheritdoc} |
||
182 | */ |
||
183 | public function setEscapeCss($escapeCss = true) |
||
184 | { |
||
185 | $this->escapeCss = $escapeCss; |
||
186 | |||
187 | return $this; |
||
188 | } |
||
189 | |||
190 | /** |
||
191 | * {@inheritdoc} |
||
192 | */ |
||
193 | public function isEscapeCss() |
||
194 | { |
||
195 | return $this->escapeCss; |
||
196 | } |
||
197 | |||
198 | /** |
||
199 | * {@inheritdoc} |
||
200 | */ |
||
201 | public function setEscapeUrl($escapeUrl = true) |
||
202 | { |
||
203 | $this->escapeUrl = $escapeUrl; |
||
204 | |||
205 | return $this; |
||
206 | } |
||
207 | |||
208 | /** |
||
209 | * {@inheritdoc} |
||
210 | */ |
||
211 | public function isEscapeUrl() |
||
212 | { |
||
213 | return $this->escapeUrl; |
||
214 | } |
||
215 | |||
216 | /** |
||
217 | * {@inheritdoc} |
||
218 | */ |
||
219 | public function exists($name) |
||
220 | { |
||
221 | return array_key_exists(lcfirst($name), $this->map); |
||
222 | } |
||
223 | |||
224 | /** |
||
225 | * {@inheritdoc} |
||
226 | */ |
||
227 | public function load($name, array $parameters = array(), array $attributes = array(), array $children = array()) |
||
228 | { |
||
229 | $element = $this->getInstance($name, $parameters, true); |
||
230 | |||
231 | $element->addAttributes($this->escapeAttributes($attributes)); |
||
232 | |||
233 | foreach($children as $child){ |
||
234 | $element->addChild($this->escape($child)); |
||
235 | } |
||
236 | |||
237 | return $element; |
||
238 | } |
||
239 | |||
240 | /** |
||
241 | * {@inheritdoc} |
||
242 | */ |
||
243 | static public function create($type = null, $text = null, array $attributes = array(), array $children = array()) |
||
0 ignored issues
–
show
Coding Style
introduced
by
![]() |
|||
244 | { |
||
245 | $htmlElement = new HtmlElement(); |
||
246 | |||
247 | $attributes = $htmlElement->escapeAttributes($attributes); |
||
248 | |||
249 | foreach($children as $key => $child){ |
||
250 | $children[$key] = $htmlElement->escape($child); |
||
251 | } |
||
252 | |||
253 | return $htmlElement->escape(new Element($type, $text, $attributes, $children)); |
||
254 | } |
||
255 | |||
256 | /** |
||
257 | * Create element on static calls. |
||
258 | * |
||
259 | * @param string $type The element type |
||
260 | * @param array $arguments The arguments array to set: |
||
261 | * [0] = text (string) |
||
262 | * [1] = attributes (array) |
||
263 | * [2] = children (array) |
||
264 | * |
||
265 | * @return ElementInterface |
||
266 | * |
||
267 | * @throws InvalidArgumentsNumberException If the arguments length is more than 3 |
||
268 | */ |
||
269 | static public function __callStatic($type, $arguments) |
||
0 ignored issues
–
show
|
|||
270 | { |
||
271 | switch(count($arguments)){ |
||
272 | case 0: |
||
273 | return self::create($type); |
||
274 | break; |
||
0 ignored issues
–
show
break is not strictly necessary here and could be removed.
The break statement is not necessary if it is preceded for example by a return statement: switch ($x) {
case 1:
return 'foo';
break; // This break is not necessary and can be left off.
}
If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive. ![]() |
|||
275 | case 1: |
||
276 | return self::create($type, $arguments[0]); |
||
277 | break; |
||
0 ignored issues
–
show
break is not strictly necessary here and could be removed.
The break statement is not necessary if it is preceded for example by a return statement: switch ($x) {
case 1:
return 'foo';
break; // This break is not necessary and can be left off.
}
If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive. ![]() |
|||
278 | case 2: |
||
279 | return self::create($type, $arguments[0], $arguments[1]); |
||
280 | break; |
||
0 ignored issues
–
show
break is not strictly necessary here and could be removed.
The break statement is not necessary if it is preceded for example by a return statement: switch ($x) {
case 1:
return 'foo';
break; // This break is not necessary and can be left off.
}
If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive. ![]() |
|||
281 | case 3: |
||
282 | return self::create($type, $arguments[0], $arguments[1], $arguments[2]); |
||
283 | break; |
||
0 ignored issues
–
show
break is not strictly necessary here and could be removed.
The break statement is not necessary if it is preceded for example by a return statement: switch ($x) {
case 1:
return 'foo';
break; // This break is not necessary and can be left off.
}
If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive. ![]() |
|||
284 | default: |
||
285 | throw new InvalidArgumentsNumberException(sprintf( |
||
286 | 'Maximum numbers of arguments is %d, [%d] given.', |
||
287 | 3, |
||
288 | count($arguments) |
||
289 | )); |
||
290 | break; |
||
0 ignored issues
–
show
break; does not seem to be reachable.
This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed. Unreachable code is most often the result of function fx() {
try {
doSomething();
return true;
}
catch (\Exception $e) {
return false;
}
return false;
}
In the above example, the last ![]() |
|||
291 | } |
||
292 | } |
||
293 | |||
294 | /** |
||
295 | * {@inheritdoc} |
||
296 | */ |
||
297 | public function escape(ElementInterface $element) |
||
298 | { |
||
299 | if($this->escapeHtml && !in_array($element->getType(), $this->specialEscapingTypes)){ |
||
300 | $element->setText($this->escaper->escapeHtml($element->getText())); |
||
301 | } |
||
302 | |||
303 | $element->setAttributes($this->escapeAttributes($element->getAttributes())); |
||
304 | |||
305 | if($this->escapeJs && 'script' == $element->getType()){ |
||
306 | $element->setText($this->escaper->escapeJs($element->getText())); |
||
307 | } |
||
308 | |||
309 | if($this->escapeCss && 'style' == $element->getType()){ |
||
310 | $element->setText($this->escaper->escapeCss($element->getText())); |
||
311 | } |
||
312 | |||
313 | return $element; |
||
314 | } |
||
315 | |||
316 | /** |
||
317 | * {@inheritdoc} |
||
318 | */ |
||
319 | public function escapeAttributes(array $attributes) |
||
320 | { |
||
321 | if($this->escapeHtmlAttr || $this->escapeUrl){ |
||
322 | foreach($attributes as $attr => $value){ |
||
323 | if('href' == $attr){ |
||
324 | if($this->escapeUrl){ |
||
325 | $value = $this->escaper->escapeUrl($value); |
||
326 | } |
||
327 | } else { |
||
328 | if($this->escapeHtmlAttr){ |
||
329 | $value = $this->escaper->escapeHtmlAttr($value); |
||
330 | } |
||
331 | } |
||
332 | |||
333 | $attributes[$attr] = $value; |
||
334 | } |
||
335 | } |
||
336 | |||
337 | return $attributes; |
||
338 | } |
||
339 | |||
340 | /** |
||
341 | * Get the element instance. |
||
342 | * |
||
343 | * @param string $name The element name |
||
344 | * @param array $parameters The parameters to replace in element |
||
345 | * @param bool $mainCall Determine if it's the main(first) call of the method |
||
346 | * |
||
347 | * @return ElementInterface |
||
348 | */ |
||
349 | private function getInstance($name, array $parameters, $mainCall = false) |
||
350 | { |
||
351 | $element = $this->resolveElement($name, $parameters, $mainCall); |
||
352 | |||
353 | $class = $element['class']; |
||
354 | $type = $element['type']; |
||
355 | $text = $element['text']; |
||
356 | $attributes = $element['attr']; |
||
357 | |||
358 | $instance = new $class($type, $text, $attributes); |
||
359 | |||
360 | if(!$instance instanceof ElementInterface){ |
||
361 | throw new InvalidElementException(sprintf( |
||
362 | 'The element "%s" does not implement the %s', |
||
363 | get_class($instance), |
||
364 | ElementInterface::class |
||
365 | )); |
||
366 | } |
||
367 | |||
368 | $children = array(); |
||
369 | foreach((array) $element['children'] as $child){ |
||
370 | $children[] = $this->getInstance($child, $parameters); |
||
371 | } |
||
372 | |||
373 | $instance->setChildren($children); |
||
374 | |||
375 | if(null !== $element['parent']){ |
||
376 | $parent = $this->getInstance($element['parent'], $parameters); |
||
377 | |||
378 | $parent->addChild($instance); |
||
379 | } |
||
380 | |||
381 | return $this->escape($instance); |
||
382 | } |
||
383 | |||
384 | /** |
||
385 | * Get the resolved element representation. |
||
386 | * |
||
387 | * @param string $name The current element name |
||
388 | * @param array $parameters The parameters to replace in element |
||
389 | * @param bool $mainCall Determine if it's the main(first) call of the method |
||
390 | * |
||
391 | * @return array |
||
392 | */ |
||
393 | private function resolveElement($name, array $parameters, $mainCall = false) |
||
394 | { |
||
395 | $getCurrent = true; |
||
396 | |||
397 | if(is_array($name)){ |
||
398 | $current = $name; |
||
399 | $name = $current['name']; |
||
400 | |||
401 | $getCurrent = false; |
||
402 | } |
||
403 | |||
404 | if($this->alreadyResolved($name)){ |
||
405 | return $this->resolved[$name]; |
||
406 | } |
||
407 | |||
408 | if($getCurrent && !$this->exists($name)){ |
||
409 | throw new UndefinedElementException(sprintf('The element with name "%s" does not exist.', $name)); |
||
410 | } |
||
411 | |||
412 | if($mainCall){ |
||
413 | $this->validBranch($name); |
||
414 | } |
||
415 | |||
416 | if($getCurrent){ |
||
417 | $current = $this->getCurrentElement($name); |
||
418 | } |
||
419 | |||
420 | foreach($this->defaults as $default => $value){ |
||
421 | if(!isset($current[$default])){ |
||
422 | $current[$default] = $value; |
||
0 ignored issues
–
show
The variable
$current does not seem to be defined for all execution paths leading up to this point.
If you define a variable conditionally, it can happen that it is not defined for all execution paths. Let’s take a look at an example: function myFunction($a) {
switch ($a) {
case 'foo':
$x = 1;
break;
case 'bar':
$x = 2;
break;
}
// $x is potentially undefined here.
echo $x;
}
In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined. Available Fixes
![]() |
|||
423 | } |
||
424 | } |
||
425 | |||
426 | $current = $this->replaceParameters($current, $parameters); |
||
427 | |||
428 | foreach((array) $current['extends'] as $extend){ |
||
429 | $extend = $this->resolveElement($extend, $parameters); |
||
430 | $current = $this->extendElement($extend, $current); |
||
431 | } |
||
432 | |||
433 | $this->resolved[$name] = $current; |
||
434 | |||
435 | return $current; |
||
436 | } |
||
437 | |||
438 | /** |
||
439 | * Check if an element has been already resolved. |
||
440 | * |
||
441 | * @param string $name |
||
442 | * |
||
443 | * @return bool |
||
444 | */ |
||
445 | private function alreadyResolved($name) |
||
446 | { |
||
447 | return array_key_exists($name, $this->resolved); |
||
448 | } |
||
449 | |||
450 | /** |
||
451 | * Valid the current map branch. |
||
452 | * |
||
453 | * @param string $name The current element name |
||
454 | * @param array $circular The array of elements names called in the current branch of map |
||
455 | * |
||
456 | * @throws InvalidElementException If the current element is defined dynamically and doesn't define a name |
||
457 | * If the current element define a parent, child or extends which creates circular |
||
458 | * declaration |
||
459 | * @throws UndefinedElementException If the current element define a parent, child or extends which doesn't exist |
||
460 | */ |
||
461 | private function validBranch($name, array $circular = array()) |
||
462 | { |
||
463 | $getCurrent = true; |
||
464 | |||
465 | if(is_array($name)){ |
||
466 | if(!isset($name['name'])){ |
||
467 | throw new InvalidElementException(sprintf( |
||
468 | 'Elements defined dynamically in parent or children must define a name.' |
||
469 | )); |
||
470 | } |
||
471 | |||
472 | $current = $name; |
||
473 | |||
474 | $name = $current['name']; |
||
475 | unset($current['name']); |
||
476 | |||
477 | $getCurrent = false; |
||
478 | } |
||
479 | |||
480 | $circular[] = $name; |
||
481 | |||
482 | if($getCurrent){ |
||
483 | $current = $this->getCurrentElement($name); |
||
484 | } |
||
485 | |||
486 | if(isset($current['class']) && !class_exists($current['class'])){ |
||
487 | throw new InvalidElementException(sprintf( |
||
488 | 'The element "%s" define a class which doesn\'t exist.', |
||
489 | $name |
||
490 | )); |
||
491 | } |
||
492 | |||
493 | foreach($this->checks as $check){ |
||
494 | if(isset($current[$check])){ |
||
495 | $currentCheck = (array) $current[$check]; |
||
496 | |||
497 | if(in_array($name, $currentCheck)){ |
||
498 | throw new InvalidElementException(sprintf( |
||
499 | 'Element "%s" cannot define himself as %s.', |
||
500 | $name, |
||
501 | $check |
||
502 | )); |
||
503 | } |
||
504 | |||
505 | foreach($currentCheck as $cc){ |
||
506 | if(!is_array($cc) && !$this->exists($cc)){ |
||
507 | throw new UndefinedElementException(sprintf( |
||
508 | 'The element "%s" defines a %s "%s" wich doesn\'t exist.', |
||
509 | $name, |
||
510 | $check, |
||
511 | $cc |
||
512 | )); |
||
513 | } |
||
514 | |||
515 | if(!is_array($cc) && in_array($cc, $circular)){ |
||
516 | $circular[] = $cc; |
||
517 | |||
518 | throw new InvalidElementException(sprintf( |
||
519 | 'Element "%s" cannot define "%s" as %s. It\'s a circular reference. [%s]', |
||
520 | $name, |
||
521 | $cc, |
||
522 | $check, |
||
523 | implode('->', $circular) |
||
524 | )); |
||
525 | } |
||
526 | |||
527 | $this->validBranch($cc, $circular); |
||
528 | } |
||
529 | } |
||
530 | } |
||
531 | } |
||
532 | |||
533 | /** |
||
534 | * Get the current element representation. |
||
535 | * |
||
536 | * @param string $name The element name |
||
537 | * |
||
538 | * @return array |
||
539 | */ |
||
540 | private function getCurrentElement($name) |
||
541 | { |
||
542 | return $this->map[lcfirst($name)]; |
||
543 | } |
||
544 | |||
545 | /** |
||
546 | * Extend element from another one. |
||
547 | * |
||
548 | * @param array $extend The array of the element to extend |
||
549 | * @param array $current The current element which extends |
||
550 | * |
||
551 | * @return array |
||
552 | */ |
||
553 | private function extendElement($extend, $current) |
||
554 | { |
||
555 | $current['class'] = $extend['class']; |
||
556 | |||
557 | $current['attr'] = $this->extendAttributes($extend['attr'], $current['attr']); |
||
558 | |||
559 | return $current; |
||
560 | } |
||
561 | |||
562 | /** |
||
563 | * Extend attributes from another element. |
||
564 | * |
||
565 | * @param array $from The array of attributes to extend |
||
566 | * @param array $to The array of attributes which extends |
||
567 | * |
||
568 | * @return array |
||
569 | */ |
||
570 | private function extendAttributes(array $from, array $to) |
||
571 | { |
||
572 | foreach($from as $key => $value){ |
||
573 | if(in_array($key, $this->mergeableAttributes) && isset($to[$key])){ |
||
574 | $to[$key] = array_merge((array) $to[$key], (array) $value); |
||
575 | } elseif(!isset($to[$key])){ |
||
576 | $to[$key] = $value; |
||
577 | } elseif(is_array($value)){ |
||
578 | $to[$key] = $this->extendAttributes($value, $to[$key]); |
||
579 | } |
||
580 | } |
||
581 | |||
582 | return $to; |
||
583 | } |
||
584 | |||
585 | /** |
||
586 | * Replace the parameters of the element. |
||
587 | * |
||
588 | * @param array $element The element with the parameters to replace |
||
589 | * @param array $parameters The array of parameters values |
||
590 | * |
||
591 | * @return array |
||
592 | */ |
||
593 | private function replaceParameters(array $element, array $parameters) |
||
594 | { |
||
595 | foreach($element as $key => $value){ |
||
596 | if(is_array($value)){ |
||
597 | $element[$key] = $this->replaceParameters($value, $parameters); |
||
598 | } |
||
599 | |||
600 | if(is_string($value)){ |
||
601 | foreach($parameters as $parameter => $replace){ |
||
602 | $value = str_replace('%'.$parameter.'%', $replace, $value); |
||
603 | } |
||
604 | |||
605 | $element[$key] = $value; |
||
606 | } |
||
607 | } |
||
608 | |||
609 | return $element; |
||
610 | } |
||
611 | } |
||
612 |