Namespaces

  • Latte
    • Loaders
    • Macros
    • Runtime
  • Nette
    • Application
      • Responses
      • Routers
      • UI
    • Bridges
      • ApplicationDI
      • ApplicationLatte
      • ApplicationTracy
      • CacheDI
      • CacheLatte
      • DatabaseDI
      • DatabaseTracy
      • DITracy
      • FormsDI
      • FormsLatte
      • Framework
      • HttpDI
      • HttpTracy
      • MailDI
      • ReflectionDI
      • SecurityDI
      • SecurityTracy
    • Caching
      • Storages
    • ComponentModel
    • Database
      • Conventions
      • Drivers
      • Table
    • DI
      • Config
        • Adapters
      • Extensions
    • Forms
      • Controls
      • Rendering
    • Http
    • Iterators
    • Loaders
    • Localization
    • Mail
    • Neon
    • PhpGenerator
    • Reflection
    • Security
    • Tokenizer
    • Utils
  • Tracy
    • Bridges
      • Nette
  • none

Classes

  • ArrayHash
  • ArrayList
  • Arrays
  • Callback
  • DateTime
  • FileSystem
  • Finder
  • Html
  • Image
  • Json
  • ObjectMixin
  • Paginator
  • Random
  • Reflection
  • Strings
  • TokenIterator
  • Tokenizer
  • Validators

Interfaces

  • IHtmlString

