diff --git a/extra/html-extra/HtmlExtension.php b/extra/html-extra/HtmlExtension.php index d8a6c0036d1..634df7f07d2 100644 --- a/extra/html-extra/HtmlExtension.php +++ b/extra/html-extra/HtmlExtension.php @@ -12,6 +12,7 @@ namespace Twig\Extra\Html; use Symfony\Component\Mime\MimeTypes; +use Twig\Environment; use Twig\Error\RuntimeError; use Twig\Extension\AbstractExtension; use Twig\TwigFilter; @@ -30,6 +31,7 @@ public function getFilters(): array { return [ new TwigFilter('data_uri', [$this, 'dataUri']), + new TwigFilter('html_attr_merge', 'twig_html_attr_merge'), ]; } @@ -38,6 +40,7 @@ public function getFunctions(): array return [ new TwigFunction('html_classes', [self::class, 'htmlClasses']), new TwigFunction('html_cva', [self::class, 'htmlCva']), + new TwigFunction('html_attr', 'twig_html_attr', ['needs_environment' => true, 'is_safe' => ['html']]), ]; } @@ -124,4 +127,75 @@ public static function htmlCva(array|string $base = [], array $variants = [], ar { return new Cva($base, $variants, $compoundVariants, $defaultVariant); } + +static function twig_html_attr_merge(...$arrays): array +{ + $result = []; + + foreach ($arrays as $argNumber => $array) { + if (!$array) { + continue; + } + + if (!twig_test_iterable($array)) { + throw new RuntimeError(sprintf('The "attr_merge" filter only works with arrays or "Traversable", got "%s" for argument %d.', \gettype($array), $argNumber + 1)); + } + + $array = twig_to_array($array); + + foreach (['class', 'style', 'data'] as $deepMergeKey) { + if (isset($array[$deepMergeKey])) { + $value = $array[$deepMergeKey]; + unset($array[$deepMergeKey]); + + if (!twig_test_iterable($value)) { + $value = (array) $value; + } + + $value = twig_to_array($value); + + $result[$deepMergeKey] = array_merge($result[$deepMergeKey] ?? [], $value); + } + } + + $result = array_merge($result, $array); + } + + return $result; +} + +static function twig_html_attr(Environment $env, ...$args): string +{ + $attr = twig_html_attr_merge(...$args); + + if (isset($attr['class'])) { + $attr['class'] = trim(implode(' ', array_values($attr['class']))); + } + + if (isset($attr['style'])) { + $style = ''; + foreach ($attr['style'] as $name => $value) { + if (is_numeric($name)) { + $style .= $value.'; '; + } else { + $style .= $name.': '.$value.'; '; + } + } + $attr['style'] = trim($style); + } + + if (isset($attr['data'])) { + foreach ($attr['data'] as $name => $value) { + $attr['data-'.$name] = $value; + } + unset($attr['data']); + } + + $result = ''; + foreach ($attr as $name => $value) { + $result .= twig_escape_filter($env, $name, 'html_attr').'="'.htmlspecialchars($value).'" '; + } + + return trim($result); +} }