Subversion-Projekte lars-tiefland.laravel_shop

Revision

Revision 148 | Details | Vergleich mit vorheriger | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
148 lars 1
<?php
2
 
3
/**
4
 * This file is part of the Nette Framework (https://nette.org)
5
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
6
 */
7
 
8
declare(strict_types=1);
9
 
10
namespace Nette\Utils;
11
 
12
use Nette;
13
use Nette\HtmlStringable;
14
use function is_array, is_float, is_object, is_string;
15
 
16
 
17
/**
18
 * HTML helper.
19
 *
20
 * @property string|null $accept
21
 * @property string|null $accesskey
22
 * @property string|null $action
23
 * @property string|null $align
24
 * @property string|null $allow
25
 * @property string|null $alt
26
 * @property bool|null   $async
27
 * @property string|null $autocapitalize
28
 * @property string|null $autocomplete
29
 * @property bool|null   $autofocus
30
 * @property bool|null   $autoplay
31
 * @property string|null $charset
32
 * @property bool|null   $checked
33
 * @property string|null $cite
34
 * @property string|null $class
35
 * @property int|null    $cols
36
 * @property int|null    $colspan
37
 * @property string|null $content
38
 * @property bool|null   $contenteditable
39
 * @property bool|null   $controls
40
 * @property string|null $coords
41
 * @property string|null $crossorigin
42
 * @property string|null $data
43
 * @property string|null $datetime
44
 * @property string|null $decoding
45
 * @property bool|null   $default
46
 * @property bool|null   $defer
47
 * @property string|null $dir
48
 * @property string|null $dirname
49
 * @property bool|null   $disabled
50
 * @property bool|null   $download
51
 * @property string|null $draggable
52
 * @property string|null $dropzone
53
 * @property string|null $enctype
54
 * @property string|null $for
55
 * @property string|null $form
56
 * @property string|null $formaction
57
 * @property string|null $formenctype
58
 * @property string|null $formmethod
59
 * @property bool|null   $formnovalidate
60
 * @property string|null $formtarget
61
 * @property string|null $headers
62
 * @property int|null    $height
63
 * @property bool|null   $hidden
64
 * @property float|null  $high
65
 * @property string|null $href
66
 * @property string|null $hreflang
67
 * @property string|null $id
68
 * @property string|null $integrity
69
 * @property string|null $inputmode
70
 * @property bool|null   $ismap
71
 * @property string|null $itemprop
72
 * @property string|null $kind
73
 * @property string|null $label
74
 * @property string|null $lang
75
 * @property string|null $list
76
 * @property bool|null   $loop
77
 * @property float|null  $low
78
 * @property float|null  $max
79
 * @property int|null    $maxlength
80
 * @property int|null    $minlength
81
 * @property string|null $media
82
 * @property string|null $method
83
 * @property float|null  $min
84
 * @property bool|null   $multiple
85
 * @property bool|null   $muted
86
 * @property string|null $name
87
 * @property bool|null   $novalidate
88
 * @property bool|null   $open
89
 * @property float|null  $optimum
90
 * @property string|null $pattern
91
 * @property string|null $ping
92
 * @property string|null $placeholder
93
 * @property string|null $poster
94
 * @property string|null $preload
95
 * @property string|null $radiogroup
96
 * @property bool|null   $readonly
97
 * @property string|null $rel
98
 * @property bool|null   $required
99
 * @property bool|null   $reversed
100
 * @property int|null    $rows
101
 * @property int|null    $rowspan
102
 * @property string|null $sandbox
103
 * @property string|null $scope
104
 * @property bool|null   $selected
105
 * @property string|null $shape
106
 * @property int|null    $size
107
 * @property string|null $sizes
108
 * @property string|null $slot
109
 * @property int|null    $span
110
 * @property string|null $spellcheck
111
 * @property string|null $src
112
 * @property string|null $srcdoc
113
 * @property string|null $srclang
114
 * @property string|null $srcset
115
 * @property int|null    $start
116
 * @property float|null  $step
117
 * @property string|null $style
118
 * @property int|null    $tabindex
119
 * @property string|null $target
120
 * @property string|null $title
121
 * @property string|null $translate
122
 * @property string|null $type
123
 * @property string|null $usemap
124
 * @property string|null $value
125
 * @property int|null    $width
126
 * @property string|null $wrap
127
 *
128
 * @method self accept(?string $val)
129
 * @method self accesskey(?string $val, bool $state = null)
130
 * @method self action(?string $val)
131
 * @method self align(?string $val)
132
 * @method self allow(?string $val, bool $state = null)
133
 * @method self alt(?string $val)
134
 * @method self async(?bool $val)
135
 * @method self autocapitalize(?string $val)
136
 * @method self autocomplete(?string $val)
137
 * @method self autofocus(?bool $val)
138
 * @method self autoplay(?bool $val)
139
 * @method self charset(?string $val)
140
 * @method self checked(?bool $val)
141
 * @method self cite(?string $val)
142
 * @method self class(?string $val, bool $state = null)
143
 * @method self cols(?int $val)
144
 * @method self colspan(?int $val)
145
 * @method self content(?string $val)
146
 * @method self contenteditable(?bool $val)
147
 * @method self controls(?bool $val)
148
 * @method self coords(?string $val)
149
 * @method self crossorigin(?string $val)
150
 * @method self datetime(?string $val)
151
 * @method self decoding(?string $val)
152
 * @method self default(?bool $val)
153
 * @method self defer(?bool $val)
154
 * @method self dir(?string $val)
155
 * @method self dirname(?string $val)
156
 * @method self disabled(?bool $val)
157
 * @method self download(?bool $val)
158
 * @method self draggable(?string $val)
159
 * @method self dropzone(?string $val)
160
 * @method self enctype(?string $val)
161
 * @method self for(?string $val)
162
 * @method self form(?string $val)
163
 * @method self formaction(?string $val)
164
 * @method self formenctype(?string $val)
165
 * @method self formmethod(?string $val)
166
 * @method self formnovalidate(?bool $val)
167
 * @method self formtarget(?string $val)
168
 * @method self headers(?string $val, bool $state = null)
169
 * @method self height(?int $val)
170
 * @method self hidden(?bool $val)
171
 * @method self high(?float $val)
172
 * @method self hreflang(?string $val)
173
 * @method self id(?string $val)
174
 * @method self integrity(?string $val)
175
 * @method self inputmode(?string $val)
176
 * @method self ismap(?bool $val)
177
 * @method self itemprop(?string $val)
178
 * @method self kind(?string $val)
179
 * @method self label(?string $val)
180
 * @method self lang(?string $val)
181
 * @method self list(?string $val)
182
 * @method self loop(?bool $val)
183
 * @method self low(?float $val)
184
 * @method self max(?float $val)
185
 * @method self maxlength(?int $val)
186
 * @method self minlength(?int $val)
187
 * @method self media(?string $val)
188
 * @method self method(?string $val)
189
 * @method self min(?float $val)
190
 * @method self multiple(?bool $val)
191
 * @method self muted(?bool $val)
192
 * @method self name(?string $val)
193
 * @method self novalidate(?bool $val)
194
 * @method self open(?bool $val)
195
 * @method self optimum(?float $val)
196
 * @method self pattern(?string $val)
197
 * @method self ping(?string $val, bool $state = null)
198
 * @method self placeholder(?string $val)
199
 * @method self poster(?string $val)
200
 * @method self preload(?string $val)
201
 * @method self radiogroup(?string $val)
202
 * @method self readonly(?bool $val)
203
 * @method self rel(?string $val)
204
 * @method self required(?bool $val)
205
 * @method self reversed(?bool $val)
206
 * @method self rows(?int $val)
207
 * @method self rowspan(?int $val)
208
 * @method self sandbox(?string $val, bool $state = null)
209
 * @method self scope(?string $val)
210
 * @method self selected(?bool $val)
211
 * @method self shape(?string $val)
212
 * @method self size(?int $val)
213
 * @method self sizes(?string $val)
214
 * @method self slot(?string $val)
215
 * @method self span(?int $val)
216
 * @method self spellcheck(?string $val)
217
 * @method self src(?string $val)
218
 * @method self srcdoc(?string $val)
219
 * @method self srclang(?string $val)
220
 * @method self srcset(?string $val)
221
 * @method self start(?int $val)
222
 * @method self step(?float $val)
223
 * @method self style(?string $property, string $val = null)
224
 * @method self tabindex(?int $val)
225
 * @method self target(?string $val)
226
 * @method self title(?string $val)
227
 * @method self translate(?string $val)
228
 * @method self type(?string $val)
229
 * @method self usemap(?string $val)
230
 * @method self value(?string $val)
231
 * @method self width(?int $val)
232
 * @method self wrap(?string $val)
233
 */