Exceptions

  • AssertionException
  • ImageException
  • JsonException
  • RegexpException
  • TokenizerException
  • UnknownImageFileException
  • Overview
  • Namespace
  • Class
  • Tree
  • Deprecated
  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: namespace Nette\Utils;
  9: 
 10: use Nette;
 11: 
 12: 
 13: /**
 14:  * HTML helper.
 15:  *
 16:  * <code>
 17:  * $el = Html::el('a')->href($link)->setText('Nette');
 18:  * $el->class = 'myclass';
 19:  * echo $el;
 20:  *
 21:  * echo $el->startTag(), $el->endTag();
 22:  * </code>
 23:  */
 24: class Html implements \ArrayAccess, \Countable, \IteratorAggregate, IHtmlString
 25: {
 26:     use Nette\SmartObject;
 27: 
 28:     /** @var array  element's attributes */
 29:     public $attrs = [];
 30: 
 31:     /** @var bool  use XHTML syntax? */
 32:     public static $xhtml = false;
 33: 
 34:     /** @var array  empty (void) elements */
 35:     public static $emptyElements = [
 36:         'img' => 1, 'hr' => 1, 'br' => 1, 'input' => 1, 'meta' => 1, 'area' => 1, 'embed' => 1, 'keygen' => 1,
 37:         'source' => 1, 'base' => 1, 'col' => 1, 'link' => 1, 'param' => 1, 'basefont' => 1, 'frame' => 1,
 38:         'isindex' => 1, 'wbr' => 1, 'command' => 1, 'track' => 1,
 39:     ];
 40: 
 41:     /** @var array  of Html | string nodes */
 42:     protected $children = [];
 43: 
 44:     /** @var string  element's name */
 45:     private $name;
 46: 
 47:     /** @var bool  is element empty? */
 48:     private $isEmpty;
 49: 
 50: 
 51:     /**
 52:      * Static factory.
 53:      * @param  string element name (or null)
 54:      * @param  array|string element's attributes or plain text content
 55:      * @return static
 56:      */
 57:     public static function el($name = null, $attrs = null)
 58:     {
 59:         $el = new static;
 60:         $parts = explode(' ', (string) $name, 2);
 61:         $el->setName($parts[0]);
 62: 
 63:         if (is_array($attrs)) {
 64:             $el->attrs = $attrs;
 65: 
 66:         } elseif ($attrs !== null) {
 67:             $el->setText($attrs);
 68:         }
 69: 
 70:         if (isset($parts[1])) {
 71:             foreach (Strings::matchAll($parts[1] . ' ', '#([a-z0-9:-]+)(?:=(["\'])?(.*?)(?(2)\\2|\s))?#i') as $m) {
 72:                 $el->attrs[$m[1]] = isset($m[3]) ? $m[3] : true;
 73:             }
 74:         }
 75: 
 76:         return $el;
 77:     }
 78: 
 79: 
 80:     /**
 81:      * Changes element's name.
 82:      * @param  string
 83:      * @param  bool  Is element empty?
 84:      * @return static
 85:      * @throws Nette\InvalidArgumentException
 86:      */
 87:     public function setName($name, $isEmpty = null)
 88:     {
 89:         if ($name !== null && !is_string($name)) {
 90:             throw new Nette\InvalidArgumentException(sprintf('Name must be string or null, %s given.', gettype($name)));
 91:         }
 92: 
 93:         $this->name = $name;
 94:         $this->isEmpty = $isEmpty === null ? isset(static::$emptyElements[$name]) : (bool) $isEmpty;
 95:         return $this;
 96:     }
 97: 
 98: 
 99:     /**
100:      * Returns element's name.
101:      * @return string
102:      */
103:     public function getName()
104:     {
105:         return $this->name;
106:     }
107: 
108: 
109:     /**
110:      * Is element empty?
111:      * @return bool
112:      */
113:     public function isEmpty()
114:     {
115:         return $this->isEmpty;
116:     }
117: 
118: 
119:     /**
120:      * Sets multiple attributes.
121:      * @param  array
122:      * @return static
123:      */
124:     public function addAttributes(array $attrs)
125:     {
126:         $this->attrs = array_merge($this->attrs, $attrs);
127:         return $this;
128:     }
129: 
130: 
131:     /**
132:      * Appends value to element's attribute.
133:      * @param  string
134:      * @param  string|array value to append
135:      * @param  string|bool  value option
136:      * @return static
137:      */
138:     public function appendAttribute($name, $value, $option = true)
139:     {
140:         if (is_array($value)) {
141:             $prev = isset($this->attrs[$name]) ? (array) $this->attrs[$name] : [];
142:             $this->attrs[$name] = $value + $prev;
143: 
144:         } elseif ((string) $value === '') {
145:             $tmp = &$this->attrs[$name]; // appending empty value? -> ignore, but ensure it exists
146: 
147:         } elseif (!isset($this->attrs[$name]) || is_array($this->attrs[$name])) { // needs array
148:             $this->attrs[$name][$value] = $option;
149: 
150:         } else {
151:             $this->attrs[$name] = [$this->attrs[$name] => true, $value => $option];
152:         }
153:         return $this;
154:     }
155: 
156: 
157:     /**
158:      * Sets element's attribute.
159:      * @param  string
160:      * @param  mixed
161:      * @return static
162:      */
163:     public function setAttribute($name, $value)
164:     {
165:         $this->attrs[$name] = $value;
166:         return $this;
167:     }
168: 
169: 
170:     /**
171:      * Returns element's attribute.
172:      * @param  string
173:      * @return mixed
174:      */
175:     public function getAttribute($name)
176:     {
177:         return isset($this->attrs[$name]) ? $this->attrs[$name] : null;
178:     }
179: 
180: 
181:     /**
182:      * Unsets element's attribute.
183:      * @param  string
184:      * @return static
185:      */
186:     public function removeAttribute($name)
187:     {
188:         unset($this->attrs[$name]);
189:         return $this;
190:     }
191: 
192: 
193:     /**
194:      * Unsets element's attributes.
195:      * @return static
196:      */
197:     public function removeAttributes(array $attributes)
198:     {
199:         foreach ($attributes as $name) {
200:             unset($this->attrs[$name]);
201:         }
202:         return $this;
203:     }
204: 
205: 
206:     /**
207:      * Overloaded setter for element's attribute.
208:      * @param  string    HTML attribute name
209:      * @param  mixed     HTML attribute value
210:      * @return void
211:      */
212:     public function __set($name, $value)
213:     {
214:         $this->attrs[$name] = $value;
215:     }
216: 
217: 
218:     /**
219:      * Overloaded getter for element's attribute.
220:      * @param  string    HTML attribute name
221:      * @return mixed     HTML attribute value
222:      */
223:     public function &__get($name)
224:     {
225:         return $this->attrs[$name];
226:     }
227: 
228: 
229:     /**
230:      * Overloaded tester for element's attribute.
231:      * @param  string    HTML attribute name
232:      * @return bool
233:      */
234:     public function __isset($name)
235:     {
236:         return isset($this->attrs[$name]);
237:     }
238: 
239: 
240:     /**
241:      * Overloaded unsetter for element's attribute.
242:      * @param  string    HTML attribute name
243:      * @return void
244:      */
245:     public function __unset($name)
246:     {
247:         unset($this->attrs[$name]);
248:     }
249: 
250: 
251:     /**
252:      * Overloaded setter for element's attribute.
253:      * @param  string  HTML attribute name
254:      * @param  array   (string) HTML attribute value or pair?
255:      * @return mixed
256:      */
257:     public function __call($m, $args)
258:     {
259:         $p = substr($m, 0, 3);
260:         if ($p === 'get' || $p === 'set' || $p === 'add') {
261:             $m = substr($m, 3);
262:             $m[0] = $m[0] | "\x20";
263:             if ($p === 'get') {
264:                 return isset($this->attrs[$m]) ? $this->attrs[$m] : null;
265: 
266:             } elseif ($p === 'add') {
267:                 $args[] = true;
268:             }
269:         }
270: 
271:         if (count($args) === 0) { // invalid
272: 
273:         } elseif (count($args) === 1) { // set
274:             $this->attrs[$m] = $args[0];
275: 
276:         } else { // add
277:             $this->appendAttribute($m, $args[0], $args[1]);
278:         }
279: 
280:         return $this;
281:     }
282: 
283: 
284:     /**
285:      * Special setter for element's attribute.
286:      * @param  string path
287:      * @param  array query
288:      * @return static
289:      */
290:     public function href($path, $query = null)
291:     {
292:         if ($query) {
293:             $query = http_build_query($query, '', '&');
294:             if ($query !== '') {
295:                 $path .= '?' . $query;
296:             }
297:         }
298:         $this->attrs['href'] = $path;
299:         return $this;
300:     }
301: 
302: 
303:     /**
304:      * Setter for data-* attributes. Booleans are converted to 'true' resp. 'false'.
305:      * @return static
306:      */
307:     public function data($name, $value = null)
308:     {
309:         if (func_num_args() === 1) {
310:             $this->attrs['data'] = $name;
311:         } else {
312:             $this->attrs["data-$name"] = is_bool($value) ? json_encode($value) : $value;
313:         }
314:         return $this;
315:     }
316: 
317: 
318:     /**
319:      * Sets element's HTML content.
320:      * @param  IHtmlString|string
321:      * @return static
322:      * @throws Nette\InvalidArgumentException
323:      */
324:     public function setHtml($html)
325:     {
326:         if (is_array($html)) {
327:             throw new Nette\InvalidArgumentException(sprintf('Textual content must be a scalar, %s given.', gettype($html)));
328:         }
329:         $this->removeChildren();
330:         $this->children[] = (string) $html;
331:         return $this;
332:     }
333: 
334: 
335:     /**
336:      * Returns element's HTML content.
337:      * @return string
338:      */
339:     public function getHtml()
340:     {
341:         return implode('', $this->children);
342:     }
343: 
344: 
345:     /**
346:      * Sets element's textual content.
347:      * @param  IHtmlString|string
348:      * @return static
349:      */
350:     public function setText($text)
351:     {
352:         if (!$text instanceof IHtmlString) {
353:             $text = htmlspecialchars($text, ENT_NOQUOTES, 'UTF-8');
354:         }
355:         return $this->setHtml($text);
356:     }
357: 
358: 
359:     /**
360:      * Returns element's textual content.
361:      * @return string
362:      */
363:     public function getText()
364:     {
365:         return html_entity_decode(strip_tags($this->getHtml()), ENT_QUOTES, 'UTF-8');
366:     }
367: 
368: 
369:     /**
370:      * @deprecated
371:      */
372:     public function add($child)
373:     {
374:         trigger_error(__METHOD__ . '() is deprecated, use addHtml() or addText() instead.', E_USER_DEPRECATED);
375:         return $this->addHtml($child);
376:     }
377: 
378: 
379:     /**
380:      * Adds new element's child.
381:      * @param  IHtmlString|string  Html node or raw HTML string
382:      * @return static
383:      */
384:     public function addHtml($child)
385:     {
386:         return $this->insert(null, $child);
387:     }
388: 
389: 
390:     /**
391:      * Appends plain-text string to element content.
392:      * @param  IHtmlString|string|int|float
393:      * @return static
394:      */
395:     public function addText($text)
396:     {
397:         if (!$text instanceof IHtmlString) {
398:             $text = htmlspecialchars((string) $text, ENT_NOQUOTES, 'UTF-8');
399:         }
400:         return $this->insert(null, $text);
401:     }
402: 
403: 
404:     /**
405:      * Creates and adds a new Html child.
406:      * @param  string  elements's name
407:      * @param  array|string element's attributes or raw HTML string
408:      * @return static  created element
409:      */
410:     public function create($name, $attrs = null)
411:     {
412:         $this->insert(null, $child = static::el($name, $attrs));
413:         return $child;
414:     }
415: 
416: 
417:     /**
418:      * Inserts child node.
419:      * @param  int|null position or null for appending
420:      * @param  IHtmlString|string Html node or raw HTML string
421:      * @param  bool
422:      * @return static
423:      * @throws Nette\InvalidArgumentException
424:      */
425:     public function insert($index, $child, $replace = false)
426:     {
427:         if ($child instanceof IHtmlString || is_scalar($child)) {
428:             $child = $child instanceof self ? $child : (string) $child;
429:             if ($index === null) { // append
430:                 $this->children[] = $child;
431: 
432:             } else { // insert or replace
433:                 array_splice($this->children, (int) $index, $replace ? 1 : 0, [$child]);
434:             }
435: 
436:         } else {
437:             throw new Nette\InvalidArgumentException(sprintf('Child node must be scalar or Html object, %s given.', is_object($child) ? get_class($child) : gettype($child)));
438:         }
439: 
440:         return $this;
441:     }
442: 
443: 
444:     /**
445:      * Inserts (replaces) child node (\ArrayAccess implementation).
446:      * @param  int|null position or null for appending
447:      * @param  Html|string Html node or raw HTML string
448:      * @return void
449:      */
450:     public function offsetSet($index, $child)
451:     {
452:         $this->insert($index, $child, true);
453:     }
454: 
455: 
456:     /**
457:      * Returns child node (\ArrayAccess implementation).
458:      * @param  int
459:      * @return static|string
460:      */
461:     public function offsetGet($index)
462:     {
463:         return $this->children[$index];
464:     }
465: 
466: 
467:     /**
468:      * Exists child node? (\ArrayAccess implementation).
469:      * @param  int
470:      * @return bool
471:      */
472:     public function offsetExists($index)
473:     {
474:         return isset($this->children[$index]);
475:     }
476: 
477: 
478:     /**
479:      * Removes child node (\ArrayAccess implementation).
480:      * @param  int
481:      * @return void
482:      */
483:     public function offsetUnset($index)
484:     {
485:         if (isset($this->children[$index])) {
486:             array_splice($this->children, (int) $index, 1);
487:         }
488:     }
489: 
490: 
491:     /**
492:      * Returns children count.
493:      * @return int
494:      */
495:     public function count()
496:     {
497:         return count($this->children);
498:     }
499: 
500: 
501:     /**
502:      * Removes all children.
503:      * @return void
504:      */
505:     public function removeChildren()
506:     {
507:         $this->children = [];
508:     }
509: 
510: 
511:     /**
512:      * Iterates over elements.
513:      * @return \ArrayIterator
514:      */
515:     public function getIterator()
516:     {
517:         return new \ArrayIterator($this->children);
518:     }
519: 
520: 
521:     /**
522:      * Returns all children.
523:      * @return array
524:      */
525:     public function getChildren()
526:     {
527:         return $this->children;
528:     }
529: 
530: 
531:     /**
532:      * Renders element's start tag, content and end tag.
533:      * @param  int
534:      * @return string
535:      */
536:     public function render($indent = null)
537:     {
538:         $s = $this->startTag();
539: 
540:         if (!$this->isEmpty) {
541:             // add content
542:             if ($indent !== null) {
543:                 $indent++;
544:             }
545:             foreach ($this->children as $child) {
546:                 if ($child instanceof self) {
547:                     $s .= $child->render($indent);
548:                 } else {
549:                     $s .= $child;
550:                 }
551:             }
552: 
553:             // add end tag
554:             $s .= $this->endTag();
555:         }
556: 
557:         if ($indent !== null) {
558:             return "\n" . str_repeat("\t", $indent - 1) . $s . "\n" . str_repeat("\t", max(0, $indent - 2));
559:         }
560:         return $s;
561:     }
562: 
563: 
564:     public function __toString()
565:     {
566:         try {
567:             return $this->render();
568:         } catch (\Exception $e) {
569:         } catch (\Throwable $e) {
570:         }
571:         trigger_error('Exception in ' . __METHOD__ . "(): {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}", E_USER_ERROR);
572:     }
573: 
574: 
575:     /**
576:      * Returns element's start tag.
577:      * @return string
578:      */
579:     public function startTag()
580:     {
581:         if ($this->name) {
582:             return '<' . $this->name . $this->attributes() . (static::$xhtml && $this->isEmpty ? ' />' : '>');
583: 
584:         } else {
585:             return '';
586:         }
587:     }
588: 
589: 
590:     /**
591:      * Returns element's end tag.
592:      * @return string
593:      */
594:     public function endTag()
595:     {
596:         return $this->name && !$this->isEmpty ? '</' . $this->name . '>' : '';
597:     }
598: 
599: 
600:     /**
601:      * Returns element's attributes.
602:      * @return string
603:      * @internal
604:      */
605:     public function attributes()
606:     {
607:         if (!is_array($this->attrs)) {
608:             return '';
609:         }
610: 
611:         $s = '';
612:         $attrs = $this->attrs;
613:         if (isset($attrs['data']) && is_array($attrs['data'])) { // deprecated
614:             trigger_error('Expanded attribute "data" is deprecated.', E_USER_DEPRECATED);
615:             foreach ($attrs['data'] as $key => $value) {
616:                 $attrs['data-' . $key] = $value;
617:             }
618:             unset($attrs['data']);
619:         }
620: 
621:         foreach ($attrs as $key => $value) {
622:             if ($value === null || $value === false) {
623:                 continue;
624: 
625:             } elseif ($value === true) {
626:                 if (static::$xhtml) {
627:                     $s .= ' ' . $key . '="' . $key . '"';
628:                 } else {
629:                     $s .= ' ' . $key;
630:                 }
631:                 continue;
632: 
633:             } elseif (is_array($value)) {
634:                 if (strncmp($key, 'data-', 5) === 0) {
635:                     $value = Json::encode($value);
636: 
637:                 } else {
638:                     $tmp = null;
639:                     foreach ($value as $k => $v) {
640:                         if ($v != null) { // intentionally ==, skip nulls & empty string
641:                             // composite 'style' vs. 'others'
642:                             $tmp[] = $v === true ? $k : (is_string($k) ? $k . ':' . $v : $v);
643:                         }
644:                     }
645:                     if ($tmp === null) {
646:                         continue;
647:                     }
648: 
649:                     $value = implode($key === 'style' || !strncmp($key, 'on', 2) ? ';' : ' ', $tmp);
650:                 }
651: 
652:             } elseif (is_float($value)) {
653:                 $value = rtrim(rtrim(number_format($value, 10, '.', ''), '0'), '.');
654: 
655:             } else {
656:                 $value = (string) $value;
657:             }
658: 
659:             $q = strpos($value, '"') === false ? '"' : "'";
660:             $s .= ' ' . $key . '=' . $q
661:                 . str_replace(
662:                     ['&', $q, '<'],
663:                     ['&amp;', $q === '"' ? '&quot;' : '&#39;', self::$xhtml ? '&lt;' : '<'],
664:                     $value
665:                 )
666:                 . (strpos($value, '`') !== false && strpbrk($value, ' <>"\'') === false ? ' ' : '')
667:                 . $q;
668:         }
669: 
670:         $s = str_replace('@', '&#64;', $s);
671:         return $s;
672:     }
673: 
674: 
675:     /**
676:      * Clones all children too.
677:      */
678:     public function __clone()
679:     {
680:         foreach ($this->children as $key => $value) {
681:             if (is_object($value)) {
682:                 $this->children[$key] = clone $value;
683:             }
684:         }
685:     }
686: }
687: 
Nette 2.4-20180206 API API documentation generated by ApiGen 2.8.0