first commit
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Common\Twig
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Twig\Node;
|
||||
|
||||
use Twig\Compiler;
|
||||
use Twig\Node\Node;
|
||||
use Twig\Node\NodeOutputInterface;
|
||||
|
||||
class TwigNodeMarkdown extends Node implements NodeOutputInterface
|
||||
{
|
||||
/**
|
||||
* TwigNodeMarkdown constructor.
|
||||
* @param Node $body
|
||||
* @param int $lineno
|
||||
* @param string $tag
|
||||
*/
|
||||
public function __construct(Node $body, $lineno, $tag = 'markdown')
|
||||
{
|
||||
parent::__construct(['body' => $body], [], $lineno, $tag);
|
||||
}
|
||||
/**
|
||||
* Compiles the node to PHP.
|
||||
*
|
||||
* @param Compiler $compiler A Twig_Compiler instance
|
||||
*/
|
||||
public function compile(Compiler $compiler)
|
||||
{
|
||||
$compiler
|
||||
->addDebugInfo($this)
|
||||
->write('ob_start();' . PHP_EOL)
|
||||
->subcompile($this->getNode('body'))
|
||||
->write('$content = ob_get_clean();' . PHP_EOL)
|
||||
->write('preg_match("/^\s*/", $content, $matches);' . PHP_EOL)
|
||||
->write('$lines = explode("\n", $content);' . PHP_EOL)
|
||||
->write('$content = preg_replace(\'/^\' . $matches[0]. \'/\', "", $lines);' . PHP_EOL)
|
||||
->write('$content = join("\n", $content);' . PHP_EOL)
|
||||
->write('echo $this->env->getExtension(\'Grav\Common\Twig\TwigExtension\')->markdownFunction($context, $content);' . PHP_EOL);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Common\Twig
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Twig\Node;
|
||||
|
||||
use Twig\Compiler;
|
||||
use Twig\Node\Expression\AbstractExpression;
|
||||
use Twig\Node\Node;
|
||||
use Twig\Node\NodeCaptureInterface;
|
||||
|
||||
class TwigNodeRender extends Node implements NodeCaptureInterface
|
||||
{
|
||||
protected $tagName = 'render';
|
||||
|
||||
/**
|
||||
* @param AbstractExpression $object
|
||||
* @param AbstractExpression|null $layout
|
||||
* @param AbstractExpression|null $context
|
||||
* @param int $lineno
|
||||
* @param string|null $tag
|
||||
*/
|
||||
public function __construct(
|
||||
AbstractExpression $object,
|
||||
?AbstractExpression $layout,
|
||||
?AbstractExpression $context,
|
||||
$lineno,
|
||||
$tag = null
|
||||
)
|
||||
{
|
||||
parent::__construct(['object' => $object, 'layout' => $layout, 'context' => $context], [], $lineno, $tag);
|
||||
}
|
||||
/**
|
||||
* Compiles the node to PHP.
|
||||
*
|
||||
* @param Compiler $compiler A Twig_Compiler instance
|
||||
* @throws \LogicException
|
||||
*/
|
||||
public function compile(Compiler $compiler)
|
||||
{
|
||||
$compiler->addDebugInfo($this);
|
||||
$compiler->write('$object = ')->subcompile($this->getNode('object'))->raw(';' . PHP_EOL);
|
||||
|
||||
$layout = $this->getNode('layout');
|
||||
if ($layout) {
|
||||
$compiler->write('$layout = ')->subcompile($layout)->raw(';' . PHP_EOL);
|
||||
} else {
|
||||
$compiler->write('$layout = null;' . PHP_EOL);
|
||||
}
|
||||
|
||||
$context = $this->getNode('context');
|
||||
if ($context) {
|
||||
$compiler->write('$attributes = ')->subcompile($context)->raw(';' . PHP_EOL);
|
||||
} else {
|
||||
$compiler->write('$attributes = null;' . PHP_EOL);
|
||||
}
|
||||
|
||||
$compiler
|
||||
->write('$html = $object->render($layout, $attributes ?? []);' . PHP_EOL)
|
||||
->write('$block = $context[\'block\'] ?? null;' . PHP_EOL)
|
||||
->write('if ($block instanceof \Grav\Framework\ContentBlock\ContentBlock && $html instanceof \Grav\Framework\ContentBlock\ContentBlock) {' . PHP_EOL)
|
||||
->indent()
|
||||
->write('$block->addBlock($html);' . PHP_EOL)
|
||||
->write('echo $html->getToken();' . PHP_EOL)
|
||||
->outdent()
|
||||
->write('} else {' . PHP_EOL)
|
||||
->indent()
|
||||
->write('echo (string)$html;' . PHP_EOL)
|
||||
->outdent()
|
||||
->write('}' . PHP_EOL)
|
||||
;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Common\Twig
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Twig\Node;
|
||||
|
||||
use Twig\Compiler;
|
||||
use Twig\Node\Expression\AbstractExpression;
|
||||
use Twig\Node\Node;
|
||||
use Twig\Node\NodeCaptureInterface;
|
||||
|
||||
class TwigNodeScript extends Node implements NodeCaptureInterface
|
||||
{
|
||||
protected $tagName = 'script';
|
||||
|
||||
/**
|
||||
* TwigNodeScript constructor.
|
||||
* @param Node|null $body
|
||||
* @param AbstractExpression|null $file
|
||||
* @param AbstractExpression|null $group
|
||||
* @param AbstractExpression|null $priority
|
||||
* @param AbstractExpression|null $attributes
|
||||
* @param int $lineno
|
||||
* @param string|null $tag
|
||||
*/
|
||||
public function __construct(
|
||||
Node $body = null,
|
||||
AbstractExpression $file = null,
|
||||
AbstractExpression $group = null,
|
||||
AbstractExpression $priority = null,
|
||||
AbstractExpression $attributes = null,
|
||||
$lineno = 0,
|
||||
$tag = null
|
||||
)
|
||||
{
|
||||
parent::__construct(['body' => $body, 'file' => $file, 'group' => $group, 'priority' => $priority, 'attributes' => $attributes], [], $lineno, $tag);
|
||||
}
|
||||
/**
|
||||
* Compiles the node to PHP.
|
||||
*
|
||||
* @param Compiler $compiler A Twig_Compiler instance
|
||||
* @throws \LogicException
|
||||
*/
|
||||
public function compile(Compiler $compiler)
|
||||
{
|
||||
$compiler->addDebugInfo($this);
|
||||
|
||||
$compiler->write("\$assets = \\Grav\\Common\\Grav::instance()['assets'];\n");
|
||||
|
||||
if ($this->getNode('attributes') !== null) {
|
||||
$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");
|
||||
} else {
|
||||
$compiler->write('$attributes = [];' . "\n");
|
||||
}
|
||||
|
||||
if ($this->getNode('group') !== null) {
|
||||
$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->getNode('priority') !== null) {
|
||||
$compiler
|
||||
->write("\$attributes['priority'] = (int)(")
|
||||
->subcompile($this->getNode('priority'))
|
||||
->raw(");\n");
|
||||
}
|
||||
|
||||
if ($this->getNode('file') !== null) {
|
||||
$compiler
|
||||
->write('$assets->addJs(')
|
||||
->subcompile($this->getNode('file'))
|
||||
->raw(", \$attributes);\n");
|
||||
} else {
|
||||
$compiler
|
||||
->write("ob_start();\n")
|
||||
->subcompile($this->getNode('body'))
|
||||
->write('$content = ob_get_clean();' . "\n")
|
||||
->write("\$assets->addInlineJs(\$content, \$attributes);\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Common\Twig
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Twig\Node;
|
||||
|
||||
use Twig\Compiler;
|
||||
use Twig\Node\Expression\AbstractExpression;
|
||||
use Twig\Node\Node;
|
||||
use Twig\Node\NodeCaptureInterface;
|
||||
|
||||
class TwigNodeStyle extends Node implements NodeCaptureInterface
|
||||
{
|
||||
protected $tagName = 'style';
|
||||
|
||||
/**
|
||||
* TwigNodeAssets constructor.
|
||||
* @param Node|null $body
|
||||
* @param AbstractExpression|null $file
|
||||
* @param AbstractExpression|null $group
|
||||
* @param AbstractExpression|null $priority
|
||||
* @param AbstractExpression|null $attributes
|
||||
* @param int $lineno
|
||||
* @param string|null $tag
|
||||
*/
|
||||
public function __construct(
|
||||
Node $body = null,
|
||||
AbstractExpression $file = null,
|
||||
AbstractExpression $group = null,
|
||||
AbstractExpression $priority = null,
|
||||
AbstractExpression $attributes = null,
|
||||
$lineno = 0,
|
||||
$tag = null
|
||||
)
|
||||
{
|
||||
parent::__construct(['body' => $body, 'file' => $file, 'group' => $group, 'priority' => $priority, 'attributes' => $attributes], [], $lineno, $tag);
|
||||
}
|
||||
/**
|
||||
* Compiles the node to PHP.
|
||||
*
|
||||
* @param Compiler $compiler A Twig_Compiler instance
|
||||
* @throws \LogicException
|
||||
*/
|
||||
public function compile(Compiler $compiler)
|
||||
{
|
||||
$compiler->addDebugInfo($this);
|
||||
|
||||
$compiler->write("\$assets = \\Grav\\Common\\Grav::instance()['assets'];\n");
|
||||
|
||||
if ($this->getNode('attributes') !== null) {
|
||||
$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");
|
||||
} else {
|
||||
$compiler->write('$attributes = [];' . "\n");
|
||||
}
|
||||
|
||||
if ($this->getNode('group') !== null) {
|
||||
$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->getNode('priority') !== null) {
|
||||
$compiler
|
||||
->write("\$attributes['priority'] = (int)(")
|
||||
->subcompile($this->getNode('priority'))
|
||||
->raw(");\n");
|
||||
}
|
||||
|
||||
if ($this->getNode('file') !== null) {
|
||||
$compiler
|
||||
->write('$assets->addCss(')
|
||||
->subcompile($this->getNode('file'))
|
||||
->raw(", \$attributes);\n");
|
||||
} else {
|
||||
$compiler
|
||||
->write("ob_start();\n")
|
||||
->subcompile($this->getNode('body'))
|
||||
->write('$content = ob_get_clean();' . "\n")
|
||||
->write("\$assets->addInlineCss(\$content, \$attributes);\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Common\Twig
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Twig\Node;
|
||||
|
||||
use Twig\Compiler;
|
||||
use Twig\Node\Node;
|
||||
|
||||
class TwigNodeSwitch extends Node
|
||||
{
|
||||
/**
|
||||
* TwigNodeSwitch constructor.
|
||||
* @param Node $value
|
||||
* @param Node $cases
|
||||
* @param Node|null $default
|
||||
* @param int $lineno
|
||||
* @param string|null $tag
|
||||
*/
|
||||
public function __construct(
|
||||
Node $value,
|
||||
Node $cases,
|
||||
Node $default = null,
|
||||
$lineno = 0,
|
||||
$tag = null
|
||||
)
|
||||
{
|
||||
parent::__construct(array('value' => $value, 'cases' => $cases, 'default' => $default), array(), $lineno, $tag);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compiles the node to PHP.
|
||||
*
|
||||
* @param Compiler $compiler A Twig_Compiler instance
|
||||
*/
|
||||
public function compile(Compiler $compiler)
|
||||
{
|
||||
$compiler
|
||||
->addDebugInfo($this)
|
||||
->write('switch (')
|
||||
->subcompile($this->getNode('value'))
|
||||
->raw(") {\n")
|
||||
->indent();
|
||||
|
||||
foreach ($this->getNode('cases') as $case) {
|
||||
if (!$case->hasNode('body')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($case->getNode('values') as $value) {
|
||||
$compiler
|
||||
->write('case ')
|
||||
->subcompile($value)
|
||||
->raw(":\n");
|
||||
}
|
||||
|
||||
$compiler
|
||||
->write("{\n")
|
||||
->indent()
|
||||
->subcompile($case->getNode('body'))
|
||||
->write("break;\n")
|
||||
->outdent()
|
||||
->write("}\n");
|
||||
}
|
||||
|
||||
if ($this->hasNode('default') && $this->getNode('default') !== null) {
|
||||
$compiler
|
||||
->write("default:\n")
|
||||
->write("{\n")
|
||||
->indent()
|
||||
->subcompile($this->getNode('default'))
|
||||
->outdent()
|
||||
->write("}\n");
|
||||
}
|
||||
|
||||
$compiler
|
||||
->outdent()
|
||||
->write("}\n");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Common\Twig
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Twig\Node;
|
||||
|
||||
use Twig\Compiler;
|
||||
use Twig\Node\Node;
|
||||
|
||||
class TwigNodeThrow extends Node
|
||||
{
|
||||
/**
|
||||
* TwigNodeThrow constructor.
|
||||
* @param int $code
|
||||
* @param Node $message
|
||||
* @param int $lineno
|
||||
* @param string|null $tag
|
||||
*/
|
||||
public function __construct(
|
||||
$code,
|
||||
Node $message,
|
||||
$lineno = 0,
|
||||
$tag = null
|
||||
)
|
||||
{
|
||||
parent::__construct(['message' => $message], ['code' => $code], $lineno, $tag);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compiles the node to PHP.
|
||||
*
|
||||
* @param Compiler $compiler A Twig_Compiler instance
|
||||
* @throws \LogicException
|
||||
*/
|
||||
public function compile(Compiler $compiler)
|
||||
{
|
||||
$compiler->addDebugInfo($this);
|
||||
|
||||
$compiler
|
||||
->write('throw new \RuntimeException(')
|
||||
->subcompile($this->getNode('message'))
|
||||
->write(', ')
|
||||
->write($this->getAttribute('code') ?: 500)
|
||||
->write(");\n");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Common\Twig
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Twig\Node;
|
||||
|
||||
use Twig\Compiler;
|
||||
use Twig\Node\Node;
|
||||
|
||||
class TwigNodeTryCatch extends Node
|
||||
{
|
||||
/**
|
||||
* TwigNodeTryCatch constructor.
|
||||
* @param Node $try
|
||||
* @param Node|null $catch
|
||||
* @param int $lineno
|
||||
* @param string|null $tag
|
||||
*/
|
||||
public function __construct(
|
||||
Node $try,
|
||||
Node $catch = null,
|
||||
$lineno = 0,
|
||||
$tag = null
|
||||
)
|
||||
{
|
||||
parent::__construct(['try' => $try, 'catch' => $catch], [], $lineno, $tag);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compiles the node to PHP.
|
||||
*
|
||||
* @param Compiler $compiler A Twig_Compiler instance
|
||||
* @throws \LogicException
|
||||
*/
|
||||
public function compile(Compiler $compiler)
|
||||
{
|
||||
$compiler->addDebugInfo($this);
|
||||
|
||||
$compiler
|
||||
->write('try {')
|
||||
;
|
||||
|
||||
$compiler
|
||||
->indent()
|
||||
->subcompile($this->getNode('try'))
|
||||
;
|
||||
|
||||
if ($this->hasNode('catch') && null !== $this->getNode('catch')) {
|
||||
$compiler
|
||||
->outdent()
|
||||
->write('} catch (\Exception $e) {' . "\n")
|
||||
->indent()
|
||||
->write('if (isset($context[\'grav\'][\'debugger\'])) $context[\'grav\'][\'debugger\']->addException($e);' . "\n")
|
||||
->write('$context[\'e\'] = $e;' . "\n")
|
||||
->subcompile($this->getNode('catch'))
|
||||
;
|
||||
}
|
||||
|
||||
$compiler
|
||||
->outdent()
|
||||
->write("}\n");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Common\Twig
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Twig\TokenParser;
|
||||
|
||||
use Grav\Common\Twig\Node\TwigNodeMarkdown;
|
||||
use Twig\Token;
|
||||
use Twig\TokenParser\AbstractTokenParser;
|
||||
|
||||
/**
|
||||
* Adds ability to inline markdown between tags.
|
||||
*
|
||||
* {% markdown %}
|
||||
* This is **bold** and this _underlined_
|
||||
*
|
||||
* 1. This is a bullet list
|
||||
* 2. This is another item in that same list
|
||||
* {% endmarkdown %}
|
||||
*/
|
||||
class TwigTokenParserMarkdown extends AbstractTokenParser
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function parse(Token $token)
|
||||
{
|
||||
$lineno = $token->getLine();
|
||||
$this->parser->getStream()->expect(Token::BLOCK_END_TYPE);
|
||||
$body = $this->parser->subparse(array($this, 'decideMarkdownEnd'), true);
|
||||
$this->parser->getStream()->expect(Token::BLOCK_END_TYPE);
|
||||
return new TwigNodeMarkdown($body, $lineno, $this->getTag());
|
||||
}
|
||||
/**
|
||||
* Decide if current token marks end of Markdown block.
|
||||
*
|
||||
* @param Token $token
|
||||
* @return bool
|
||||
*/
|
||||
public function decideMarkdownEnd(Token $token)
|
||||
{
|
||||
return $token->test('endmarkdown');
|
||||
}
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getTag()
|
||||
{
|
||||
return 'markdown';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Common\Twig
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Twig\TokenParser;
|
||||
|
||||
use Grav\Common\Twig\Node\TwigNodeRender;
|
||||
use Twig\Node\Node;
|
||||
use Twig\Token;
|
||||
use Twig\TokenParser\AbstractTokenParser;
|
||||
|
||||
/**
|
||||
* Renders an object.
|
||||
*
|
||||
* {% render object layout: 'default' with { variable: true } %}
|
||||
*/
|
||||
class TwigTokenParserRender extends AbstractTokenParser
|
||||
{
|
||||
/**
|
||||
* Parses a token and returns a node.
|
||||
*
|
||||
* @param Token $token A Twig_Token instance
|
||||
*
|
||||
* @return Node A Twig_Node instance
|
||||
*/
|
||||
public function parse(Token $token)
|
||||
{
|
||||
$lineno = $token->getLine();
|
||||
|
||||
[$object, $layout, $context] = $this->parseArguments($token);
|
||||
|
||||
return new TwigNodeRender($object, $layout, $context, $lineno, $this->getTag());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Token $token
|
||||
* @return array
|
||||
*/
|
||||
protected function parseArguments(Token $token)
|
||||
{
|
||||
$stream = $this->parser->getStream();
|
||||
|
||||
$object = $this->parser->getExpressionParser()->parseExpression();
|
||||
|
||||
$layout = null;
|
||||
if ($stream->nextIf(Token::NAME_TYPE, 'layout')) {
|
||||
$stream->expect(Token::PUNCTUATION_TYPE, ':');
|
||||
$layout = $this->parser->getExpressionParser()->parseExpression();
|
||||
}
|
||||
|
||||
$context = null;
|
||||
if ($stream->nextIf(Token::NAME_TYPE, 'with')) {
|
||||
$context = $this->parser->getExpressionParser()->parseExpression();
|
||||
}
|
||||
|
||||
$stream->expect(Token::BLOCK_END_TYPE);
|
||||
|
||||
return [$object, $layout, $context];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the tag name associated with this token parser.
|
||||
*
|
||||
* @return string The tag name
|
||||
*/
|
||||
public function getTag()
|
||||
{
|
||||
return 'render';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Common\Twig
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Twig\TokenParser;
|
||||
|
||||
use Grav\Common\Twig\Node\TwigNodeScript;
|
||||
use Twig\Node\Node;
|
||||
use Twig\Token;
|
||||
use Twig\TokenParser\AbstractTokenParser;
|
||||
|
||||
/**
|
||||
* Adds a script to head/bottom/custom location in the document.
|
||||
*
|
||||
* {% script 'theme://js/something.js' in 'bottom' priority: 20 with { defer: true, async: true } %}
|
||||
*
|
||||
* {% script in 'bottom' priority: 20 %}
|
||||
* alert('Warning!');
|
||||
* {% endscript %}
|
||||
|
||||
*/
|
||||
class TwigTokenParserScript extends AbstractTokenParser
|
||||
{
|
||||
/**
|
||||
* Parses a token and returns a node.
|
||||
*
|
||||
* @param Token $token A Twig_Token instance
|
||||
*
|
||||
* @return Node A Twig_Node instance
|
||||
*/
|
||||
public function parse(Token $token)
|
||||
{
|
||||
$lineno = $token->getLine();
|
||||
$stream = $this->parser->getStream();
|
||||
|
||||
list($file, $group, $priority, $attributes) = $this->parseArguments($token);
|
||||
|
||||
$content = null;
|
||||
if ($file === null) {
|
||||
$content = $this->parser->subparse([$this, 'decideBlockEnd'], true);
|
||||
$stream->expect(Token::BLOCK_END_TYPE);
|
||||
}
|
||||
|
||||
return new TwigNodeScript($content, $file, $group, $priority, $attributes, $lineno, $this->getTag());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Token $token
|
||||
* @return array
|
||||
*/
|
||||
protected function parseArguments(Token $token)
|
||||
{
|
||||
$stream = $this->parser->getStream();
|
||||
|
||||
$file = null;
|
||||
if (!$stream->test(Token::NAME_TYPE) && !$stream->test(Token::OPERATOR_TYPE) && !$stream->test(Token::BLOCK_END_TYPE)) {
|
||||
$file = $this->parser->getExpressionParser()->parseExpression();
|
||||
}
|
||||
|
||||
$group = null;
|
||||
if ($stream->nextIf(Token::OPERATOR_TYPE, 'in')) {
|
||||
$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 [$file, $group, $priority, $attributes];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Token $token
|
||||
* @return bool
|
||||
*/
|
||||
public function decideBlockEnd(Token $token)
|
||||
{
|
||||
return $token->test('endscript');
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the tag name associated with this token parser.
|
||||
*
|
||||
* @return string The tag name
|
||||
*/
|
||||
public function getTag()
|
||||
{
|
||||
return 'script';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Common\Twig
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Twig\TokenParser;
|
||||
|
||||
use Grav\Common\Twig\Node\TwigNodeStyle;
|
||||
use Twig\Node\Node;
|
||||
use Twig\Token;
|
||||
use Twig\TokenParser\AbstractTokenParser;
|
||||
|
||||
/**
|
||||
* Adds a style to the document.
|
||||
*
|
||||
* {% style 'theme://css/foo.css' priority: 20 %}
|
||||
|
||||
* {% style priority: 20 with { media: 'screen' } %}
|
||||
* a { color: red; }
|
||||
* {% endstyle %}
|
||||
*/
|
||||
class TwigTokenParserStyle extends AbstractTokenParser
|
||||
{
|
||||
/**
|
||||
* Parses a token and returns a node.
|
||||
*
|
||||
* @param Token $token A Twig_Token instance
|
||||
*
|
||||
* @return Node A Twig_Node instance
|
||||
*/
|
||||
public function parse(Token $token)
|
||||
{
|
||||
$lineno = $token->getLine();
|
||||
$stream = $this->parser->getStream();
|
||||
|
||||
list ($file, $group, $priority, $attributes) = $this->parseArguments($token);
|
||||
|
||||
$content = null;
|
||||
if (!$file) {
|
||||
$content = $this->parser->subparse([$this, 'decideBlockEnd'], true);
|
||||
$stream->expect(Token::BLOCK_END_TYPE);
|
||||
}
|
||||
|
||||
return new TwigNodeStyle($content, $file, $group, $priority, $attributes, $lineno, $this->getTag());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Token $token
|
||||
* @return array
|
||||
*/
|
||||
protected function parseArguments(Token $token)
|
||||
{
|
||||
$stream = $this->parser->getStream();
|
||||
|
||||
$file = null;
|
||||
if (!$stream->test(Token::NAME_TYPE) && !$stream->test(Token::OPERATOR_TYPE) && !$stream->test(Token::BLOCK_END_TYPE)) {
|
||||
$file = $this->parser->getExpressionParser()->parseExpression();
|
||||
}
|
||||
|
||||
$group = null;
|
||||
if ($stream->nextIf(Token::OPERATOR_TYPE, 'in')) {
|
||||
$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 [$file, $group, $priority, $attributes];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Token $token
|
||||
* @return bool
|
||||
*/
|
||||
public function decideBlockEnd(Token $token)
|
||||
{
|
||||
return $token->test('endstyle');
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the tag name associated with this token parser.
|
||||
*
|
||||
* @return string The tag name
|
||||
*/
|
||||
public function getTag()
|
||||
{
|
||||
return 'style';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Common\Twig
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
* @origin https://gist.github.com/maxgalbu/9409182
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Twig\TokenParser;
|
||||
|
||||
use Grav\Common\Twig\Node\TwigNodeSwitch;
|
||||
use Twig\Error\SyntaxError;
|
||||
use Twig\Node\Node;
|
||||
use Twig\Token;
|
||||
use Twig\TokenParser\AbstractTokenParser;
|
||||
|
||||
/**
|
||||
* Adds ability use elegant switch instead of ungainly if statements
|
||||
*
|
||||
* {% switch type %}
|
||||
* {% case 'foo' %}
|
||||
* {{ my_data.foo }}
|
||||
* {% case 'bar' %}
|
||||
* {{ my_data.bar }}
|
||||
* {% default %}
|
||||
* {{ my_data.default }}
|
||||
* {% endswitch %}
|
||||
*/
|
||||
class TwigTokenParserSwitch extends AbstractTokenParser
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function parse(Token $token)
|
||||
{
|
||||
$lineno = $token->getLine();
|
||||
$stream = $this->parser->getStream();
|
||||
|
||||
$name = $this->parser->getExpressionParser()->parseExpression();
|
||||
$stream->expect(Token::BLOCK_END_TYPE);
|
||||
|
||||
// There can be some whitespace between the {% switch %} and first {% case %} tag.
|
||||
while ($stream->getCurrent()->getType() === Token::TEXT_TYPE && trim($stream->getCurrent()->getValue()) === '') {
|
||||
$stream->next();
|
||||
}
|
||||
|
||||
$stream->expect(Token::BLOCK_START_TYPE);
|
||||
|
||||
$expressionParser = $this->parser->getExpressionParser();
|
||||
|
||||
$default = null;
|
||||
$cases = [];
|
||||
$end = false;
|
||||
|
||||
while (!$end) {
|
||||
$next = $stream->next();
|
||||
|
||||
switch ($next->getValue()) {
|
||||
case 'case':
|
||||
$values = [];
|
||||
|
||||
while (true) {
|
||||
$values[] = $expressionParser->parsePrimaryExpression();
|
||||
// Multiple allowed values?
|
||||
if ($stream->test(Token::OPERATOR_TYPE, 'or')) {
|
||||
$stream->next();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$stream->expect(Token::BLOCK_END_TYPE);
|
||||
$body = $this->parser->subparse(array($this, 'decideIfFork'));
|
||||
$cases[] = new Node([
|
||||
'values' => new Node($values),
|
||||
'body' => $body
|
||||
]);
|
||||
break;
|
||||
|
||||
case 'default':
|
||||
$stream->expect(Token::BLOCK_END_TYPE);
|
||||
$default = $this->parser->subparse(array($this, 'decideIfEnd'));
|
||||
break;
|
||||
|
||||
case 'endswitch':
|
||||
$end = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new SyntaxError(sprintf('Unexpected end of template. Twig was looking for the following tags "case", "default", or "endswitch" to close the "switch" block started at line %d)', $lineno), -1);
|
||||
}
|
||||
}
|
||||
|
||||
$stream->expect(Token::BLOCK_END_TYPE);
|
||||
|
||||
return new TwigNodeSwitch($name, new Node($cases), $default, $lineno, $this->getTag());
|
||||
}
|
||||
|
||||
/**
|
||||
* Decide if current token marks switch logic.
|
||||
*
|
||||
* @param Token $token
|
||||
* @return bool
|
||||
*/
|
||||
public function decideIfFork(Token $token)
|
||||
{
|
||||
return $token->test(array('case', 'default', 'endswitch'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Decide if current token marks end of swtich block.
|
||||
*
|
||||
* @param Token $token
|
||||
* @return bool
|
||||
*/
|
||||
public function decideIfEnd(Token $token)
|
||||
{
|
||||
return $token->test(array('endswitch'));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getTag()
|
||||
{
|
||||
return 'switch';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Common\Twig
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Twig\TokenParser;
|
||||
|
||||
use Grav\Common\Twig\Node\TwigNodeThrow;
|
||||
use Twig\Node\Node;
|
||||
use Twig\Token;
|
||||
use Twig\TokenParser\AbstractTokenParser;
|
||||
|
||||
/**
|
||||
* Handles try/catch in template file.
|
||||
*
|
||||
* <pre>
|
||||
* {% throw 404 'Not Found' %}
|
||||
* </pre>
|
||||
*/
|
||||
class TwigTokenParserThrow extends AbstractTokenParser
|
||||
{
|
||||
/**
|
||||
* Parses a token and returns a node.
|
||||
*
|
||||
* @param Token $token A Twig_Token instance
|
||||
*
|
||||
* @return Node A Twig_Node instance
|
||||
*/
|
||||
public function parse(Token $token)
|
||||
{
|
||||
$lineno = $token->getLine();
|
||||
$stream = $this->parser->getStream();
|
||||
|
||||
$code = $stream->expect(Token::NUMBER_TYPE)->getValue();
|
||||
$message = $this->parser->getExpressionParser()->parseExpression();
|
||||
$stream->expect(Token::BLOCK_END_TYPE);
|
||||
|
||||
return new TwigNodeThrow($code, $message, $lineno, $this->getTag());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the tag name associated with this token parser.
|
||||
*
|
||||
* @return string The tag name
|
||||
*/
|
||||
public function getTag()
|
||||
{
|
||||
return 'throw';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Common\Twig
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Twig\TokenParser;
|
||||
|
||||
use Grav\Common\Twig\Node\TwigNodeTryCatch;
|
||||
use Twig\Node\Node;
|
||||
use Twig\Token;
|
||||
use Twig\TokenParser\AbstractTokenParser;
|
||||
|
||||
/**
|
||||
* Handles try/catch in template file.
|
||||
*
|
||||
* <pre>
|
||||
* {% try %}
|
||||
* <li>{{ user.get('name') }}</li>
|
||||
* {% catch %}
|
||||
* {{ e.message }}
|
||||
* {% endcatch %}
|
||||
* </pre>
|
||||
*/
|
||||
class TwigTokenParserTryCatch extends AbstractTokenParser
|
||||
{
|
||||
/**
|
||||
* Parses a token and returns a node.
|
||||
*
|
||||
* @param Token $token A Twig_Token instance
|
||||
*
|
||||
* @return Node A Twig_Node instance
|
||||
*/
|
||||
public function parse(Token $token)
|
||||
{
|
||||
$lineno = $token->getLine();
|
||||
$stream = $this->parser->getStream();
|
||||
|
||||
$stream->expect(Token::BLOCK_END_TYPE);
|
||||
$try = $this->parser->subparse([$this, 'decideCatch']);
|
||||
$stream->next();
|
||||
$stream->expect(Token::BLOCK_END_TYPE);
|
||||
$catch = $this->parser->subparse([$this, 'decideEnd']);
|
||||
$stream->next();
|
||||
$stream->expect(Token::BLOCK_END_TYPE);
|
||||
|
||||
return new TwigNodeTryCatch($try, $catch, $lineno, $this->getTag());
|
||||
}
|
||||
|
||||
public function decideCatch(Token $token)
|
||||
{
|
||||
return $token->test(array('catch'));
|
||||
}
|
||||
|
||||
public function decideEnd(Token $token)
|
||||
{
|
||||
return $token->test(array('endtry')) || $token->test(array('endcatch'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the tag name associated with this token parser.
|
||||
*
|
||||
* @return string The tag name
|
||||
*/
|
||||
public function getTag()
|
||||
{
|
||||
return 'try';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,454 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Common\Twig
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Twig;
|
||||
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\Config\Config;
|
||||
use Grav\Common\Language\Language;
|
||||
use Grav\Common\Language\LanguageCodes;
|
||||
use Grav\Common\Page\Interfaces\PageInterface;
|
||||
use Grav\Common\Page\Pages;
|
||||
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
|
||||
use RocketTheme\Toolbox\Event\Event;
|
||||
use Phive\Twig\Extensions\Deferred\DeferredExtension;
|
||||
|
||||
class Twig
|
||||
{
|
||||
/**
|
||||
* @var \Twig_Environment
|
||||
*/
|
||||
public $twig;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public $twig_vars = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public $twig_paths;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $template;
|
||||
|
||||
/**
|
||||
* @var Grav
|
||||
*/
|
||||
protected $grav;
|
||||
|
||||
/**
|
||||
* @var \Twig_Loader_Filesystem
|
||||
*/
|
||||
protected $loader;
|
||||
|
||||
/**
|
||||
* @var \Twig_Loader_Array
|
||||
*/
|
||||
protected $loaderArray;
|
||||
|
||||
|
||||
protected $autoescape;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param Grav $grav
|
||||
*/
|
||||
public function __construct(Grav $grav)
|
||||
{
|
||||
$this->grav = $grav;
|
||||
$this->twig_paths = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Twig initialization that sets the twig loader chain, then the environment, then extensions
|
||||
* and also the base set of twig vars
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
if (null === $this->twig) {
|
||||
/** @var Config $config */
|
||||
$config = $this->grav['config'];
|
||||
/** @var UniformResourceLocator $locator */
|
||||
$locator = $this->grav['locator'];
|
||||
/** @var Language $language */
|
||||
$language = $this->grav['language'];
|
||||
|
||||
$active_language = $language->getActive();
|
||||
|
||||
// handle language templates if available
|
||||
if ($language->enabled()) {
|
||||
$lang_templates = $locator->findResource('theme://templates/' . ($active_language ? $active_language : $language->getDefault()));
|
||||
if ($lang_templates) {
|
||||
$this->twig_paths[] = $lang_templates;
|
||||
}
|
||||
}
|
||||
|
||||
$this->twig_paths = array_merge($this->twig_paths, $locator->findResources('theme://templates'));
|
||||
|
||||
$this->grav->fireEvent('onTwigTemplatePaths');
|
||||
|
||||
// Add Grav core templates location
|
||||
$core_templates = array_merge($locator->findResources('system://templates'), $locator->findResources('system://templates/testing'));
|
||||
$this->twig_paths = array_merge($this->twig_paths, $core_templates);
|
||||
|
||||
$this->loader = new \Twig_Loader_Filesystem($this->twig_paths);
|
||||
|
||||
// Register all other prefixes as namespaces in twig
|
||||
foreach ($locator->getPaths('theme') as $prefix => $_) {
|
||||
if ($prefix === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$twig_paths = [];
|
||||
|
||||
// handle language templates if available
|
||||
if ($language->enabled()) {
|
||||
$lang_templates = $locator->findResource('theme://'.$prefix.'templates/' . ($active_language ? $active_language : $language->getDefault()));
|
||||
if ($lang_templates) {
|
||||
$twig_paths[] = $lang_templates;
|
||||
}
|
||||
}
|
||||
|
||||
$twig_paths = array_merge($twig_paths, $locator->findResources('theme://'.$prefix.'templates'));
|
||||
|
||||
$namespace = trim($prefix, '/');
|
||||
$this->loader->setPaths($twig_paths, $namespace);
|
||||
}
|
||||
|
||||
$this->grav->fireEvent('onTwigLoader');
|
||||
|
||||
$this->loaderArray = new \Twig_Loader_Array([]);
|
||||
$loader_chain = new \Twig_Loader_Chain([$this->loaderArray, $this->loader]);
|
||||
|
||||
$params = $config->get('system.twig');
|
||||
if (!empty($params['cache'])) {
|
||||
$cachePath = $locator->findResource('cache://twig', true, true);
|
||||
$params['cache'] = new \Twig_Cache_Filesystem($cachePath, \Twig_Cache_Filesystem::FORCE_BYTECODE_INVALIDATION);
|
||||
}
|
||||
|
||||
if (!$config->get('system.strict_mode.twig_compat', true)) {
|
||||
// Force autoescape on for all files if in strict mode.
|
||||
$params['autoescape'] = 'html';
|
||||
} elseif (!empty($this->autoescape)) {
|
||||
$params['autoescape'] = $this->autoescape ? 'html' : false;
|
||||
}
|
||||
|
||||
if (empty($params['autoescape'])) {
|
||||
user_error('Grav 2.0 will have Twig auto-escaping forced on (can be emulated by turning off \'system.strict_mode.twig_compat\' setting in your configuration)', E_USER_DEPRECATED);
|
||||
}
|
||||
|
||||
$this->twig = new TwigEnvironment($loader_chain, $params);
|
||||
|
||||
if ($config->get('system.twig.undefined_functions')) {
|
||||
$this->twig->registerUndefinedFunctionCallback(function ($name) {
|
||||
if (function_exists($name)) {
|
||||
return new \Twig_SimpleFunction($name, $name);
|
||||
}
|
||||
|
||||
return new \Twig_SimpleFunction($name, function () {
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if ($config->get('system.twig.undefined_filters')) {
|
||||
$this->twig->registerUndefinedFilterCallback(function ($name) {
|
||||
if (function_exists($name)) {
|
||||
return new \Twig_SimpleFilter($name, $name);
|
||||
}
|
||||
|
||||
return new \Twig_SimpleFilter($name, function () {
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
$this->grav->fireEvent('onTwigInitialized');
|
||||
|
||||
// set default date format if set in config
|
||||
if ($config->get('system.pages.dateformat.long')) {
|
||||
/** @var \Twig_Extension_Core $extension */
|
||||
$extension = $this->twig->getExtension('Twig_Extension_Core');
|
||||
$extension->setDateFormat($config->get('system.pages.dateformat.long'));
|
||||
}
|
||||
// enable the debug extension if required
|
||||
if ($config->get('system.twig.debug')) {
|
||||
$this->twig->addExtension(new \Twig_Extension_Debug());
|
||||
}
|
||||
$this->twig->addExtension(new TwigExtension());
|
||||
$this->twig->addExtension(new DeferredExtension());
|
||||
|
||||
$this->grav->fireEvent('onTwigExtensions');
|
||||
|
||||
/** @var Pages $pages */
|
||||
$pages = $this->grav['pages'];
|
||||
|
||||
// Set some standard variables for twig
|
||||
$this->twig_vars += [
|
||||
'config' => $config,
|
||||
'system' => $config->get('system'),
|
||||
'theme' => $config->get('theme'),
|
||||
'site' => $config->get('site'),
|
||||
'uri' => $this->grav['uri'],
|
||||
'assets' => $this->grav['assets'],
|
||||
'taxonomy' => $this->grav['taxonomy'],
|
||||
'browser' => $this->grav['browser'],
|
||||
'base_dir' => rtrim(ROOT_DIR, '/'),
|
||||
'home_url' => $pages->homeUrl($active_language),
|
||||
'base_url' => $pages->baseUrl($active_language),
|
||||
'base_url_absolute' => $pages->baseUrl($active_language, true),
|
||||
'base_url_relative' => $pages->baseUrl($active_language, false),
|
||||
'base_url_simple' => $this->grav['base_url'],
|
||||
'theme_dir' => $locator->findResource('theme://'),
|
||||
'theme_url' => $this->grav['base_url'] . '/' . $locator->findResource('theme://', false),
|
||||
'html_lang' => $this->grav['language']->getActive() ?: $config->get('site.default_lang', 'en'),
|
||||
'language_codes' => new LanguageCodes,
|
||||
];
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Twig_Environment
|
||||
*/
|
||||
public function twig()
|
||||
{
|
||||
return $this->twig;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Twig_Loader_Filesystem
|
||||
*/
|
||||
public function loader()
|
||||
{
|
||||
return $this->loader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds or overrides a template.
|
||||
*
|
||||
* @param string $name The template name
|
||||
* @param string $template The template source
|
||||
*/
|
||||
public function setTemplate($name, $template)
|
||||
{
|
||||
$this->loaderArray->setTemplate($name, $template);
|
||||
}
|
||||
|
||||
/**
|
||||
* Twig process that renders a page item. It supports two variations:
|
||||
* 1) Handles modular pages by rendering a specific page based on its modular twig template
|
||||
* 2) Renders individual page items for twig processing before the site rendering
|
||||
*
|
||||
* @param PageInterface $item The page item to render
|
||||
* @param string $content Optional content override
|
||||
*
|
||||
* @return string The rendered output
|
||||
* @throws \Twig_Error_Loader
|
||||
*/
|
||||
public function processPage(PageInterface $item, $content = null)
|
||||
{
|
||||
$content = $content ?? $item->content();
|
||||
|
||||
// override the twig header vars for local resolution
|
||||
$this->grav->fireEvent('onTwigPageVariables', new Event(['page' => $item]));
|
||||
$twig_vars = $this->twig_vars;
|
||||
|
||||
$twig_vars['page'] = $item;
|
||||
$twig_vars['media'] = $item->media();
|
||||
$twig_vars['header'] = $item->header();
|
||||
|
||||
$local_twig = clone $this->twig;
|
||||
|
||||
$output = '';
|
||||
try {
|
||||
// Process Modular Twig
|
||||
if ($item->modularTwig()) {
|
||||
$twig_vars['content'] = $content;
|
||||
$extension = $item->templateFormat();
|
||||
$extension = $extension ? ".{$extension}.twig" : TEMPLATE_EXT;
|
||||
$template = $item->template() . $extension;
|
||||
$output = $content = $local_twig->render($template, $twig_vars);
|
||||
}
|
||||
|
||||
// Process in-page Twig
|
||||
if ($item->shouldProcess('twig')) {
|
||||
$name = '@Page:' . $item->path();
|
||||
$this->setTemplate($name, $content);
|
||||
$output = $local_twig->render($name, $twig_vars);
|
||||
}
|
||||
|
||||
} catch (\Twig_Error_Loader $e) {
|
||||
throw new \RuntimeException($e->getRawMessage(), 404, $e);
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a Twig template directly by using a template name
|
||||
* and optional array of variables
|
||||
*
|
||||
* @param string $template template to render with
|
||||
* @param array $vars Optional variables
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function processTemplate($template, $vars = [])
|
||||
{
|
||||
// override the twig header vars for local resolution
|
||||
$this->grav->fireEvent('onTwigTemplateVariables');
|
||||
$vars += $this->twig_vars;
|
||||
|
||||
try {
|
||||
$output = $this->twig->render($template, $vars);
|
||||
} catch (\Twig_Error_Loader $e) {
|
||||
throw new \RuntimeException($e->getRawMessage(), 404, $e);
|
||||
}
|
||||
|
||||
return $output;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Process a Twig template directly by using a Twig string
|
||||
* and optional array of variables
|
||||
*
|
||||
* @param string $string string to render.
|
||||
* @param array $vars Optional variables
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function processString($string, array $vars = [])
|
||||
{
|
||||
// override the twig header vars for local resolution
|
||||
$this->grav->fireEvent('onTwigStringVariables');
|
||||
$vars += $this->twig_vars;
|
||||
|
||||
$name = '@Var:' . $string;
|
||||
$this->setTemplate($name, $string);
|
||||
|
||||
try {
|
||||
$output = $this->twig->render($name, $vars);
|
||||
} catch (\Twig_Error_Loader $e) {
|
||||
throw new \RuntimeException($e->getRawMessage(), 404, $e);
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Twig process that renders the site layout. This is the main twig process that renders the overall
|
||||
* page and handles all the layout for the site display.
|
||||
*
|
||||
* @param string $format Output format (defaults to HTML).
|
||||
*
|
||||
* @return string the rendered output
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function processSite($format = null, array $vars = [])
|
||||
{
|
||||
// set the page now its been processed
|
||||
$this->grav->fireEvent('onTwigSiteVariables');
|
||||
$pages = $this->grav['pages'];
|
||||
$page = $this->grav['page'];
|
||||
$content = $page->content();
|
||||
|
||||
$twig_vars = $this->twig_vars;
|
||||
|
||||
$twig_vars['theme'] = $this->grav['config']->get('theme');
|
||||
$twig_vars['pages'] = $pages->root();
|
||||
$twig_vars['page'] = $page;
|
||||
$twig_vars['header'] = $page->header();
|
||||
$twig_vars['media'] = $page->media();
|
||||
$twig_vars['content'] = $content;
|
||||
$ext = '.' . ($format ?: 'html') . TWIG_EXT;
|
||||
|
||||
// determine if params are set, if so disable twig cache
|
||||
$params = $this->grav['uri']->params(null, true);
|
||||
if (!empty($params)) {
|
||||
$this->twig->setCache(false);
|
||||
}
|
||||
|
||||
// Get Twig template layout
|
||||
$template = $this->template($page->template() . $ext);
|
||||
|
||||
try {
|
||||
$output = $this->twig->render($template, $vars + $twig_vars);
|
||||
} catch (\Twig_Error_Loader $e) {
|
||||
$error_msg = $e->getMessage();
|
||||
// Try html version of this template if initial template was NOT html
|
||||
if ($ext !== '.html' . TWIG_EXT) {
|
||||
try {
|
||||
$page->templateFormat('html');
|
||||
$output = $this->twig->render($page->template() . '.html' . TWIG_EXT, $vars + $twig_vars);
|
||||
} catch (\Twig_Error_Loader $e) {
|
||||
throw new \RuntimeException($error_msg, 400, $e);
|
||||
}
|
||||
} else {
|
||||
throw new \RuntimeException($error_msg, 400, $e);
|
||||
}
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps the Twig_Loader_Filesystem addPath method (should be used only in `onTwigLoader()` event
|
||||
* @param string $template_path
|
||||
* @param string $namespace
|
||||
*/
|
||||
public function addPath($template_path, $namespace = '__main__')
|
||||
{
|
||||
$this->loader->addPath($template_path, $namespace);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps the Twig_Loader_Filesystem prependPath method (should be used only in `onTwigLoader()` event
|
||||
* @param string $template_path
|
||||
* @param string $namespace
|
||||
*/
|
||||
public function prependPath($template_path, $namespace = '__main__')
|
||||
{
|
||||
$this->loader->prependPath($template_path, $namespace);
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple helper method to get the twig template if it has already been set, else return
|
||||
* the one being passed in
|
||||
*
|
||||
* @param string $template the template name
|
||||
*
|
||||
* @return string the template name
|
||||
*/
|
||||
public function template($template)
|
||||
{
|
||||
return $this->template ?? $template;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides the autoescape setting
|
||||
*
|
||||
* @param bool $state
|
||||
* @deprecated 1.5 Auto-escape should always be turned on to protect against XSS issues (can be disabled per template file).
|
||||
*/
|
||||
public function setAutoescape($state)
|
||||
{
|
||||
if (!$state) {
|
||||
user_error(__CLASS__ . '::' . __FUNCTION__ . '(false) is deprecated since Grav 1.5', E_USER_DEPRECATED);
|
||||
}
|
||||
|
||||
$this->autoescape = (bool) $state;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Common\Twig
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Twig;
|
||||
|
||||
class TwigEnvironment extends \Twig_Environment
|
||||
{
|
||||
use WriteCacheFileTrait;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Grav\Common\Twig
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\Twig;
|
||||
|
||||
use Grav\Common\Filesystem\Folder;
|
||||
use Grav\Common\Grav;
|
||||
|
||||
trait WriteCacheFileTrait
|
||||
{
|
||||
protected static $umask;
|
||||
|
||||
/**
|
||||
* This exists so template cache files use the same
|
||||
* group between apache and cli
|
||||
*
|
||||
* @param string $file
|
||||
* @param string $content
|
||||
*/
|
||||
protected function writeCacheFile($file, $content)
|
||||
{
|
||||
if (empty($file)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isset(self::$umask)) {
|
||||
self::$umask = Grav::instance()['config']->get('system.twig.umask_fix', false);
|
||||
}
|
||||
|
||||
if (self::$umask) {
|
||||
$dir = dirname($file);
|
||||
if (!is_dir($dir)) {
|
||||
$old = umask(0002);
|
||||
Folder::create($dir);
|
||||
umask($old);
|
||||
}
|
||||
parent::writeCacheFile($file, $content);
|
||||
chmod($file, 0775);
|
||||
} else {
|
||||
parent::writeCacheFile($file, $content);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user