diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b5a5635c..28317c9a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ 1. [](#new) * Added links and modules support to `HtmlBlock` class * Added module support for twig script tag: `{% script module 'theme://js/module.mjs' %}` + * Added twig tag for links: `{% link icon 'theme://images/favicon.png' priority: 20 with { type: 'image/png' } %}` # v1.7.27.1 ## 01/12/2022 diff --git a/system/src/Grav/Common/Twig/Extension/GravExtension.php b/system/src/Grav/Common/Twig/Extension/GravExtension.php index b6f30906b..dcff83750 100644 --- a/system/src/Grav/Common/Twig/Extension/GravExtension.php +++ b/system/src/Grav/Common/Twig/Extension/GravExtension.php @@ -22,6 +22,7 @@ use Grav\Common\Page\Media; use Grav\Common\Scheduler\Cron; use Grav\Common\Security; use Grav\Common\Twig\TokenParser\TwigTokenParserCache; +use Grav\Common\Twig\TokenParser\TwigTokenParserLink; use Grav\Common\Twig\TokenParser\TwigTokenParserRender; use Grav\Common\Twig\TokenParser\TwigTokenParserScript; use Grav\Common\Twig\TokenParser\TwigTokenParserStyle; @@ -252,6 +253,7 @@ class GravExtension extends AbstractExtension implements GlobalsInterface new TwigTokenParserTryCatch(), new TwigTokenParserScript(), new TwigTokenParserStyle(), + new TwigTokenParserLink(), new TwigTokenParserMarkdown(), new TwigTokenParserSwitch(), new TwigTokenParserCache(), diff --git a/system/src/Grav/Common/Twig/Node/TwigNodeLink.php b/system/src/Grav/Common/Twig/Node/TwigNodeLink.php new file mode 100644 index 000000000..8fff84e00 --- /dev/null +++ b/system/src/Grav/Common/Twig/Node/TwigNodeLink.php @@ -0,0 +1,98 @@ + $file, 'group' => $group, 'priority' => $priority, 'attributes' => $attributes]; + $nodes = array_filter($nodes); + + parent::__construct($nodes, ['rel' => $rel], $lineno, $tag); + } + + /** + * Compiles the node to PHP. + * + * @param Compiler $compiler A Twig Compiler instance + * @return void + * @throws LogicException + */ + public function compile(Compiler $compiler): void + { + $compiler->addDebugInfo($this); + if (!$this->hasNode('file')) { + return; + } + + $compiler->write("\$assets = \\Grav\\Common\\Grav::instance()['assets'];\n"); + + $compiler->write('$attributes = [\'rel\' => \'' . $this->getAttribute('rel') . '\'];' . "\n"); + if ($this->hasNode('attributes')) { + $compiler + ->write('$attributes += ') + ->subcompile($this->getNode('attributes')) + ->raw(";\n") + ->write("if (!is_array(\$attributes)) {\n") + ->indent() + ->write("throw new UnexpectedValueException('{% {$this->tagName} with x %}: x is not an array');\n") + ->outdent() + ->write("}\n"); + } + + if ($this->hasNode('group')) { + $compiler + ->write("\$attributes['group'] = ") + ->subcompile($this->getNode('group')) + ->raw(";\n") + ->write("if (!is_string(\$attributes['group'])) {\n") + ->indent() + ->write("throw new UnexpectedValueException('{% {$this->tagName} in x %}: x is not a string');\n") + ->outdent() + ->write("}\n"); + } + + if ($this->hasNode('priority')) { + $compiler + ->write("\$attributes['priority'] = (int)(") + ->subcompile($this->getNode('priority')) + ->raw(");\n"); + } + + $compiler + ->write('$assets->addLink(') + ->subcompile($this->getNode('file')) + ->raw(", \$attributes);\n"); + } +} diff --git a/system/src/Grav/Common/Twig/TokenParser/TwigTokenParserLink.php b/system/src/Grav/Common/Twig/TokenParser/TwigTokenParserLink.php new file mode 100644 index 000000000..2cb0208eb --- /dev/null +++ b/system/src/Grav/Common/Twig/TokenParser/TwigTokenParserLink.php @@ -0,0 +1,109 @@ +getLine(); + + [$rel, $file, $group, $priority, $attributes] = $this->parseArguments($token); + + return new TwigNodeLink($rel, $file, $group, $priority, $attributes, $lineno, $this->getTag()); + } + + /** + * @param Token $token + * @return array + */ + protected function parseArguments(Token $token): array + { + $stream = $this->parser->getStream(); + + + $rel = null; + if ($stream->test(Token::NAME_TYPE, $this->rel)) { + $rel = $stream->getCurrent()->getValue(); + $stream->next(); + } + + $file = null; + if (!$stream->test(Token::NAME_TYPE) && !$stream->test(Token::BLOCK_END_TYPE)) { + $file = $this->parser->getExpressionParser()->parseExpression(); + } + + $group = null; + if ($stream->nextIf(Token::NAME_TYPE, 'at')) { + $group = $this->parser->getExpressionParser()->parseExpression(); + } + + $priority = null; + if ($stream->nextIf(Token::NAME_TYPE, 'priority')) { + $stream->expect(Token::PUNCTUATION_TYPE, ':'); + $priority = $this->parser->getExpressionParser()->parseExpression(); + } + + $attributes = null; + if ($stream->nextIf(Token::NAME_TYPE, 'with')) { + $attributes = $this->parser->getExpressionParser()->parseExpression(); + } + + $stream->expect(Token::BLOCK_END_TYPE); + + return [$rel, $file, $group, $priority, $attributes]; + } + + /** + * Gets the tag name associated with this token parser. + * + * @return string The tag name + */ + public function getTag(): string + { + return 'link'; + } +}