diff --git a/modules/cms/ServiceProvider.php b/modules/cms/ServiceProvider.php
index 59950e258c..d2fa63a562 100644
--- a/modules/cms/ServiceProvider.php
+++ b/modules/cms/ServiceProvider.php
@@ -5,6 +5,7 @@
use Lang;
use File;
use Event;
+use Config;
use Backend;
use BackendMenu;
use BackendAuth;
@@ -17,7 +18,12 @@
use System\Classes\CombineAssets;
use Cms\Classes\Theme as CmsTheme;
use Backend\Classes\WidgetManager;
+use System\Classes\MarkupManager;
use System\Classes\SettingsManager;
+use Twig\Cache\FilesystemCache as TwigCacheFilesystem;
+use Cms\Twig\Loader as CmsTwigLoader;
+use Cms\Twig\DebugExtension;
+use Cms\Twig\Extension as CmsTwigExtension;
use Winter\Storm\Support\ModuleServiceProvider;
@@ -31,6 +37,7 @@ class ServiceProvider extends ModuleServiceProvider
public function register()
{
$this->registerConsole();
+ $this->registerTwigParser();
$this->registerAssetBundles();
$this->registerComponents();
$this->registerThemeLogging();
@@ -78,6 +85,43 @@ protected function registerConsole()
$this->registerConsoleCommand('theme.sync', \Cms\Console\ThemeSync::class);
}
+ /*
+ * Register Twig Environments and other Twig modifications provided by the module
+ */
+ protected function registerTwigParser()
+ {
+ // Register CMS Twig environment
+ App::singleton('twig.environment.cms', function ($app) {
+ // Load Twig options
+ $useCache = !Config::get('cms.twigNoCache');
+ $isDebugMode = Config::get('app.debug', false);
+ $strictVariables = Config::get('cms.enableTwigStrictVariables', false);
+ $strictVariables = $strictVariables ?? $isDebugMode;
+ $forceBytecode = Config::get('cms.forceBytecodeInvalidation', false);
+
+ $options = [
+ 'auto_reload' => true,
+ 'debug' => $isDebugMode,
+ 'strict_variables' => $strictVariables,
+ ];
+
+ if ($useCache) {
+ $options['cache'] = new TwigCacheFilesystem(
+ storage_path().'/cms/twig',
+ $forceBytecode ? TwigCacheFilesystem::FORCE_BYTECODE_INVALIDATION : 0
+ );
+ }
+
+ $twig = MarkupManager::makeBaseTwigEnvironment(new CmsTwigLoader, $options);
+ $twig->addExtension(new CmsTwigExtension);
+ if ($isDebugMode) {
+ $twig->addExtension(new DebugExtension);
+ }
+
+ return $twig;
+ });
+ }
+
/**
* Register asset bundles
*/
diff --git a/modules/cms/classes/CmsCompoundObject.php b/modules/cms/classes/CmsCompoundObject.php
index ce7069ae30..acfc6fa684 100644
--- a/modules/cms/classes/CmsCompoundObject.php
+++ b/modules/cms/classes/CmsCompoundObject.php
@@ -4,14 +4,10 @@
use Lang;
use Cache;
use Config;
-use Cms\Twig\Loader as TwigLoader;
-use Cms\Twig\Extension as CmsTwigExtension;
use Cms\Components\ViewBag;
use Cms\Helpers\Cms as CmsHelpers;
-use System\Twig\Extension as SystemTwigExtension;
use Winter\Storm\Halcyon\Processors\SectionParser;
use Twig\Source as TwigSource;
-use Twig\Environment as TwigEnvironment;
use ApplicationException;
/**
@@ -107,7 +103,7 @@ public function beforeSave()
$this->code = $this->getOriginal('code');
}
}
-
+
$this->checkSafeMode();
}
@@ -414,11 +410,7 @@ public function getTwigContent()
*/
public function getTwigNodeTree($markup = false)
{
- $loader = new TwigLoader();
- $twig = new TwigEnvironment($loader, []);
- $twig->addExtension(new CmsTwigExtension());
- $twig->addExtension(new SystemTwigExtension);
-
+ $twig = App::make('twig.environment.cms');
$stream = $twig->tokenize(new TwigSource($markup === false ? $this->markup : $markup, 'getTwigNodeTree'));
return $twig->parse($stream);
}
diff --git a/modules/cms/classes/Controller.php b/modules/cms/classes/Controller.php
index f982b65024..b4b0bef877 100644
--- a/modules/cms/classes/Controller.php
+++ b/modules/cms/classes/Controller.php
@@ -14,17 +14,10 @@
use SystemException;
use BackendAuth;
use Twig\Environment as TwigEnvironment;
-use Twig\Cache\FilesystemCache as TwigCacheFilesystem;
-use Twig\Extension\SandboxExtension;
-use Cms\Twig\Loader as TwigLoader;
-use Cms\Twig\DebugExtension;
-use Cms\Twig\Extension as CmsTwigExtension;
use Cms\Models\MaintenanceSetting;
use System\Models\RequestLog;
use System\Helpers\View as ViewHelper;
use System\Classes\CombineAssets;
-use System\Twig\Extension as SystemTwigExtension;
-use System\Twig\SecurityPolicy;
use Winter\Storm\Exception\AjaxException;
use Winter\Storm\Exception\ValidationException;
use Winter\Storm\Parse\Bracket as TextParser;
@@ -54,11 +47,6 @@ class Controller
*/
protected $router;
- /**
- * @var \Cms\Twig\Loader A reference to the Twig template loader.
- */
- protected $loader;
-
/**
* @var \Cms\Classes\Page A reference to the CMS page template being processed.
*/
@@ -429,8 +417,8 @@ public function runPage($page, $useAjax = true)
* Render the page
*/
CmsException::mask($this->page, 400);
- $this->loader->setObject($this->page);
- $template = $this->twig->loadTemplate($this->page->getFilePath());
+ $this->getLoader()->setObject($this->page);
+ $template = $this->getTwig()->load($this->page->getFilePath());
$this->pageContents = $template->render($this->vars);
CmsException::unmask();
}
@@ -439,8 +427,8 @@ public function runPage($page, $useAjax = true)
* Render the layout
*/
CmsException::mask($this->layout, 400);
- $this->loader->setObject($this->layout);
- $template = $this->twig->loadTemplate($this->layout->getFilePath());
+ $this->getLoader()->setObject($this->layout);
+ $template = $this->getTwig()->load($this->layout->getFilePath());
$result = $template->render($this->vars);
CmsException::unmask();
@@ -595,35 +583,7 @@ protected function postProcessResult($page, $url, $content)
*/
protected function initTwigEnvironment()
{
- $this->loader = new TwigLoader;
-
- $useCache = !Config::get('cms.twigNoCache');
- $isDebugMode = Config::get('app.debug', false);
- $strictVariables = Config::get('cms.enableTwigStrictVariables', false);
- $strictVariables = $strictVariables ?? $isDebugMode;
- $forceBytecode = Config::get('cms.forceBytecodeInvalidation', false);
-
- $options = [
- 'auto_reload' => true,
- 'debug' => $isDebugMode,
- 'strict_variables' => $strictVariables,
- ];
-
- if ($useCache) {
- $options['cache'] = new TwigCacheFilesystem(
- storage_path().'/cms/twig',
- $forceBytecode ? TwigCacheFilesystem::FORCE_BYTECODE_INVALIDATION : 0
- );
- }
-
- $this->twig = new TwigEnvironment($this->loader, $options);
- $this->twig->addExtension(new CmsTwigExtension($this));
- $this->twig->addExtension(new SystemTwigExtension);
- $this->twig->addExtension(new SandboxExtension(new SecurityPolicy, true));
-
- if ($isDebugMode) {
- $this->twig->addExtension(new DebugExtension($this));
- }
+ $this->twig = App::make('twig.environment.cms');
}
/**
@@ -1096,8 +1056,8 @@ public function renderPartial($name, $parameters = [], $throwException = true)
* Render the partial
*/
CmsException::mask($partial, 400);
- $this->loader->setObject($partial);
- $template = $this->twig->loadTemplate($partial->getFilePath());
+ $this->getLoader()->setObject($partial);
+ $template = $this->getTwig()->load($partial->getFilePath());
$partialContent = $template->render(array_merge($this->vars, $parameters));
CmsException::unmask();
@@ -1274,7 +1234,7 @@ public function getTwig()
*/
public function getLoader()
{
- return $this->loader;
+ return $this->getTwig()->getLoader();
}
/**
diff --git a/modules/cms/twig/ComponentNode.php b/modules/cms/twig/ComponentNode.php
index 4787fe9b2d..e91e2bbca4 100644
--- a/modules/cms/twig/ComponentNode.php
+++ b/modules/cms/twig/ComponentNode.php
@@ -34,7 +34,7 @@ public function compile(TwigCompiler $compiler)
}
$compiler
- ->write("echo \$this->env->getExtension('Cms\Twig\Extension')->componentFunction(")
+ ->write("echo \$this->env->getExtension('Cms\Twig\Extension')->componentFunction(\$context,")
->subcompile($this->getNode('nodes')->getNode(0))
->write(", \$context['__cms_component_params']")
->write(");\n")
diff --git a/modules/cms/twig/ContentNode.php b/modules/cms/twig/ContentNode.php
index f1f9df8602..a6880951eb 100644
--- a/modules/cms/twig/ContentNode.php
+++ b/modules/cms/twig/ContentNode.php
@@ -36,7 +36,7 @@ public function compile(TwigCompiler $compiler)
}
$compiler
- ->write("echo \$this->env->getExtension('Cms\Twig\Extension')->contentFunction(")
+ ->write("echo \$this->env->getExtension('Cms\Twig\Extension')->contentFunction(\$context,")
->subcompile($this->getNode('nodes')->getNode(0))
->write(", \$context['__cms_content_params']")
->write(");\n")
diff --git a/modules/cms/twig/DebugExtension.php b/modules/cms/twig/DebugExtension.php
index 67b03730f9..11aed1bd0d 100644
--- a/modules/cms/twig/DebugExtension.php
+++ b/modules/cms/twig/DebugExtension.php
@@ -4,7 +4,6 @@
use Twig\Extension\AbstractExtension as TwigExtension;
use Twig\Environment as TwigEnvironment;
use Twig\TwigFunction as TwigSimpleFunction;
-use Cms\Classes\Controller;
use Cms\Classes\ComponentBase;
use Illuminate\Pagination\Paginator;
use Illuminate\Support\Collection;
@@ -19,11 +18,6 @@ class DebugExtension extends TwigExtension
const OBJECT_CAPTION = 'Object variables';
const COMPONENT_CAPTION = 'Component variables';
- /**
- * @var \Cms\Classes\Controller A reference to the CMS controller.
- */
- protected $controller;
-
/**
* @var integer Helper for rendering table row styles.
*/
@@ -52,15 +46,6 @@ class DebugExtension extends TwigExtension
'offsetUnset'
];
- /**
- * Creates the extension instance.
- * @param \Cms\Classes\Controller $controller The CMS controller object.
- */
- public function __construct(Controller $controller)
- {
- $this->controller = $controller;
- }
-
/**
* Returns a list of global functions to add to the existing list.
* @return array An array of global functions
diff --git a/modules/cms/twig/Extension.php b/modules/cms/twig/Extension.php
index 867087888c..4d527d4f74 100644
--- a/modules/cms/twig/Extension.php
+++ b/modules/cms/twig/Extension.php
@@ -5,7 +5,6 @@
use Twig\Extension\AbstractExtension as TwigExtension;
use Twig\TwigFilter as TwigSimpleFilter;
use Twig\TwigFunction as TwigSimpleFunction;
-use Cms\Classes\Controller;
/**
* The CMS Twig extension class implements the basic CMS Twig functions and filters.
@@ -16,54 +15,44 @@
class Extension extends TwigExtension
{
/**
- * @var \Cms\Classes\Controller A reference to the CMS controller.
+ * Returns an array of functions to add to the existing list.
*/
- protected $controller;
-
- /**
- * Creates the extension instance.
- * @param \Cms\Classes\Controller $controller The CMS controller object.
- */
- public function __construct(Controller $controller = null)
+ public function getFunctions(): array
{
- $this->controller = $controller;
- }
+ $options = [
+ 'is_safe' => ['html'],
+ 'needs_context' => true,
+ ];
- /**
- * Returns a list of functions to add to the existing list.
- *
- * @return array An array of functions
- */
- public function getFunctions()
- {
return [
- new TwigSimpleFunction('page', [$this, 'pageFunction'], ['is_safe' => ['html']]),
- new TwigSimpleFunction('partial', [$this, 'partialFunction'], ['is_safe' => ['html']]),
- new TwigSimpleFunction('content', [$this, 'contentFunction'], ['is_safe' => ['html']]),
- new TwigSimpleFunction('component', [$this, 'componentFunction'], ['is_safe' => ['html']]),
+ new TwigSimpleFunction('page', [$this, 'pageFunction'], $options),
+ new TwigSimpleFunction('partial', [$this, 'partialFunction'], $options),
+ new TwigSimpleFunction('content', [$this, 'contentFunction'], $options),
+ new TwigSimpleFunction('component', [$this, 'componentFunction'], $options),
new TwigSimpleFunction('placeholder', [$this, 'placeholderFunction'], ['is_safe' => ['html']]),
];
}
/**
- * Returns a list of filters this extensions provides.
- *
- * @return array An array of filters
+ * Returns an array of filters this extension provides.
*/
- public function getFilters()
+ public function getFilters(): array
{
+ $options = [
+ 'is_safe' => ['html'],
+ 'needs_context' => true,
+ ];
+
return [
- new TwigSimpleFilter('page', [$this, 'pageFilter'], ['is_safe' => ['html']]),
- new TwigSimpleFilter('theme', [$this, 'themeFilter'], ['is_safe' => ['html']]),
+ new TwigSimpleFilter('page', [$this, 'pageFilter'], $options),
+ new TwigSimpleFilter('theme', [$this, 'themeFilter'], $options),
];
}
/**
- * Returns a list of token parsers this extensions provides.
- *
- * @return array An array of token parsers
+ * Returns an array of token parsers this extension provides.
*/
- public function getTokenParsers()
+ public function getTokenParsers(): array
{
return [
new PageTokenParser,
@@ -82,64 +71,49 @@ public function getTokenParsers()
}
/**
- * Renders a page.
- * This function should be used in the layout code to output the requested page.
- * @return string Returns the page contents.
+ * Renders a page; used in the layout code to output the requested page.
*/
- public function pageFunction()
+ public function pageFunction(array $context): string
{
- return $this->controller->renderPage();
+ return $context['this']['controller']->renderPage();
}
/**
- * Renders a partial.
- * @param string $name Specifies the partial name.
- * @param array $parameters A optional list of parameters to pass to the partial.
- * @param bool $throwException Throw an exception if the partial is not found.
- * @return string Returns the partial contents.
+ * Renders the requested partial with the provided parameters. Optionally throw an exception if the partial cannot be found
*/
- public function partialFunction($name, $parameters = [], $throwException = false)
+ public function partialFunction(array $context, string $name, array $parameters = [], bool $throwException = false): string
{
- return $this->controller->renderPartial($name, $parameters, $throwException);
+ return $context['this']['controller']->renderPartial($name, $parameters, $throwException);
}
/**
- * Renders a content file.
- * @param string $name Specifies the content block name.
- * @param array $parameters A optional list of parameters to pass to the content.
- * @return string Returns the file contents.
+ * Renders the requested content file.
*/
- public function contentFunction($name, $parameters = [])
+ public function contentFunction(array $context, string $name, array $parameters = []): string
{
- return $this->controller->renderContent($name, $parameters);
+ return $context['this']['controller']->renderContent($name, $parameters);
}
/**
- * Renders a component's default content.
- * @param string $name Specifies the component name.
- * @param array $parameters A optional list of parameters to pass to the component.
- * @return string Returns the component default contents.
+ * Renders a component's default partial.
*/
- public function componentFunction($name, $parameters = [])
+ public function componentFunction(array $context, string $name, array $parameters = []): string
{
- return $this->controller->renderComponent($name, $parameters);
+ return $context['this']['controller']->renderComponent($name, $parameters);
}
/**
- * Renders registered assets of a given type
- * @return string Returns the component default contents.
+ * Renders registered assets of a given type or all types if $type not provided
*/
- public function assetsFunction($type = null)
+ public function assetsFunction(array $context, string $type = null): ?string
{
- return $this->controller->makeAssets($type);
+ return $context['this']['controller']->makeAssets($type);
}
/**
- * Renders a placeholder content, without removing the block,
- * must be called before the placeholder tag itself
- * @return string Returns the placeholder contents.
+ * Renders placeholder content, without removing the block, must be called before the placeholder tag itself
*/
- public function placeholderFunction($name, $default = null)
+ public function placeholderFunction(string $name, string $default = null): string
{
if (($result = Block::get($name)) === null) {
return null;
@@ -150,45 +124,42 @@ public function placeholderFunction($name, $default = null)
}
/**
- * Looks up the URL for a supplied page and returns it relative to the website root.
+ * Returns the relative URL for the provided page
+ *
+ * @param array $context The Twig context for the call (relies on $context['this']['controller'] to exist)
* @param mixed $name Specifies the Cms Page file name.
* @param array $parameters Route parameters to consider in the URL.
- * @param bool $routePersistence By default the existing routing parameters will be included
- * when creating the URL, set to false to disable this feature.
- * @return string
+ * @param bool $routePersistence Set to false to exclude the existing routing parameters from the generated URL
*/
- public function pageFilter($name, $parameters = [], $routePersistence = true)
+ public function pageFilter(array $context, $name, array $parameters = [], $routePersistence = true): string
{
- return $this->controller->pageUrl($name, $parameters, $routePersistence);
+ return $context['this']['controller']->pageUrl($name, $parameters, $routePersistence);
}
/**
* Converts supplied URL to a theme URL relative to the website root. If the URL provided is an
* array then the files will be combined.
- * @param mixed $url Specifies the theme-relative URL
- * @return string
+ *
+ * @param array $context The Twig context for the call (relies on $context['this']['controller'] to exist)
+ * @param mixed $url Specifies the input to be turned into a URL (arrays will be passed to the AssetCombiner)
*/
- public function themeFilter($url)
+ public function themeFilter(array $context, $url): string
{
- return $this->controller->themeUrl($url);
+ return $context['this']['controller']->themeUrl($url);
}
/**
* Opens a layout block.
- * @param string $name Specifies the block name
*/
- public function startBlock($name)
+ public function startBlock(string $name): void
{
Block::startBlock($name);
}
/**
- * Returns a layout block contents and removes the block.
- * @param string $name Specifies the block name
- * @param string $default The default placeholder contents.
- * @return mixed Returns the block contents string or null of the block doesn't exist
+ * Returns a layout block contents (or null if it doesn't exist) and removes the block.
*/
- public function displayBlock($name, $default = null)
+ public function displayBlock(string $name, string $default = null): ?string
{
if (($result = Block::placeholder($name)) === null) {
return $default;
@@ -218,7 +189,7 @@ public function displayBlock($name, $default = null)
/**
* Closes a layout block.
*/
- public function endBlock($append = true)
+ public function endBlock($append = true): void
{
Block::endBlock($append);
}
diff --git a/modules/cms/twig/Loader.php b/modules/cms/twig/Loader.php
index 87f13b9fc6..ca20215d26 100644
--- a/modules/cms/twig/Loader.php
+++ b/modules/cms/twig/Loader.php
@@ -39,11 +39,8 @@ public function setObject(CmsObject $obj)
/**
* Returns the Twig content string.
* This step is cached internally by Twig.
- *
- * @param string $name The template name
- * @return TwigSource
*/
- public function getSourceContext($name)
+ public function getSourceContext(string $name): TwigSource
{
if (!$this->validateCmsObject($name)) {
return parent::getSourceContext($name);
@@ -70,11 +67,8 @@ public function getSourceContext($name)
/**
* Returns the Twig cache key.
- *
- * @param string $name The template name
- * @return string
*/
- public function getCacheKey($name)
+ public function getCacheKey(string $name): string
{
if (!$this->validateCmsObject($name)) {
return parent::getCacheKey($name);
@@ -90,7 +84,7 @@ public function getCacheKey($name)
* @param mixed $time The time to check against the template
* @return bool
*/
- public function isFresh($name, $time)
+ public function isFresh(string $name, int $time): bool
{
if (!$this->validateCmsObject($name)) {
return parent::isFresh($name, $time);
@@ -101,11 +95,8 @@ public function isFresh($name, $time)
/**
* Returns the file name of the loaded template.
- *
- * @param string $name The template name
- * @return string
*/
- public function getFilename($name)
+ public function getFilename(string $name): string
{
if (!$this->validateCmsObject($name)) {
return parent::getFilename($name);
@@ -116,11 +107,8 @@ public function getFilename($name)
/**
* Checks that the template exists.
- *
- * @param string $name The template name
- * @return bool
*/
- public function exists($name)
+ public function exists(string $name): bool
{
if (!$this->validateCmsObject($name)) {
return parent::exists($name);
@@ -132,11 +120,8 @@ public function exists($name)
/**
* Internal method that checks if the template name matches
* the loaded object, with fallback support to partials.
- *
- * @param string $name The template name to validate
- * @return bool
*/
- protected function validateCmsObject($name)
+ protected function validateCmsObject(string $name): bool
{
if ($this->obj && $name === $this->obj->getFilePath()) {
return true;
diff --git a/modules/cms/twig/PageNode.php b/modules/cms/twig/PageNode.php
index f4481c9185..3dfb7173b5 100644
--- a/modules/cms/twig/PageNode.php
+++ b/modules/cms/twig/PageNode.php
@@ -25,7 +25,7 @@ public function compile(TwigCompiler $compiler)
{
$compiler
->addDebugInfo($this)
- ->write("echo \$this->env->getExtension('Cms\Twig\Extension')->pageFunction();\n")
+ ->write("echo \$this->env->getExtension('Cms\Twig\Extension')->pageFunction(\$context);\n")
;
}
}
diff --git a/modules/cms/twig/PartialNode.php b/modules/cms/twig/PartialNode.php
index 34578dec7f..e1f1de0950 100644
--- a/modules/cms/twig/PartialNode.php
+++ b/modules/cms/twig/PartialNode.php
@@ -34,7 +34,7 @@ public function compile(TwigCompiler $compiler)
}
$compiler
- ->write("echo \$this->env->getExtension('Cms\Twig\Extension')->partialFunction(")
+ ->write("echo \$this->env->getExtension('Cms\Twig\Extension')->partialFunction(\$context,")
->subcompile($this->getNode('nodes')->getNode(0))
->write(", \$context['__cms_partial_params']")
->write(", true")
diff --git a/modules/cms/twig/ScriptsNode.php b/modules/cms/twig/ScriptsNode.php
index 4b2b9cdf50..d659440f39 100644
--- a/modules/cms/twig/ScriptsNode.php
+++ b/modules/cms/twig/ScriptsNode.php
@@ -25,7 +25,7 @@ public function compile(TwigCompiler $compiler)
{
$compiler
->addDebugInfo($this)
- ->write("echo \$this->env->getExtension('Cms\Twig\Extension')->assetsFunction('js');\n")
+ ->write("echo \$this->env->getExtension('Cms\Twig\Extension')->assetsFunction(\$context, 'js');\n")
->write("echo \$this->env->getExtension('Cms\Twig\Extension')->displayBlock('scripts');\n")
;
}
diff --git a/modules/cms/twig/StylesNode.php b/modules/cms/twig/StylesNode.php
index 35a442c2f5..7ed22d47b3 100644
--- a/modules/cms/twig/StylesNode.php
+++ b/modules/cms/twig/StylesNode.php
@@ -25,7 +25,7 @@ public function compile(TwigCompiler $compiler)
{
$compiler
->addDebugInfo($this)
- ->write("echo \$this->env->getExtension('Cms\Twig\Extension')->assetsFunction('css');\n")
+ ->write("echo \$this->env->getExtension('Cms\Twig\Extension')->assetsFunction(\$context, 'css');\n")
->write("echo \$this->env->getExtension('Cms\Twig\Extension')->displayBlock('styles');\n")
;
}
diff --git a/modules/system/ServiceProvider.php b/modules/system/ServiceProvider.php
index 3899141336..28aa72b1e1 100644
--- a/modules/system/ServiceProvider.php
+++ b/modules/system/ServiceProvider.php
@@ -12,8 +12,6 @@
use BackendAuth;
use SystemException;
use Backend\Models\UserRole;
-use Twig\Extension\SandboxExtension;
-use Twig\Environment as TwigEnvironment;
use System\Classes\MailManager;
use System\Classes\ErrorHandler;
use System\Classes\MarkupManager;
@@ -21,9 +19,6 @@
use System\Classes\SettingsManager;
use System\Classes\UpdateManager;
use System\Twig\Engine as TwigEngine;
-use System\Twig\Loader as TwigLoader;
-use System\Twig\Extension as TwigExtension;
-use System\Twig\SecurityPolicy as TwigSecurityPolicy;
use System\Models\EventLog;
use System\Models\MailSetting;
use System\Classes\CombineAssets;
@@ -301,23 +296,22 @@ protected function registerLogging()
}
/*
- * Register text twig parser
+ * Register Twig Environments and other Twig modifications provided by the module
*/
protected function registerTwigParser()
{
- /*
- * Register system Twig environment
- */
+ // Register System Twig environment
App::singleton('twig.environment', function ($app) {
- $twig = new TwigEnvironment(new TwigLoader, ['auto_reload' => true]);
- $twig->addExtension(new TwigExtension);
- $twig->addExtension(new SandboxExtension(new TwigSecurityPolicy, true));
- return $twig;
+ return MarkupManager::makeBaseTwigEnvironment();
});
- /*
- * Register .htm extension for Twig views
- */
+ // Register Mailer Twig environment
+ App::singleton('twig.environment.mailer', function ($app) {
+ $twig = MarkupManager::makeBaseTwigEnvironment();
+ $twig->addTokenParser(new \System\Twig\MailPartialTokenParser);
+ });
+
+ // Register .htm extension for Twig views
App::make('view')->addExtension('htm', 'twig', function () {
return new TwigEngine(App::make('twig.environment'));
});
diff --git a/modules/system/classes/MailManager.php b/modules/system/classes/MailManager.php
index a166e6525e..af1877536c 100644
--- a/modules/system/classes/MailManager.php
+++ b/modules/system/classes/MailManager.php
@@ -1,12 +1,10 @@
startTwig();
-
/*
* Inject global view variables
*/
@@ -133,7 +121,7 @@ protected function addContentToMailerInternal($message, $template, $data, $plain
$symfonyMessage = $message->getSymfonyMessage();
if (empty($symfonyMessage->getSubject())) {
- $message->subject(Twig::parse($template->subject, $data));
+ $message->subject($this->renderTwig($template->subject, $data));
}
$data += [
@@ -155,11 +143,6 @@ protected function addContentToMailerInternal($message, $template, $data, $plain
$text = $this->renderTextTemplate($template, $data);
$message->addPart($text, 'text/plain');
-
- /*
- * End twig transaction
- */
- $this->stopTwig();
}
//
@@ -202,7 +185,7 @@ public function renderTemplate($template, $data = [])
$html = $this->renderTwig($template->layout->content_html, [
'content' => $html,
'css' => $template->layout->content_css,
- 'brandCss' => $css
+ 'brandCss' => $css,
] + (array) $data);
$css .= PHP_EOL . $template->layout->content_css;
@@ -276,56 +259,13 @@ public function renderPartial($code, array $params = [])
}
/**
- * Internal helper for rendering Twig
- */
- protected function renderTwig($content, $data = [])
- {
- if ($this->isTwigStarted) {
- return Twig::parse($content, $data);
- }
-
- $this->startTwig();
-
- $result = Twig::parse($content, $data);
-
- $this->stopTwig();
-
- return $result;
- }
-
- /**
- * Temporarily registers mail based token parsers with Twig.
- * @return void
- */
- protected function startTwig()
- {
- if ($this->isTwigStarted) {
- return;
- }
-
- $this->isTwigStarted = true;
-
- $markupManager = MarkupManager::instance();
- $markupManager->beginTransaction();
- $markupManager->registerTokenParsers([
- new MailPartialTokenParser
- ]);
- }
-
- /**
- * Indicates that we are finished with Twig.
- * @return void
+ * Internal helper for rendering Twig using the mailer Twig environment
*/
- protected function stopTwig()
+ protected function renderTwig(string $content, array $data = []): string
{
- if (!$this->isTwigStarted) {
- return;
- }
-
- $markupManager = MarkupManager::instance();
- $markupManager->endTransaction();
-
- $this->isTwigStarted = false;
+ return App::make('twig.environment.mailer')
+ ->createTemplate($contents)
+ ->render($data);
}
//
diff --git a/modules/system/classes/MarkupManager.php b/modules/system/classes/MarkupManager.php
index 01f28c689e..95831351b4 100644
--- a/modules/system/classes/MarkupManager.php
+++ b/modules/system/classes/MarkupManager.php
@@ -4,6 +4,13 @@
use Twig\TokenParser\AbstractTokenParser as TwigTokenParser;
use Twig\TwigFilter as TwigSimpleFilter;
use Twig\TwigFunction as TwigSimpleFunction;
+use Twig\Environment as TwigEnvironment;
+use Twig\Extension\SandboxExtension;
+use Twig\Loader\LoaderInterface;
+use System\Twig\Loader as SystemTwigLoader;
+use System\Twig\Extension as SystemTwigExtension;
+use System\Twig\SecurityPolicy as TwigSecurityPolicy;
+
use ApplicationException;
/**
@@ -35,16 +42,6 @@ class MarkupManager
*/
protected $pluginManager;
- /**
- * @var array Transaction based extension items
- */
- protected $transactionItems;
-
- /**
- * @var bool Manager is in transaction mode
- */
- protected $transactionMode = false;
-
/**
* Initialize this singleton.
*/
@@ -53,18 +50,36 @@ protected function init()
$this->pluginManager = PluginManager::instance();
}
- protected function loadExtensions()
+ /**
+ * Make an instance of the base TwigEnvironment to extend further
+ */
+ public static function makeBaseTwigEnvironment(LoaderInterface $loader = null, array $options = []): TwigEnvironment
{
- /*
- * Load module items
- */
+ if (!$loader) {
+ $loader = new SystemTwigLoader();
+ }
+
+ $options = array_merge([
+ 'auto_reload' => true,
+ ], $options);
+
+ $twig = new TwigEnvironment($loader, $options);
+ $twig->addExtension(new SystemTwigExtension);
+ $twig->addExtension(new SandboxExtension(new TwigSecurityPolicy, true));
+ return $twig;
+ }
+
+ /**
+ * Loads all of the registered Twig extensions
+ */
+ protected function loadExtensions(): void
+ {
+ // Load Module extensions
foreach ($this->callbacks as $callback) {
$callback($this);
}
- /*
- * Load plugin items
- */
+ // Load Plugin extensions
$plugins = $this->pluginManager->getPlugins();
foreach ($plugins as $id => $plugin) {
@@ -95,69 +110,60 @@ protected function loadExtensions()
* $manager->registerTokenParsers([...]);
* });
*
- * @param callable $callback A callable function.
*/
- public function registerCallback(callable $callback)
+ public function registerCallback(callable $callback): void
{
$this->callbacks[] = $callback;
}
/**
- * Registers the CMS Twig extension items.
- * The argument is an array of the extension definitions. The array keys represent the
- * function/filter name, specific for the plugin/module. Each element in the
- * array should be an associative array.
- * @param string $type The extension type: filters, functions, tokens
- * @param array $definitions An array of the extension definitions.
+ * Registers the Twig extension items.
+ * $type must be one of self::EXTENSION_TOKEN_PARSER, self::EXTENSION_FILTER, or self::EXTENSION_FUNCTION
+ * $definitions is of the format of [$extensionName => $associativeExtensionOptions]
*/
- public function registerExtensions($type, array $definitions)
+ public function registerExtensions(string $type, array $definitions): void
{
- $items = $this->transactionMode ? 'transactionItems' : 'items';
-
- if ($this->$items === null) {
- $this->$items = [];
+ if ($this->items === null) {
+ $this->items = [];
}
- if (!array_key_exists($type, $this->$items)) {
- $this->$items[$type] = [];
+ if (!array_key_exists($type, $this->items)) {
+ $this->items[$type] = [];
}
foreach ($definitions as $name => $definition) {
switch ($type) {
case self::EXTENSION_TOKEN_PARSER:
- $this->$items[$type][] = $definition;
+ $this->items[$type][] = $definition;
break;
case self::EXTENSION_FILTER:
case self::EXTENSION_FUNCTION:
- $this->$items[$type][$name] = $definition;
+ $this->items[$type][$name] = $definition;
break;
}
}
}
/**
- * Registers a CMS Twig Filter
- * @param array $definitions An array of the extension definitions.
+ * Registers a Twig Filter
*/
- public function registerFilters(array $definitions)
+ public function registerFilters(array $definitions): void
{
$this->registerExtensions(self::EXTENSION_FILTER, $definitions);
}
/**
- * Registers a CMS Twig Function
- * @param array $definitions An array of the extension definitions.
+ * Registers a Twig Function
*/
- public function registerFunctions(array $definitions)
+ public function registerFunctions(array $definitions): void
{
$this->registerExtensions(self::EXTENSION_FUNCTION, $definitions);
}
/**
- * Registers a CMS Twig Token Parser
- * @param array $definitions An array of the extension definitions.
+ * Registers a Twig Token Parser
*/
- public function registerTokenParsers(array $definitions)
+ public function registerTokenParsers(array $definitions): void
{
$this->registerExtensions(self::EXTENSION_TOKEN_PARSER, $definitions);
}
@@ -179,10 +185,6 @@ public function listExtensions($type)
$results = $this->items[$type];
}
- if ($this->transactionItems !== null && isset($this->transactionItems[$type])) {
- $results = array_merge($results, $this->transactionItems[$type]);
- }
-
return $results;
}
@@ -373,41 +375,4 @@ protected function isWildCallable($callable, $replaceWith = false)
return $isWild;
}
-
- //
- // Transactions
- //
-
- /**
- * Execute a single serving transaction, containing filters, functions,
- * and token parsers that are disposed of afterwards.
- * @param \Closure $callback
- * @return void
- */
- public function transaction(Closure $callback)
- {
- $this->beginTransaction();
- $callback($this);
- $this->endTransaction();
- }
-
- /**
- * Start a new transaction.
- * @return void
- */
- public function beginTransaction()
- {
- $this->transactionMode = true;
- }
-
- /**
- * Ends an active transaction.
- * @return void
- */
- public function endTransaction()
- {
- $this->transactionMode = false;
-
- $this->transactionItems = null;
- }
}
diff --git a/modules/system/traits/AssetMaker.php b/modules/system/traits/AssetMaker.php
index 5781441ed5..7668d67f0f 100644
--- a/modules/system/traits/AssetMaker.php
+++ b/modules/system/traits/AssetMaker.php
@@ -39,11 +39,10 @@ public function flushAssets()
}
/**
- * Outputs `` and `