diff --git a/modules/cms/classes/Controller.php b/modules/cms/classes/Controller.php index a89a1c957e..f2822675c4 100644 --- a/modules/cms/classes/Controller.php +++ b/modules/cms/classes/Controller.php @@ -306,15 +306,7 @@ public function runPage($page, $useAjax = true) /* * The 'this' variable is reserved for default variables. */ - $this->vars['this'] = [ - 'page' => $this->page, - 'layout' => $this->layout, - 'theme' => $this->theme, - 'param' => $this->router->getParameters(), - 'controller' => $this, - 'environment' => App::environment(), - 'session' => App::make('session'), - ]; + $this->vars['this'] = $this->getFrontendContext(); /* * Check for the presence of validation errors in the session. @@ -584,6 +576,7 @@ protected function postProcessResult($page, $url, $content) protected function initTwigEnvironment() { $this->twig = App::make('twig.environment.cms'); + $this->twig->getExtension('Cms\Twig\Extension')->setController($this); } /** @@ -1564,4 +1557,21 @@ protected function isSoftComponent($label) { return starts_with($label, '@'); } + + /** + * Returns the frontend context variables + * @return array + */ + public function getFrontendContext(): array + { + return [ + 'page' => $this->page, + 'layout' => $this->layout, + 'theme' => $this->theme, + 'param' => $this->router->getParameters(), + 'controller' => $this, + 'environment' => App::environment(), + 'session' => App::make('session'), + ]; + } } diff --git a/modules/cms/twig/Extension.php b/modules/cms/twig/Extension.php index 3d9f5c4414..38db54f6be 100644 --- a/modules/cms/twig/Extension.php +++ b/modules/cms/twig/Extension.php @@ -1,6 +1,7 @@ controller->getFrontendContext(); + } + + public function setController(Controller $controller) + { + $this->controller = $controller; + } } diff --git a/modules/cms/twig/MacroNode.php b/modules/cms/twig/MacroNode.php new file mode 100644 index 0000000000..80b2b6dfa1 --- /dev/null +++ b/modules/cms/twig/MacroNode.php @@ -0,0 +1,97 @@ +addDebugInfo($this) + ->write(sprintf('public function macro_%s(', $this->getAttribute('name'))) + ; + + $count = \count($this->getNode('arguments')); + $pos = 0; + foreach ($this->getNode('arguments') as $name => $default) { + $compiler + ->raw('$__'.$name.'__ = ') + ->subcompile($default) + ; + + if (++$pos < $count) { + $compiler->raw(', '); + } + } + + if ($count) { + $compiler->raw(', '); + } + + $compiler + ->raw('...$__varargs__') + ->raw(")\n") + ->write("{\n") + ->indent() + ->write("\$macros = \$this->macros;\n") + ->write("\$context = \$this->env->mergeGlobals(array_merge(\n") + ->indent() + ->write("['this' => \$this->env->getExtension('Cms\Twig\Extension')->getFrontendContext()],\n") + ->write("[\n") + ; + + foreach ($this->getNode('arguments') as $name => $default) { + $compiler + ->write('') + ->string($name) + ->raw(' => $__'.$name.'__') + ->raw(",\n") + ; + } + + $compiler + ->write('') + ->string(self::VARARGS_NAME) + ->raw(' => ') + ; + + $compiler + ->raw("\$__varargs__,\n") + ->outdent() + ->write("]));\n\n") + ->write("\$blocks = [];\n\n") + ; + if ($compiler->getEnvironment()->isDebug()) { + $compiler->write("ob_start();\n"); + } else { + $compiler->write("ob_start(function () { return ''; });\n"); + } + $compiler + ->write("try {\n") + ->indent() + ->subcompile($this->getNode('body')) + ->raw("\n") + ->write("return ('' === \$tmp = ob_get_contents()) ? '' : new Markup(\$tmp, \$this->env->getCharset());\n") + ->outdent() + ->write("} finally {\n") + ->indent() + ->write("ob_end_clean();\n") + ->outdent() + ->write("}\n") + ->outdent() + ->write("}\n\n") + ; + } +} diff --git a/modules/cms/twig/MacroTokenParser.php b/modules/cms/twig/MacroTokenParser.php new file mode 100644 index 0000000000..3a4f26fa0e --- /dev/null +++ b/modules/cms/twig/MacroTokenParser.php @@ -0,0 +1,55 @@ +getLine(); + $stream = $this->parser->getStream(); + $name = $stream->expect(/* Token::NAME_TYPE */ 5)->getValue(); + + $arguments = $this->parser->getExpressionParser()->parseArguments(true, true); + + $stream->expect(/* Token::BLOCK_END_TYPE */ 3); + $this->parser->pushLocalScope(); + $body = $this->parser->subparse([$this, 'decideBlockEnd'], true); + if ($token = $stream->nextIf(/* Token::NAME_TYPE */ 5)) { + $value = $token->getValue(); + + if ($value != $name) { + throw new SyntaxError(sprintf('Expected endmacro for macro "%s" (but "%s" given).', $name, $value), $stream->getCurrent()->getLine(), $stream->getSourceContext()); + } + } + $this->parser->popLocalScope(); + $stream->expect(/* Token::BLOCK_END_TYPE */ 3); + + $this->parser->setMacro($name, new MacroNode($name, new BodyNode([$body]), $arguments, $lineno, $this->getTag())); + + return new Node(); + } + + public function decideBlockEnd(Token $token): bool + { + return $token->test('endmacro'); + } + + public function getTag(): string + { + return 'macro'; + } +}