Passed
Push — master ( 2802ef...c7d11a )
by Bruno
06:54
created

HTMLElement::getRenderHTML()   D

Complexity

Conditions 19
Paths 145

Size

Total Lines 66
Code Lines 35

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 29
CRAP Score 20.8176

Importance

Changes 0
Metric Value
cc 19
eloc 35
c 0
b 0
f 0
nc 145
nop 2
dl 0
loc 66
ccs 29
cts 35
cp 0.8286
crap 20.8176
rs 4.1416

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace Formularium;
4
5
use PHP_CodeSniffer\Generators\HTML;
6
7
/**
8
 * Class that encapsule DOM elements. Similar to PHP DOMElement but more flexible.
9
 * This is not used for parsing, but to build HTML.
10
 */
11
class HTMLElement
12
{
13
    /**
14
     * The HTML attributes and respectives values
15
     * This is an associative array wich:
16
     *     key is the attribute and
17
     *     value is the array with attributes values
18
     * @var array
19
     */
20
    protected $attributes = [];
21
22
    /**
23
     * Tag name
24
     * @var string
25
     */
26
    protected $tag;
27
28
    /**
29
     * The content of Element
30
     * @var array
31
     */
32
    protected $content = [];
33
34
    /**
35
     * If this tag has no children, still render it?
36
     * @var boolean
37
     */
38
    protected $renderIfEmpty = true;
39
40
    /**
41
     * Create a HTML Element
42
     * @param string $tag The tag name of Element
43
     * @param array $attributes The attribute with values
44
     * @param mixed $content The content of element, can be:
45
     *                - string (with text content)
46
     *                - HTMLElement
47
     *                - array with others elements or text
48
     * @param boolean $raw If true, do not escape content.
49
     */
50 9
    public function __construct(string $tag, array $attributes = [], $content = '', $raw = false)
51
    {
52 9
        $this->tag = $tag;
53
54 9
        $this->setAttributes($attributes);
55
56 9
        if (!empty($content)) {
57 5
            $this->setContent($content, true, $raw);
58
        }
59 9
    }
60
61
    /**
62
     * Create a HTML Element
63
     * @param string $tag The tag name of Element
64
     * @param array $attributes The attribute with values
65
     * @param mixed $content The content of element, can be:
66
     *                - string (with text content)
67
     *                - HTMLElement
68
     *                - array with others elements or text
69
     * @param boolean $raw If true, do not escape content.
70
     * @return HTMLElement
71
     */
72
    public static function factory(string $tag, array $attributes = [], $content = '', $raw = false): HTMLElement
73
    {
74
        return new self($tag, $attributes, $content, $raw);
75
    }
76
77
    /**
78
     * Sets whether this tag will render if it is empty (that is,
79
     * no contents or children)
80
     *
81
     * @param boolean $val
82
     * @return HTMLElement Itself
83
     */
84
    public function setRenderIfEmpty(bool $val): HTMLElement
85
    {
86
        $this->renderIfEmpty = $val;
87
        return $this;
88
    }
89
90
    /**
91
     * Return a tag
92
     * @return string tag name
93
     */
94 4
    public function getTag()
95
    {
96 4
        return $this->tag;
97
    }
98
99
    /**
100
     * Modifies our tag
101
     *
102
     * @param string $tag
103
     * @return HTMLElement Itself
104
     */
105 1
    public function setTag($tag)
106
    {
107 1
        $this->tag = $tag;
108 1
        return $this;
109
    }
110
111
    /**
112
     * Return a attribute
113
     * @param string $name The name of attribute
114
     * @return array Return the list of values, if attribute don't exist return empty array
115
     */
116 1
    public function getAttribute($name): array
117
    {
118 1
        return $this->attributes[$name] ?? [];
119
    }
120
121
    /**
122
     * Return the content of element
123
     * @return mixed array of HTMLElement and string (text)
124
     */
125 3
    public function getContent()
126
    {
127 3
        return $this->content;
128
    }
129
130
    public function isEmpty(): bool
131
    {
132
        return count($this->content) > 0;
133
    }
134
135
    /**
136
     *
137
     * @return int
138
     */
139
    public function getCountChildren(): int
140
    {
141
        return count($this->content);
142
    }
143
144
    /**
145
     * Set a attribute value, if attribute exist overwrite it
146
     * @param string $name
147
     * @param mixed $value Can be a string or array of string
148
     * @return HTMLElement Itself
149
     */
150 1
    public function setAttribute(string $name, $value): HTMLElement
151
    {
152 1
        $this->setAttributes([$name => $value], true);
153 1
        return $this;
154
    }
155
156
    /**
157
     * Add an attribute value, if attribute exist append value
158
     * @param string $name
159
     * @param mixed $value Can be a string or array of string
160
     * @return HTMLElement Itself
161
     */
162 1
    public function addAttribute(string $name, $value): HTMLElement
163
    {
164 1
        $this->setAttributes([$name => $value], false);
165 1
        return $this;
166
    }
167
168
    /**
169
     * Set a set of attributes
170
     * @param array $attributes Associative array wich
171
     * 				key is attributes names
172
     *              value is string or array values
173
     * @param bool $overwrite if true and attribute name exist overwrite them
174
     * @return HTMLElement Itself
175
     *
176
     */
177 9
    public function setAttributes(array $attributes, $overwrite = true): HTMLElement
178
    {
179 9
        foreach ($attributes as $atrib => $value) {
180 8
            if (is_array($value)) {
181 2
                $values = $value;
182
            } else {
183 8
                $values = [$value];
184
            }
185
186 8
            if ($overwrite) {
187 8
                $this->attributes[$atrib] = $values;
188
            } else {
189 1
                if (empty($this->attributes[$atrib])) {
190 1
                    $this->attributes[$atrib] = [];
191
                }
192
193 8
                $this->attributes[$atrib] = array_merge($this->attributes[$atrib], $values);
194
            }
195
        }
196 9
        return $this;
197
    }
198
199
    public function removeAttribute(string $attribute): HTMLElement
200
    {
201
        if (array_key_exists($attribute, $this->attributes)) {
202
            unset($this->attributes[$attribute]);
203
        }
204
        return $this;
205
    }
206
207
    /**
208
     * Aliases to HTMLElement::setAttributes($content, false);
209
     * @see setAttributes
210
     * @return HTMLElement Itself
211
     */
212 1
    public function addAttributes(array $attributes): HTMLElement
213
    {
214 1
        $this->setAttributes($attributes, false);
215 1
        return $this;
216
    }
217
218
    /**
219
     * Set content (dom objects or texts) to element
220
     * @param string|HTMLElement|string[]|HTMLElement[] $content The content of element, can be:
221
     *                - string (with text content)
222
     *                - HTMLElement
223
     *                - array with others elements or text
224
     * @param bool $overwrite if true overwrite content otherwise append the content
225
     * @param bool $raw If true, this is raw content (html) and should not be escaped.
226
     * @param bool $prepend If true prepend instead of appending
227
     * @return HTMLElement Itself
228
     */
229 7
    public function setContent($content, $overwrite = true, $raw = false, $prepend = false): HTMLElement
230
    {
231
        // TODO Don't work with reference objects, change it
232 7
        if (!is_array($content)) {
233 4
            $content = [$content];
234
        }
235
236 7
        if ($raw === false) {
237 5
            foreach ($content as &$item) {
238 5
                if (is_string($item)) {
239 5
                    $item = htmlspecialchars($item);
240
                }
241
            }
242
        }
243
244 7
        if ($overwrite) {
245 5
            $this->content = $content;
246
        } else {
247 4
            if ($prepend) {
248
                $this->content = array_merge($content, $this->content);
249
            } else {
250 4
                $this->content = array_merge($this->content, $content);
251
            }
252
        }
253 7
        return $this;
254
    }
255
256
    /**
257
     * Aliases to HTMLElement::setContent($content, false);
258
     * @see setContent
259
     * @param string|HTMLElement|string[]|HTMLElement[] $content
260
     * @return HTMLElement Itself
261
     */
262 3
    public function addContent($content, bool $raw = false): HTMLElement
263
    {
264 3
        $this->setContent($content, false, $raw);
265 3
        return $this;
266
    }
267
268
    /**
269
     * Appends content nodes to the bottom of this element.
270
     *
271
     * @see setContent
272
     * @param string|HTMLElement|string[]|HTMLElement[] $content
273
     * @param boolean $raw
274
     * @return HTMLElement
275
     */
276
    public function appendContent($content, bool $raw = false): HTMLElement
277
    {
278
        $this->setContent($content, false, $raw);
279
        return $this;
280
    }
281
282
    /**
283
     * Prepends content nodes to the beginning of this element.
284
     *
285
     * @see setContent
286
     * @param string|HTMLElement|string[]|HTMLElement[] $content
287
     * @param boolean $raw
288
     * @return HTMLElement
289
     */
290
    public function prependContent($content, bool $raw = false): HTMLElement
291
    {
292
        $this->setContent($content, false, $raw, true);
293
        return $this;
294
    }
295
296
    /**
297
     * Find and return elements using selector
298
     * @param string $selector A selector of elements based in jQuery
299
     *						'eee' - Select elements 'eee' (with tag)
300
     * 						'#ii' - Select a element with id attribute 'ii'
301
     * 						'.cc' - Select elements with class attribute 'cc'
302
     * 						'a=v' - Select elements with 'a' attribute with 'v' value
303
     * 						'e#i' - Select elements 'e' with id attribute 'i'
304
     * 						'e.c' - Select elements 'e' with class attribute 'c'
305
     * @return HTMLElement[]
306
     */
307 1
    public function get(string $selector)
308
    {
309 1
        $tag = null;
310 1
        $attr = null;
311 1
        $val = null;
312
313
        // Define what could be found
314 1
        $selector = trim($selector);
315 1
        if ($selector[0] == "#") {
316 1
            $attr = "id";
317 1
            $val = mb_substr($selector, 1);
318 1
        } elseif ($selector[0] == ".") {
319 1
            $attr = "class";
320 1
            $val = mb_substr($selector, 1);
321 1
        } elseif (mb_strpos($selector, "=") !== false) {
322 1
            list($attr, $val) = explode("=", $selector);
323 1
        } elseif (mb_strpos($selector, "#") !== false) {
324 1
            $attr = "id";
325 1
            list($tag, $val) = explode("#", $selector);
326 1
        } elseif (mb_strpos($selector, ".") !== false) {
327 1
            $attr = "class";
328 1
            list($tag, $val) = explode(".", $selector);
329
        } else {
330 1
            $tag = $selector;
331
        }
332
333 1
        return $this->getInternal($this, $tag, $attr, $val);
334
    }
335
336
    /**
337
     * Find and return elements based in $tag, $attr, $val
338
     * The $tag or $attr must be a value
339
     *
340
     * @see HTMLElement::get
341
     * @param string $tag tag of search
342
     * @param string $attr attribute of search
343
     * @param string $val value of attribute search
344
     * @return HTMLElement[]
345
     */
346 1
    public function getElements(string $tag, string $attr, string $val)
347
    {
348 1
        return $this->getInternal($this, $tag, $attr, $val);
349
    }
350
351
    /**
352
     * Recursive function to found elements
353
     * @param HTMLElement $element Element that will be available
354
     * @param string $tag Tag or null value to compare
355
     * @param string $attr Attribute name or null value to compare
356
     * @param string $val Value of attribute or null value to compare
357
     * @return HTMLElement[]
358
     */
359 1
    protected function getInternal(HTMLElement $element, string $tag = null, string $attr = null, string $val = null)
360
    {
361 1
        if ($this->match($element, $tag, $attr, $val)) {
362 1
            $return = [$element];
363
        } else {
364 1
            $return = [];
365
        }
366
367 1
        foreach ($element->getContent() as $content) {
368 1
            if ($content instanceof HTMLElement) {
369 1
                $return = array_merge($return, $this->getInternal($content, $tag, $attr, $val));
370
            }
371
        }
372
373 1
        return $return;
374
    }
375
376
    /**
377
     * Return a boolean based on match of the element with $tag, $attr or $val
378
     * @param HTMLElement $element Element that will be available
379
     * @param string $tag Tag or null value to compare
380
     * @param string $attr Attribute name or null value to compare
381
     * @param string $val Value of attribute or null value to compare
382
     * @return boolean - true when satisfy and false otherwise
383
     */
384 1
    protected function match(HTMLElement $element, $tag, $attr, $val): bool
385
    {
386 1
        if (!empty($tag)) {
387 1
            if ($element->getTag() != $tag) {
388 1
                return false;
389
            }
390
        } elseif (empty($attr)) {
391
            // Only when $tag and $attr is empty
392 1
            return false;
393
        }
394
395 1
        if (!empty($attr)) {
396 1
            $values = $element->getAttribute($attr);
397 1
            if (count($values) == 0) {
398 1
                return false;
399
            }
400
401 1
            if (!empty($val)) {
402 1
                return in_array($val, $values);
403
            }
404
        }
405
406 1
        return true;
407
    }
408
409
    public function __toString()
410
    {
411
        return $this->getRenderHTML();
412
    }
413
    
414
    /**
415
     * Return the html element code including all children
416
     * @return string (html code)
417
     */
418 5
    public function getRenderHTML($indentString = '  ', $level = 0): string
419
    {
420
        // skip empty non renderable
421 5
        if ($this->renderIfEmpty === false) {
422
            if (!count($this->content)) {
423
                return '';
424
            }
425
        }
426
427
        // start
428 5
        $data = [];
429
430
        // if this is not empty, the tag
431 5
        if (!empty($this->tag)) {
432 5
            $open = [];
433 5
            $open[] = ($level > 0 ? $indentString : '') . // initial indentation
434 5
                '<' . htmlspecialchars($this->tag);
435
436
            // render tag attributes
437 5
            foreach ($this->attributes as $atrib => $value) {
438 4
                $open[] = $atrib . '="' . htmlspecialchars(implode(' ', $value)) . '"';
439
            }
440 5
            $data[] = join(' ', $open) . '>';
441
        }
442
443
        // recurse
444 5
        $contentdata = [];
445 5
        $emptyfieldset = ($this->tag == 'fieldset'); // avoid rendering fieldset with only a "legend"
446 5
        foreach ($this->content as $content) {
447 2
            if ($content instanceof HTMLElement) {
448 1
                $c = $content->getRenderHTML($indentString, $level + 1);
449 1
                if ($this->tag == 'fieldset' and $content->getTag() != 'legend' and $c) {
450
                    $emptyfieldset = false;
451
                }
452 1
                $contentdata[] = $c;
453
            } else {
454 2
                $emptyfieldset = false;
455 2
                $contentdata[] = $indentString . $content;
456
            }
457
        }
458
459
        // handle special empty cases
460 5
        if ($emptyfieldset) {
461
            return '';
462 5
        } elseif ($contentdata == [] && $this->renderIfEmpty === false) {
463
            return '';
464
        }
465
466
        // join content
467 5
        $data = array_merge($data, $contentdata);
468
469
        // handle closing
470 5
        if (!empty($this->tag)
471 5
            && !in_array(
472 5
                $this->tag,
473 5
                ['img', 'hr', 'br', 'input', 'meta', 'col', 'command', 'link', 'param', 'source', 'embed']
474
            )
475
        ) {
476 5
            $data[] = '</' . htmlspecialchars($this->tag) . '>';
477
        }
478
479 5
        if ($indentString && $level === 0) {
480
            $data[] = "\n";
481
        }
482
483 5
        return implode(($indentString ? "\n" : '') . str_repeat($indentString, $level), $data);
484
    }
485
486
    /**
487
     * Clone HTMLElement object and its child
488
     * @return Object HTMLElement
489
     */
490
    public function __clone()
491
    {
492
        $obj = new HTMLElement($this->tag, $this->attributes);
493
        foreach ($this->content as $content) {
494
            if ($content instanceof HTMLElement) {
495
                $obj->addContent(clone $content);
496
            } else {
497
                $obj->addContent($content);
498
            }
499
        }
500
    }
501
502
    /**
503
     * Clear All Attributes
504
     */
505
    public function clearAttributes(): HTMLElement
506
    {
507
        $this->attributes = [];
508
        return $this;
509
    }
510
511
512
    /**
513
     * Clear All Content
514
     */
515 1
    public function clearContent(): HTMLElement
516
    {
517 1
        $this->content = [];
518 1
        return $this;
519
    }
520
521
    /**
522
     * Similar to array_walk(). Applied to this HTMLElement and all its children.
523
     * Does not call text content.
524
     *
525
     * @param callable $f
526
     * @return HTMLElement self
527
     */
528 1
    public function walk(callable $f): HTMLElement
529
    {
530 1
        $f($this);
531 1
        foreach ($this->content as $content) {
532 1
            if ($content instanceof HTMLElement) {
533 1
                $content->walk($f);
534
            }
535
        }
536 1
        return $this;
537
    }
538
539
    /**
540
     * Similar to array_map(). Calls for text content too.
541
     *
542
     * @param callable $f
543
     * @return HTMLElement
544
     */
545 1
    public function map(callable $f): array
546
    {
547 1
        $data = [$f($this)];
548 1
        foreach ($this->content as $content) {
549 1
            if ($content instanceof HTMLElement) {
550 1
                $data = array_merge($data, $content->map($f));
551
            } else {
552 1
                $data[] = $f($content);
553
            }
554
        }
555 1
        return $data;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $data returns the type array which is incompatible with the documented return type Formularium\HTMLElement.
Loading history...
556
    }
557
558
    /**
559
     * Similar to array_filter().
560
     *
561
     * @param callable $f
562
     * @return HTMLElement
563
     */
564 1
    public function filter(callable $f): HTMLElement
565
    {
566 1
        foreach ($this->content as $key => $content) {
567 1
            if ($content instanceof HTMLElement) {
568 1
                if (!$f($content)) {
569 1
                    unset($this->content[$key]);
570
                } else {
571 1
                    $content->filter($f);
572
                }
573
            }
574
        }
575 1
        return $this;
576
    }
577
}
578