first commit

This commit is contained in:
2018-08-01 16:00:05 +02:00
commit 944fd76206
3205 changed files with 356767 additions and 0 deletions
@@ -0,0 +1,35 @@
<?php
/**
* @package Grav.Common.Twig
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Twig\Node;
class TwigNodeMarkdown extends \Twig_Node implements \Twig_NodeOutputInterface
{
public function __construct(\Twig_Node $body, $lineno, $tag = 'markdown')
{
parent::__construct(array('body' => $body), array(), $lineno, $tag);
}
/**
* Compiles the node to PHP.
*
* @param \Twig_Compiler A Twig_Compiler instance
*/
public function compile(\Twig_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($content);' . PHP_EOL);
}
}
@@ -0,0 +1,102 @@
<?php
/**
* @package Grav.Common.Twig
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Twig\Node;
class TwigNodeScript extends \Twig_Node implements \Twig_NodeOutputInterface
{
protected $tagName = 'script';
/**
* TwigNodeScript constructor.
* @param \Twig_NodeInterface|null $body
* @param \Twig_Node_Expression|null $file
* @param \Twig_Node_Expression|null $group
* @param \Twig_Node_Expression|null $priority
* @param \Twig_Node_Expression|null $attributes
* @param int $lineno
* @param string|null $tag
*/
public function __construct(
\Twig_NodeInterface $body = null,
\Twig_Node_Expression $file = null,
\Twig_Node_Expression $group = null,
\Twig_Node_Expression $priority = null,
\Twig_Node_Expression $attributes = null,
$lineno,
$tag = null
)
{
parent::__construct(['body' => $body, 'file' => $file, 'group' => $group, 'priority' => $priority, 'attributes' => $attributes], [], $lineno, $tag);
}
/**
* Compiles the node to PHP.
*
* @param \Twig_Compiler $compiler A Twig_Compiler instance
* @throws \LogicException
*/
public function compile(\Twig_Compiler $compiler)
{
$compiler->addDebugInfo($this);
if ($this->getNode('attributes') !== null) {
$compiler
->write('$attributes = ')
->subcompile($this->getNode('attributes'))
->raw(";\n")
->write("if (\$attributes !== null && !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('$group = ')
->subcompile($this->getNode('group'))
->raw(";\n")
->write("if (\$group !== null && !is_string(\$group)) {\n")
->indent()
->write("throw new UnexpectedValueException('{% {$this->tagName} in x %}: x is not a string');\n")
->outdent()
->write("}\n");
} else {
$compiler->write('$group = null;' . "\n");
}
if ($this->getNode('priority') !== null) {
$compiler
->write('$priority = (int)(')
->subcompile($this->getNode('priority'))
->raw(");\n");
} else {
$compiler->write('$priority = null;' . "\n");
}
$compiler->write("\$assets = \\Grav\\Common\\Grav::instance()['assets'];\n");
if ($this->getNode('file') !== null) {
$compiler
->write('$file = ')
->subcompile($this->getNode('file'))
->write(";\n")
->write("\$pipeline = !empty(\$attributes['pipeline']);\n")
->write("\$loading = !empty(\$attributes['defer']) ? 'defer' : (!empty(\$attributes['async']) ? 'async' : null);\n")
->write("\$assets->addJs(\$file, \$priority, \$pipeline, \$loading, \$group);\n");
} else {
$compiler
->write("ob_start();\n")
->subcompile($this->getNode('body'))
->write("\$content = ob_get_clean();")
->write("\$assets->addInlineJs(\$content, \$priority, \$group, \$attributes);\n");
}
}
}
@@ -0,0 +1,98 @@
<?php
/**
* @package Grav.Common.Twig
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Twig\Node;
class TwigNodeStyle extends \Twig_Node implements \Twig_NodeOutputInterface
{
protected $tagName = 'style';
/**
* TwigNodeAssets constructor.
* @param \Twig_NodeInterface|null $body
* @param \Twig_Node_Expression|null $attributes
* @param int $lineno
* @param null $tag
*/
public function __construct(
\Twig_NodeInterface $body = null,
\Twig_Node_Expression $file = null,
\Twig_Node_Expression $group = null,
\Twig_Node_Expression $priority = null,
\Twig_Node_Expression $attributes = null,
$lineno,
$tag = null
)
{
parent::__construct(['body' => $body, 'file' => $file, 'group' => $group, 'priority' => $priority, 'attributes' => $attributes], [], $lineno, $tag);
}
/**
* Compiles the node to PHP.
*
* @param \Twig_Compiler $compiler A Twig_Compiler instance
* @throws \LogicException
*/
public function compile(\Twig_Compiler $compiler)
{
$compiler->addDebugInfo($this);
if ($this->getNode('attributes') !== null) {
$compiler
->write('$attributes = ')
->subcompile($this->getNode('attributes'))
->raw(";\n")
->write("if (\$attributes !== null && !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('$group = ')
->subcompile($this->getNode('group'))
->raw(";\n")
->write("if (\$group !== null && !is_string(\$group)) {\n")
->indent()
->write("throw new UnexpectedValueException('{% {$this->tagName} in x %}: x is not a string');\n")
->outdent()
->write("}\n");
} else {
$compiler->write('$group = null;' . "\n");
}
if ($this->getNode('priority') !== null) {
$compiler
->write('$priority = (int)(')
->subcompile($this->getNode('priority'))
->raw(");\n");
} else {
$compiler->write('$priority = null;' . "\n");
}
$compiler->write("\$assets = \\Grav\\Common\\Grav::instance()['assets'];\n");
if ($this->getNode('file') !== null) {
$compiler
->write('$file = ')
->subcompile($this->getNode('file'))
->write(";\n")
->write("\$pipeline = !empty(\$attributes['pipeline']);\n")
->write("\$assets->addCss(\$file, \$priority, \$pipeline, \$group);\n");
} else {
$compiler
->write("ob_start();\n")
->subcompile($this->getNode('body'))
->write("\$content = ob_get_clean();")
->write("\$assets->addInlineCss(\$content, \$priority, \$group);\n");
}
}
}
@@ -0,0 +1,71 @@
<?php
/**
* @package Grav.Common.Twig
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Twig\Node;
class TwigNodeSwitch extends \Twig_Node implements \Twig_NodeOutputInterface
{
public function __construct(\Twig_NodeInterface $value, \Twig_NodeInterface $cases, \Twig_NodeInterface $default = null, $lineno, $tag = null)
{
parent::__construct(array('value' => $value, 'cases' => $cases, 'default' => $default), array(), $lineno, $tag);
}
/**
* Compiles the node to PHP.
*
* @param \Twig_Compiler A Twig_Compiler instance
*/
public function compile(\Twig_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,52 @@
<?php
/**
* @package Grav.Common.Twig
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Twig\Node;
class TwigNodeTryCatch extends \Twig_Node
{
public function __construct(\Twig_NodeInterface $try, \Twig_NodeInterface $catch = null, $lineno, $tag = null)
{
parent::__construct(array('try' => $try, 'catch' => $catch), array(), $lineno, $tag);
}
/**
* Compiles the node to PHP.
*
* @param \Twig_Compiler $compiler A Twig_Compiler instance
* @throws \LogicException
*/
public function compile(\Twig_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,53 @@
<?php
/**
* @package Grav.Common.Twig
*
* @copyright Copyright (C) 2015 - 2018 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;
/**
* 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 \Twig_TokenParser
{
/**
* {@inheritdoc}
*/
public function parse(\Twig_Token $token)
{
$lineno = $token->getLine();
$this->parser->getStream()->expect(\Twig_Token::BLOCK_END_TYPE);
$body = $this->parser->subparse(array($this, 'decideMarkdownEnd'), true);
$this->parser->getStream()->expect(\Twig_Token::BLOCK_END_TYPE);
return new TwigNodeMarkdown($body, $lineno, $this->getTag());
}
/**
* Decide if current token marks end of Markdown block.
*
* @param \Twig_Token $token
* @return bool
*/
public function decideMarkdownEnd(\Twig_Token $token)
{
return $token->test('endmarkdown');
}
/**
* {@inheritdoc}
*/
public function getTag()
{
return 'markdown';
}
}
@@ -0,0 +1,100 @@
<?php
/**
* @package Grav.Common.Twig
*
* @copyright Copyright (C) 2015 - 2018 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;
/**
* 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 \Twig_TokenParser
{
/**
* Parses a token and returns a node.
*
* @param \Twig_Token $token A Twig_Token instance
*
* @return \Twig_NodeInterface A Twig_NodeInterface instance
*/
public function parse(\Twig_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(\Twig_Token::BLOCK_END_TYPE);
}
return new TwigNodeScript($content, $file, $group, $priority, $attributes, $lineno, $this->getTag());
}
/**
* @param \Twig_Token $token
* @return array
*/
protected function parseArguments(\Twig_Token $token)
{
$stream = $this->parser->getStream();
$file = null;
if (!$stream->test(\Twig_Token::NAME_TYPE) && !$stream->test(\Twig_Token::OPERATOR_TYPE) && !$stream->test(\Twig_Token::BLOCK_END_TYPE)) {
$file = $this->parser->getExpressionParser()->parseExpression();
}
$group = null;
if ($stream->nextIf(\Twig_Token::OPERATOR_TYPE, 'in')) {
$group = $this->parser->getExpressionParser()->parseExpression();
}
$priority = null;
if ($stream->nextIf(\Twig_Token::NAME_TYPE, 'priority')) {
$stream->expect(\Twig_Token::PUNCTUATION_TYPE, ':');
$priority = $this->parser->getExpressionParser()->parseExpression();
}
$attributes = null;
if ($stream->nextIf(\Twig_Token::NAME_TYPE, 'with')) {
$attributes = $this->parser->getExpressionParser()->parseExpression();
}
$stream->expect(\Twig_Token::BLOCK_END_TYPE);
return [$file, $group, $priority, $attributes];
}
/**
* @param \Twig_Token $token
* @return bool
*/
public function decideBlockEnd(\Twig_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,99 @@
<?php
/**
* @package Grav.Common.Twig
*
* @copyright Copyright (C) 2015 - 2018 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;
/**
* 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 \Twig_TokenParser
{
/**
* Parses a token and returns a node.
*
* @param \Twig_Token $token A Twig_Token instance
*
* @return \Twig_NodeInterface A Twig_NodeInterface instance
*/
public function parse(\Twig_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(\Twig_Token::BLOCK_END_TYPE);
}
return new TwigNodeStyle($content, $file, $group, $priority, $attributes, $lineno, $this->getTag());
}
/**
* @param \Twig_Token $token
* @return array
*/
protected function parseArguments(\Twig_Token $token)
{
$stream = $this->parser->getStream();
$file = null;
if (!$stream->test(\Twig_Token::NAME_TYPE) && !$stream->test(\Twig_Token::OPERATOR_TYPE) && !$stream->test(\Twig_Token::BLOCK_END_TYPE)) {
$file = $this->parser->getExpressionParser()->parseExpression();
}
$group = null;
if ($stream->nextIf(\Twig_Token::OPERATOR_TYPE, 'in')) {
$group = $this->parser->getExpressionParser()->parseExpression();
}
$priority = null;
if ($stream->nextIf(\Twig_Token::NAME_TYPE, 'priority')) {
$stream->expect(\Twig_Token::PUNCTUATION_TYPE, ':');
$priority = $this->parser->getExpressionParser()->parseExpression();
}
$attributes = null;
if ($stream->nextIf(\Twig_Token::NAME_TYPE, 'with')) {
$attributes = $this->parser->getExpressionParser()->parseExpression();
}
$stream->expect(\Twig_Token::BLOCK_END_TYPE);
return [$file, $group, $priority, $attributes];
}
/**
* @param \Twig_Token $token
* @return bool
*/
public function decideBlockEnd(\Twig_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,138 @@
<?php
/**
* @package Grav.Common.Twig
*
* @copyright Copyright (C) 2015 - 2018 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;
/**
* 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 \Twig_TokenParser
{
/**
* {@inheritdoc}
*/
public function parse(\Twig_Token $token)
{
$lineno = $token->getLine();
$stream = $this->parser->getStream();
$name = $this->parser->getExpressionParser()->parseExpression();
$stream->expect(\Twig_Token::BLOCK_END_TYPE);
// There can be some whitespace between the {% switch %} and first {% case %} tag.
while ($stream->getCurrent()->getType() == \Twig_Token::TEXT_TYPE && trim($stream->getCurrent()->getValue()) == '')
{
$stream->next();
}
$stream->expect(\Twig_Token::BLOCK_START_TYPE);
$expressionParser = $this->parser->getExpressionParser();
$default = null;
$cases = array();
$end = false;
while (!$end)
{
$next = $stream->next();
switch ($next->getValue())
{
case 'case':
{
$values = array();
while (true)
{
$values[] = $expressionParser->parsePrimaryExpression();
// Multiple allowed values?
if ($stream->test(\Twig_Token::OPERATOR_TYPE, 'or'))
{
$stream->next();
}
else
{
break;
}
}
$stream->expect(\Twig_Token::BLOCK_END_TYPE);
$body = $this->parser->subparse(array($this, 'decideIfFork'));
$cases[] = new \Twig_Node(array(
'values' => new \Twig_Node($values),
'body' => $body
));
break;
}
case 'default':
{
$stream->expect(\Twig_Token::BLOCK_END_TYPE);
$default = $this->parser->subparse(array($this, 'decideIfEnd'));
break;
}
case 'endswitch':
{
$end = true;
break;
}
default:
{
throw new \Twig_Error_Syntax(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(\Twig_Token::BLOCK_END_TYPE);
return new TwigNodeSwitch($name, new \Twig_Node($cases), $default, $lineno, $this->getTag());
}
/**
* Decide if current token marks switch logic.
*
* @param \Twig_Token $token
* @return bool
*/
public function decideIfFork(\Twig_Token $token)
{
return $token->test(array('case', 'default', 'endswitch'));
}
/**
* Decide if current token marks end of swtich block.
*
* @param \Twig_Token $token
* @return bool
*/
public function decideIfEnd(\Twig_Token $token)
{
return $token->test(array('endswitch'));
}
/**
* {@inheritdoc}
*/
public function getTag()
{
return 'switch';
}
}
@@ -0,0 +1,68 @@
<?php
/**
* @package Grav.Common.Twig
*
* @copyright Copyright (C) 2015 - 2018 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;
/**
* Handles try/catch in template file.
*
* <pre>
* {% try %}
* <li>{{ user.get('name') }}</li>
* {% catch %}
* {{ e.message }}
* {% endcatch %}
* </pre>
*/
class TwigTokenParserTryCatch extends \Twig_TokenParser
{
/**
* Parses a token and returns a node.
*
* @param \Twig_Token $token A Twig_Token instance
*
* @return \Twig_NodeInterface A Twig_NodeInterface instance
*/
public function parse(\Twig_Token $token)
{
$lineno = $token->getLine();
$stream = $this->parser->getStream();
$stream->expect(\Twig_Token::BLOCK_END_TYPE);
$try = $this->parser->subparse([$this, 'decideCatch']);
$stream->next();
$stream->expect(\Twig_Token::BLOCK_END_TYPE);
$catch = $this->parser->subparse([$this, 'decideEnd']);
$stream->next();
$stream->expect(\Twig_Token::BLOCK_END_TYPE);
return new TwigNodeTryCatch($try, $catch, $lineno, $this->getTag());
}
public function decideCatch(\Twig_Token $token)
{
return $token->test(array('catch'));
}
public function decideEnd(\Twig_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';
}
}
+415
View File
@@ -0,0 +1,415 @@
<?php
/**
* @package Grav.Common.Twig
*
* @copyright Copyright (C) 2015 - 2018 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\Page;
use Grav\Common\Page\Pages;
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
use RocketTheme\Toolbox\Event\Event;
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 (!isset($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
$this->twig_paths = array_merge($this->twig_paths, $locator->findResources('system://templates'));
$this->loader = new \Twig_Loader_Filesystem($this->twig_paths);
$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 (!empty($this->autoescape)) {
$params['autoescape'] = $this->autoescape;
}
$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_Function_Function($name);
}
return new \Twig_Function_Function(function () {
});
});
}
if ($config->get('system.twig.undefined_filters')) {
$this->twig->registerUndefinedFilterCallback(function ($name) {
if (function_exists($name)) {
return new \Twig_Filter_Function($name);
}
return new \Twig_Filter_Function(function () {
});
});
}
$this->grav->fireEvent('onTwigInitialized');
// set default date format if set in config
if ($config->get('system.pages.dateformat.long')) {
$this->twig->getExtension('core')->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->grav->fireEvent('onTwigExtensions');
/** @var Pages $pages */
$pages = $this->grav['pages'];
// Set some standard variables for twig
$this->twig_vars = $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 \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 Page $item The page item to render
* @param string $content Optional content override
*
* @return string The rendered output
* @throws \Twig_Error_Loader
*/
public function processPage(Page $item, $content = null)
{
$content = $content !== null ? $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);
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 ? $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 $template_path
* @param null $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 $template_path
* @param null $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)
{
if (isset($this->template)) {
return $this->template;
} else {
return $template;
}
}
/**
* Overrides the autoescape setting
*
* @param boolean $state
*/
public function setAutoescape($state) {
$this->autoescape = (bool) $state;
}
}
@@ -0,0 +1,14 @@
<?php
/**
* @package Grav.Common.Twig
*
* @copyright Copyright (C) 2015 - 2018 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,46 @@
<?php
/**
* @package Grav.Common.Twig
*
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\Twig;
use Grav\Common\Grav;
trait WriteCacheFileTrait
{
protected static $umask;
/**
* This exists so template cache files use the same
* group between apache and cli
*
* @param $file
* @param $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) {
if (!is_dir(dirname($file))) {
$old = umask(0002);
mkdir(dirname($file), 0777, true);
umask($old);
}
parent::writeCacheFile($file, $content);
chmod($file, 0775);
} else {
parent::writeCacheFile($file, $content);
}
}
}