* @link https://getkirby.com * @copyright Bastian Allgeier * @license https://opensource.org/licenses/MIT */ class Html extends Xml { /** * An internal store for an HTML entities translation table */ public static array|null $entities; /** * List of HTML tags that can be used inline */ public static array $inlineList = [ 'b', 'i', 'small', 'abbr', 'cite', 'code', 'dfn', 'em', 'kbd', 'strong', 'samp', 'var', 'a', 'bdo', 'br', 'img', 'q', 'span', 'sub', 'sup' ]; /** * Closing string for void tags; * can be used to switch to trailing slashes if required * * ```php * Html::$void = ' />' * ``` * * @var string */ public static $void = '>'; /** * List of HTML tags that are considered to be self-closing * * @var array */ public static $voidList = [ 'area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', 'keygen', 'link', 'meta', 'param', 'source', 'track', 'wbr' ]; /** * Generic HTML tag generator * Can be called like `Html::p('A paragraph', ['class' => 'text'])` * * @param string $tag Tag name * @param array $arguments Further arguments for the Html::tag() method * @return string */ public static function __callStatic(string $tag, array $arguments = []): string { if (static::isVoid($tag) === true) { return static::tag($tag, null, ...$arguments); } return static::tag($tag, ...$arguments); } /** * Generates an `` tag; automatically supports mailto: and tel: links * * @param string $href The URL for the `` tag * @param string|array|null $text The optional text; if `null`, the URL will be used as text * @param array $attr Additional attributes for the tag * @return string The generated HTML */ public static function a(string $href, $text = null, array $attr = []): string { if (Str::startsWith($href, 'mailto:')) { return static::email(substr($href, 7), $text, $attr); } if (Str::startsWith($href, 'tel:')) { return static::tel(substr($href, 4), $text, $attr); } return static::link($href, $text, $attr); } /** * Generates a single attribute or a list of attributes * * @param string|array $name String: A single attribute with that name will be generated. * Key-value array: A list of attributes will be generated. Don't pass a second argument in that case. * @param mixed $value If used with a `$name` string, pass the value of the attribute here. * If used with a `$name` array, this can be set to `false` to disable attribute sorting. * @param string|null $before An optional string that will be prepended if the result is not empty * @param string|null $after An optional string that will be appended if the result is not empty * @return string|null The generated HTML attributes string */ public static function attr($name, $value = null, string|null $before = null, string|null $after = null): string|null { // HTML supports boolean attributes without values if (is_array($name) === false && is_bool($value) === true) { return $value === true ? strtolower($name) : null; } // HTML attribute names are case-insensitive if (is_string($name) === true) { $name = strtolower($name); } // all other cases can share the XML variant $attr = parent::attr($name, $value); if ($attr === null) { return null; } // HTML supports named entities $entities = parent::entities(); $html = array_keys($entities); $xml = array_values($entities); $attr = str_replace($xml, $html, $attr); if ($attr) { return $before . $attr . $after; } return null; } /** * Converts lines in a string into HTML breaks * * @param string $string * @return string */ public static function breaks(string $string): string { return nl2br($string); } /** * Generates an `` tag with `mailto:` * * @param string $email The email address * @param string|array|null $text The optional text; if `null`, the email address will be used as text * @param array $attr Additional attributes for the tag * @return string The generated HTML */ public static function email(string $email, $text = null, array $attr = []): string { if (empty($email) === true) { return ''; } if (empty($text) === true) { // show only the email address without additional parameters $address = Str::contains($email, '?') ? Str::before($email, '?') : $email; $text = [Str::encode($address)]; } $email = Str::encode($email); $attr = array_merge([ 'href' => [ 'value' => 'mailto:' . $email, 'escape' => false ] ], $attr); // add rel=noopener to target blank links to improve security $attr['rel'] = static::rel($attr['rel'] ?? null, $attr['target'] ?? null); return static::tag('a', $text, $attr); } /** * Converts a string to an HTML-safe string * * @param string|null $string * @param bool $keepTags If true, existing tags won't be escaped * @return string The HTML string * * @psalm-suppress ParamNameMismatch */ public static function encode(string|null $string, bool $keepTags = false): string { if ($string === null) { return ''; } if ($keepTags === true) { $list = static::entities(); unset($list['"'], $list['<'], $list['>'], $list['&']); $search = array_keys($list); $values = array_values($list); return str_replace($search, $values, $string); } return htmlentities($string, ENT_QUOTES, 'utf-8'); } /** * Returns the entity translation table * * @return array */ public static function entities(): array { return self::$entities ??= get_html_translation_table(HTML_ENTITIES); } /** * Creates a `
` tag with optional caption * * @param string|array $content Contents of the `
` tag * @param string|array $caption Optional `
` text to use * @param array $attr Additional attributes for the `
` tag * @return string The generated HTML */ public static function figure($content, $caption = '', array $attr = []): string { if ($caption) { $figcaption = static::tag('figcaption', $caption); if (is_string($content) === true) { $content = [static::encode($content, false)]; } $content[] = $figcaption; } return static::tag('figure', $content, $attr); } /** * Embeds a GitHub Gist * * @param string $url Gist URL * @param string|null $file Optional specific file to embed * @param array $attr Additional attributes for the `