* @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 `