234
class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringable
235
{
236
	use Nette\SmartObject;
237
 
238
	/** @var array<string, mixed>  element's attributes */
239
	public $attrs = [];
240
 
399 lars 241
	/** void elements */
148 lars 242
	public static $emptyElements = [
243
		'img' => 1, 'hr' => 1, 'br' => 1, 'input' => 1, 'meta' => 1, 'area' => 1, 'embed' => 1, 'keygen' => 1,
244
		'source' => 1, 'base' => 1, 'col' => 1, 'link' => 1, 'param' => 1, 'basefont' => 1, 'frame' => 1,
245
		'isindex' => 1, 'wbr' => 1, 'command' => 1, 'track' => 1,
246
	];
247
 
248
	/** @var array<int, HtmlStringable|string> nodes */
249
	protected $children = [];
250
 
399 lars 251
	/** element's name */
252
	private string $name = '';
148 lars 253
 
399 lars 254
	private bool $isEmpty = false;
148 lars 255
 
256
 
257
	/**
258
	 * Constructs new HTML element.
259
	 * @param  array|string $attrs element's attributes or plain text content
260
	 */
399 lars 261
	public static function el(?string $name = null, array|string|null $attrs = null): static
148 lars 262
	{
263
		$el = new static;
264
		$parts = explode(' ', (string) $name, 2);
265
		$el->setName($parts[0]);
266
 
267
		if (is_array($attrs)) {
268
			$el->attrs = $attrs;
269
 
270
		} elseif ($attrs !== null) {
271
			$el->setText($attrs);
272
		}
273
 
274
		if (isset($parts[1])) {
275
			foreach (Strings::matchAll($parts[1] . ' ', '#([a-z0-9:-]+)(?:=(["\'])?(.*?)(?(2)\2|\s))?#i') as $m) {
276
				$el->attrs[$m[1]] = $m[3] ?? true;
277
			}
278
		}
279
 
280
		return $el;
281
	}
282
 
283
 
284
	/**
285
	 * Returns an object representing HTML text.
286
	 */
399 lars 287
	public static function fromHtml(string $html): static
148 lars 288
	{
289
		return (new static)->setHtml($html);
290
	}
291
 
292
 
293
	/**
294
	 * Returns an object representing plain text.
295
	 */
399 lars 296
	public static function fromText(string $text): static
148 lars 297
	{
298
		return (new static)->setText($text);
299
	}
300
 
301
 
302
	/**
303
	 * Converts to HTML.
304
	 */
305
	final public function toHtml(): string
306
	{
307
		return $this->render();
308
	}
309
 
310
 
311
	/**
312
	 * Converts to plain text.
313
	 */
314
	final public function toText(): string
315
	{
316
		return $this->getText();
317
	}
318
 
319
 
320
	/**
321
	 * Converts given HTML code to plain text.
322
	 */
323
	public static function htmlToText(string $html): string
324
	{
325
		return html_entity_decode(strip_tags($html), ENT_QUOTES | ENT_HTML5, 'UTF-8');
326
	}
327
 
328
 
329
	/**
330
	 * Changes element's name.
331
	 */
399 lars 332
	final public function setName(string $name, ?bool $isEmpty = null): static
148 lars 333
	{
334
		$this->name = $name;
335
		$this->isEmpty = $isEmpty ?? isset(static::$emptyElements[$name]);
336
		return $this;
337
	}
338
 
339
 
340
	/**
341
	 * Returns element's name.
342
	 */
343
	final public function getName(): string
344
	{
345
		return $this->name;
346
	}
347
 
348
 
349
	/**
350
	 * Is element empty?
351
	 */
352
	final public function isEmpty(): bool
353
	{
354
		return $this->isEmpty;
355
	}
356
 
357
 
358
	/**
359
	 * Sets multiple attributes.
360
	 */
399 lars 361
	public function addAttributes(array $attrs): static
148 lars 362
	{
363
		$this->attrs = array_merge($this->attrs, $attrs);
364
		return $this;
365
	}
366
 
367
 
368
	/**
369
	 * Appends value to element's attribute.
370
	 */
399 lars 371
	public function appendAttribute(string $name, mixed $value, mixed $option = true): static
148 lars 372
	{
373
		if (is_array($value)) {
374
			$prev = isset($this->attrs[$name]) ? (array) $this->attrs[$name] : [];
375
			$this->attrs[$name] = $value + $prev;
376
 
377
		} elseif ((string) $value === '') {
378
			$tmp = &$this->attrs[$name]; // appending empty value? -> ignore, but ensure it exists
379
 
380
		} elseif (!isset($this->attrs[$name]) || is_array($this->attrs[$name])) { // needs array
381
			$this->attrs[$name][$value] = $option;
382
 
383
		} else {
384
			$this->attrs[$name] = [$this->attrs[$name] => true, $value => $option];
385
		}
386
 
387
		return $this;
388
	}
389
 
390
 
391
	/**
392
	 * Sets element's attribute.
393
	 */
399 lars 394
	public function setAttribute(string $name, mixed $value): static
148 lars 395
	{
396
		$this->attrs[$name] = $value;
397
		return $this;
398
	}
399
 
400
 
401
	/**
402
	 * Returns element's attribute.
403
	 */
399 lars 404
	public function getAttribute(string $name): mixed
148 lars 405
	{
406
		return $this->attrs[$name] ?? null;
407
	}
408
 
409
 
410
	/**
411
	 * Unsets element's attribute.
412
	 */
399 lars 413
	public function removeAttribute(string $name): static
148 lars 414
	{
415
		unset($this->attrs[$name]);
416
		return $this;
417
	}
418
 
419
 
420
	/**
421
	 * Unsets element's attributes.
422
	 */
399 lars 423
	public function removeAttributes(array $attributes): static
148 lars 424
	{
425
		foreach ($attributes as $name) {
426
			unset($this->attrs[$name]);
427
		}
428
 
429
		return $this;
430
	}
431
 
432
 
433
	/**
434
	 * Overloaded setter for element's attribute.
435
	 */
399 lars 436
	final public function __set(string $name, mixed $value): void
148 lars 437
	{
438
		$this->attrs[$name] = $value;
439
	}
440
 
441
 
442
	/**
443
	 * Overloaded getter for element's attribute.
444
	 */
399 lars 445
	final public function &__get(string $name): mixed
148 lars 446
	{
447
		return $this->attrs[$name];
448
	}
449
 
450
 
451
	/**
452
	 * Overloaded tester for element's attribute.
453
	 */
454
	final public function __isset(string $name): bool
455
	{
456
		return isset($this->attrs[$name]);
457
	}
458
 
459
 
460
	/**
461
	 * Overloaded unsetter for element's attribute.
462
	 */
463
	final public function __unset(string $name): void
464
	{
465
		unset($this->attrs[$name]);
466
	}
467
 
468
 
469
	/**
470
	 * Overloaded setter for element's attribute.
471
	 */
399 lars 472
	final public function __call(string $m, array $args): mixed
148 lars 473
	{
474
		$p = substr($m, 0, 3);
475
		if ($p === 'get' || $p === 'set' || $p === 'add') {
476
			$m = substr($m, 3);
477
			$m[0] = $m[0] | "\x20";
478
			if ($p === 'get') {
479
				return $this->attrs[$m] ?? null;
480
 
481
			} elseif ($p === 'add') {
482
				$args[] = true;
483
			}
484
		}
485
 
486
		if (count($args) === 0) { // invalid
487
 
488
		} elseif (count($args) === 1) { // set
489
			$this->attrs[$m] = $args[0];
490
 
491
		} else { // add
492
			$this->appendAttribute($m, $args[0], $args[1]);
493
		}
494
 
495
		return $this;
496
	}
497
 
498
 
499
	/**
500
	 * Special setter for element's attribute.
501
	 */
399 lars 502
	final public function href(string $path, array $query = []): static
148 lars 503
	{
504
		if ($query) {
505
			$query = http_build_query($query, '', '&');
506
			if ($query !== '') {
507
				$path .= '?' . $query;
508
			}
509
		}
510
 
511
		$this->attrs['href'] = $path;
512
		return $this;
513
	}
514
 
515
 
516
	/**
517
	 * Setter for data-* attributes. Booleans are converted to 'true' resp. 'false'.
518
	 */
399 lars 519
	public function data(string $name, mixed $value = null): static
148 lars 520
	{
521
		if (func_num_args() === 1) {
522
			$this->attrs['data'] = $name;
523
		} else {
524
			$this->attrs["data-$name"] = is_bool($value)
525
				? json_encode($value)
526
				: $value;
527
		}
528
 
529
		return $this;
530
	}
531
 
532
 
533
	/**
534
	 * Sets element's HTML content.
535
	 */
399 lars 536
	final public function setHtml(mixed $html): static
148 lars 537
	{
538
		$this->children = [(string) $html];
539
		return $this;
540
	}
541
 
542
 
543
	/**
544
	 * Returns element's HTML content.
545
	 */
546
	final public function getHtml(): string
547
	{
548
		return implode('', $this->children);
549
	}
550
 
551
 
552
	/**
553
	 * Sets element's textual content.
554
	 */
399 lars 555
	final public function setText(mixed $text): static
148 lars 556
	{
557
		if (!$text instanceof HtmlStringable) {
558
			$text = htmlspecialchars((string) $text, ENT_NOQUOTES, 'UTF-8');
559
		}
560
 
561
		$this->children = [(string) $text];
562
		return $this;
563
	}
564
 
565
 
566
	/**
567
	 * Returns element's textual content.
568
	 */
569
	final public function getText(): string
570
	{
571
		return self::htmlToText($this->getHtml());
572
	}
573
 
574
 
575
	/**
576
	 * Adds new element's child.
577
	 */
399 lars 578
	final public function addHtml(mixed $child): static
148 lars 579
	{
580
		return $this->insert(null, $child);
581
	}
582
 
583
 
584
	/**
585
	 * Appends plain-text string to element content.
586
	 */
399 lars 587
	public function addText(mixed $text): static
148 lars 588
	{
589
		if (!$text instanceof HtmlStringable) {
590
			$text = htmlspecialchars((string) $text, ENT_NOQUOTES, 'UTF-8');
591
		}
592
 
593
		return $this->insert(null, $text);
594
	}
595
 
596
 
597
	/**
598
	 * Creates and adds a new Html child.
599
	 */
399 lars 600
	final public function create(string $name, array|string|null $attrs = null): static
148 lars 601
	{
602
		$this->insert(null, $child = static::el($name, $attrs));
603
		return $child;
604
	}
605
 
606
 
607
	/**
608
	 * Inserts child node.
609
	 */
399 lars 610
	public function insert(?int $index, HtmlStringable|string $child, bool $replace = false): static
148 lars 611
	{
612
		$child = $child instanceof self ? $child : (string) $child;
613
		if ($index === null) { // append
614
			$this->children[] = $child;
615
 
616
		} else { // insert or replace
617
			array_splice($this->children, $index, $replace ? 1 : 0, [$child]);
618
		}
619
 
620
		return $this;
621
	}
622
 
623
 
624
	/**
625
	 * Inserts (replaces) child node (\ArrayAccess implementation).
626
	 * @param  int|null  $index  position or null for appending
627
	 * @param  Html|string  $child  Html node or raw HTML string
628
	 */
629
	final public function offsetSet($index, $child): void
630
	{
631
		$this->insert($index, $child, true);
632
	}
633
 
634
 
635
	/**
636
	 * Returns child node (\ArrayAccess implementation).
637
	 * @param  int  $index
638
	 */
399 lars 639
	final public function offsetGet($index): HtmlStringable|string
148 lars 640
	{
641
		return $this->children[$index];
642
	}
643
 
644
 
645
	/**
646
	 * Exists child node? (\ArrayAccess implementation).
647
	 * @param  int  $index
648
	 */
649
	final public function offsetExists($index): bool
650
	{
651
		return isset($this->children[$index]);
652
	}
653
 
654
 
655
	/**
656
	 * Removes child node (\ArrayAccess implementation).
657
	 * @param  int  $index
658
	 */
659
	public function offsetUnset($index): void
660
	{
661
		if (isset($this->children[$index])) {
662
			array_splice($this->children, $index, 1);
663
		}
664
	}
665
 
666
 
667
	/**
668
	 * Returns children count.
669
	 */
670
	final public function count(): int
671
	{
672
		return count($this->children);
673
	}
674
 
675
 
676
	/**
677
	 * Removes all children.
678
	 */
679
	public function removeChildren(): void
680
	{
681
		$this->children = [];
682
	}
683
 
684
 
685
	/**
686
	 * Iterates over elements.
687
	 * @return \ArrayIterator<int, HtmlStringable|string>
688
	 */
689
	final public function getIterator(): \ArrayIterator
690
	{
691
		return new \ArrayIterator($this->children);
692
	}
693
 
694
 
695
	/**
696
	 * Returns all children.
697
	 */
698
	final public function getChildren(): array
699
	{
700
		return $this->children;
701
	}
702
 
703
 
704
	/**
705
	 * Renders element's start tag, content and end tag.
706
	 */
707
	final public function render(?int $indent = null): string
708
	{
709
		$s = $this->startTag();
710
 
711
		if (!$this->isEmpty) {
712
			// add content
713
			if ($indent !== null) {
714
				$indent++;
715
			}
716
 
717
			foreach ($this->children as $child) {
718
				if ($child instanceof self) {
719
					$s .= $child->render($indent);
720
				} else {
721
					$s .= $child;
722
				}
723
			}
724
 
725
			// add end tag
726
			$s .= $this->endTag();
727
		}
728
 
729
		if ($indent !== null) {
730
			return "\n" . str_repeat("\t", $indent - 1) . $s . "\n" . str_repeat("\t", max(0, $indent - 2));
731
		}
732
 
733
		return $s;
734
	}
735
 
736
 
737
	final public function __toString(): string
738
	{
399 lars 739
		return $this->render();
148 lars 740
	}
741
 
742
 
743
	/**
744
	 * Returns element's start tag.
745
	 */
746
	final public function startTag(): string
747
	{
748
		return $this->name
399 lars 749
			? '<' . $this->name . $this->attributes() . '>'
148 lars 750
			: '';
751
	}
752
 
753
 
754
	/**
755
	 * Returns element's end tag.
756
	 */
757
	final public function endTag(): string
758
	{
759
		return $this->name && !$this->isEmpty ? '</' . $this->name . '>' : '';
760
	}
761
 
762
 
763
	/**
764
	 * Returns element's attributes.
765
	 * @internal
766
	 */
767
	final public function attributes(): string
768
	{
769
		if (!is_array($this->attrs)) {
770
			return '';
771
		}
772
 
773
		$s = '';
774
		$attrs = $this->attrs;
775
		foreach ($attrs as $key => $value) {
776
			if ($value === null || $value === false) {
777
				continue;
778
 
779
			} elseif ($value === true) {
399 lars 780
				$s .= ' ' . $key;
148 lars 781
 
782
				continue;
783
 
784
			} elseif (is_array($value)) {
785
				if (strncmp($key, 'data-', 5) === 0) {
786
					$value = Json::encode($value);
787
 
788
				} else {
789
					$tmp = null;
790
					foreach ($value as $k => $v) {
791
						if ($v != null) { // intentionally ==, skip nulls & empty string
792
							// composite 'style' vs. 'others'
793
							$tmp[] = $v === true
794
								? $k
795
								: (is_string($k) ? $k . ':' . $v : $v);
796
						}
797
					}
798
 
799
					if ($tmp === null) {
800
						continue;
801
					}
802
 
803
					$value = implode($key === 'style' || !strncmp($key, 'on', 2) ? ';' : ' ', $tmp);
804
				}
805
			} elseif (is_float($value)) {
806
				$value = rtrim(rtrim(number_format($value, 10, '.', ''), '0'), '.');
807
 
808
			} else {
809
				$value = (string) $value;
810
			}
811
 
399 lars 812
			$q = str_contains($value, '"') ? "'" : '"';
148 lars 813
			$s .= ' ' . $key . '=' . $q
814
				. str_replace(
815
					['&', $q, '<'],
399 lars 816
					['&amp;', $q === '"' ? '&quot;' : '&#39;', '<'],
817
					$value,
148 lars 818
				)
399 lars 819
				. (str_contains($value, '`') && strpbrk($value, ' <>"\'') === false ? ' ' : '')
148 lars 820
				. $q;
821
		}
822
 
823
		$s = str_replace('@', '&#64;', $s);
824
		return $s;
825
	}
826
 
827
 
828
	/**
829
	 * Clones all children too.
830
	 */
831
	public function __clone()
832
	{
833
		foreach ($this->children as $key => $value) {
834
			if (is_object($value)) {
835
				$this->children[$key] = clone $value;
836
			}
837
		}
838
	}
839
}