diff --git a/modules/backend/ServiceProvider.php b/modules/backend/ServiceProvider.php index 50874b5a0..29316b4b0 100644 --- a/modules/backend/ServiceProvider.php +++ b/modules/backend/ServiceProvider.php @@ -51,10 +51,10 @@ class ServiceProvider extends ModuleServiceProvider */ protected function registerMailer() { - MailManager::instance()->registerCallback(function ($template) { - $template->registerMailTemplates([ - 'backend::mail.invite' => 'Invitation for newly created administrators.', - 'backend::mail.restore' => 'Password reset instructions for backend-end administrators.', + MailManager::instance()->registerCallback(function ($manager) { + $manager->registerMailTemplates([ + 'backend::mail.invite', + 'backend::mail.restore', ]); }); } diff --git a/modules/backend/views/mail/invite.htm b/modules/backend/views/mail/invite.htm index 1c6c18b21..5abbe1403 100644 --- a/modules/backend/views/mail/invite.htm +++ b/modules/backend/views/mail/invite.htm @@ -1,5 +1,6 @@ subject = "Welcome to October CMS" layout = "system" +description = "Invite new admin to the site" == Hi {{ name }}, diff --git a/modules/backend/views/mail/restore.htm b/modules/backend/views/mail/restore.htm index c3f4c5ede..b0719c725 100644 --- a/modules/backend/views/mail/restore.htm +++ b/modules/backend/views/mail/restore.htm @@ -1,5 +1,6 @@ subject = "Password Reset" layout = "system" +description = "Reset an admin password" == Hello {{ name }}, diff --git a/modules/system/ServiceProvider.php b/modules/system/ServiceProvider.php index 336beba4e..1c5164f52 100644 --- a/modules/system/ServiceProvider.php +++ b/modules/system/ServiceProvider.php @@ -287,6 +287,21 @@ class ServiceProvider extends ModuleServiceProvider */ protected function registerMailer() { + /* + * Register system layouts + */ + MailManager::instance()->registerCallback(function ($manager) { + $manager->registerMailLayouts([ + 'default' => 'system::mail.default', + 'system' => 'system::mail.system', + ]); + + $manager->registerMailPartials([ + 'button' => 'system::mail.button', + 'table' => 'system::mail.table', + ]); + }); + /* * Override system mailer with mail settings */ diff --git a/modules/system/classes/MailManager.php b/modules/system/classes/MailManager.php index bd32de888..1801e04c9 100644 --- a/modules/system/classes/MailManager.php +++ b/modules/system/classes/MailManager.php @@ -2,9 +2,13 @@ use Twig; use Markdown; +use System\Models\MailPartial; use System\Models\MailTemplate; use System\Helpers\View as ViewHelper; use System\Classes\PluginManager; +use System\Classes\MarkupManager; +use System\Twig\MailPartialTokenParser; +use System\Twig\MailComponentTokenParser; /** * This class manages Mail sending functions @@ -31,6 +35,21 @@ class MailManager */ protected $registeredTemplates; + /** + * @var array List of registered partials in the system + */ + protected $registeredPartials; + + /** + * @var array List of registered layouts in the system + */ + protected $registeredLayouts; + + /** + * @var bool Internal marker for rendering mode + */ + protected $isHtmlRenderMode; + /** * This function hijacks the `addContent` method of the `October\Rain\Mail\Mailer` * class, using the `mailer.beforeAddContent` event. @@ -44,6 +63,16 @@ class MailManager $this->templateCache[$code] = $template = MailTemplate::findOrMakeTemplate($code); } + /* + * Start twig transaction + */ + $markupManager = MarkupManager::instance(); + $markupManager->beginTransaction(); + $markupManager->registerTokenParsers([ + new MailPartialTokenParser, + new MailComponentTokenParser + ]); + /* * Inject global view variables */ @@ -63,6 +92,31 @@ class MailManager /* * HTML contents */ + $html = $this->renderHtmlContents($template, $data); + + $message->setBody($html, 'text/html'); + + /* + * Text contents + */ + $text = $this->renderTextContents($template, $data); + + $message->addPart($text, 'text/plain'); + + /* + * End twig transaction + */ + $markupManager->endTransaction(); + } + + // + // Rendering + // + + public function renderHtmlContents($template, $data) + { + $this->isHtmlRenderMode = true; + $templateHtml = $template->content_html; $html = Twig::parse($templateHtml, $data); @@ -75,11 +129,13 @@ class MailManager ] + (array) $data); } - $message->setBody($html, 'text/html'); + return $html; + } + + public function renderTextContents($template, $data) + { + $this->isHtmlRenderMode = false; - /* - * Text contents - */ $templateText = $template->content_text; if (!strlen($template->content_text)) { @@ -93,7 +149,45 @@ class MailManager ] + (array) $data); } - $message->addPart($text, 'text/plain'); + return $text; + } + + public function renderPartial($code, $params) + { + if (!$partial = MailPartial::whereCode($code)->first()) { + return ''; + } + + if ($this->isHtmlRenderMode) { + return $this->renderHtmlPartial($partial, $params); + } + else { + return $this->renderTextPartial($partial, $params); + } + } + + public function renderHtmlPartial($partial, $params) + { + $content = $partial->content_html; + + if (!strlen(trim($content))) { + return ''; + } + + $params['body'] = Markdown::parse(array_get($params, 'body')); + + return Twig::parse($content, $params); + } + + public function renderTextPartial($partial, $params) + { + $content = $partial->content_text ?: $partial->content_html; + + if (!strlen(trim($content))) { + return ''; + } + + return Twig::parse($content, $params); } // @@ -134,6 +228,32 @@ class MailManager return $this->registeredTemplates; } + /** + * Returns a list of the registered partials. + * @return array + */ + public function listRegisteredPartials() + { + if ($this->registeredPartials === null) { + $this->loadRegisteredTemplates(); + } + + return $this->registeredPartials; + } + + /** + * Returns a list of the registered layouts. + * @return array + */ + public function listRegisteredLayouts() + { + if ($this->registeredLayouts === null) { + $this->loadRegisteredTemplates(); + } + + return $this->registeredLayouts; + } + /** * Registers a callback function that defines mail templates. * The callback function should register templates by calling the manager's @@ -160,6 +280,37 @@ class MailManager $this->registeredTemplates = []; } - $this->registeredTemplates = array_merge($this->registeredTemplates, $definitions); + // Prior sytax where (key) code => (value) description + if (!isset($definitions[0])) { + $definitions = array_keys($definitions); + } + + $definitions = array_combine($definitions, $definitions); + + $this->registeredTemplates = $definitions + $this->registeredTemplates; + } + + /** + * Registers mail views and manageable layouts. + */ + public function registerMailPartials(array $definitions) + { + if (!$this->registeredPartials) { + $this->registeredPartials = []; + } + + $this->registeredPartials = $definitions + $this->registeredPartials; + } + + /** + * Registers mail views and manageable layouts. + */ + public function registerMailLayouts(array $definitions) + { + if (!$this->registeredLayouts) { + $this->registeredLayouts = []; + } + + $this->registeredLayouts = $definitions + $this->registeredLayouts; } } diff --git a/modules/system/classes/MarkupManager.php b/modules/system/classes/MarkupManager.php index 331cfc94e..79fc8916b 100644 --- a/modules/system/classes/MarkupManager.php +++ b/modules/system/classes/MarkupManager.php @@ -27,7 +27,7 @@ class MarkupManager protected $callbacks = []; /** - * @var array Registered extension items + * @var array Globally registered extension items */ protected $items; @@ -36,6 +36,16 @@ class MarkupManager */ protected $pluginManager; + /** + * @var array Transaction based extension items + */ + protected $tranItems; + + /** + * @var bool Manager is in transaction mode + */ + protected $transactionMode = false; + /** * Initialize this singleton. */ @@ -71,7 +81,6 @@ class MarkupManager $this->registerExtensions($type, $definitions); } - } } @@ -105,23 +114,25 @@ class MarkupManager */ public function registerExtensions($type, array $definitions) { - if (is_null($this->items)) { - $this->items = []; + $items = $this->transactionMode ? 'tranItems' : 'items'; + + if (is_null($this->$items)) { + $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; } } @@ -161,15 +172,21 @@ class MarkupManager */ public function listExtensions($type) { + $results = []; + if ($this->items === null) { $this->loadExtensions(); } - if (!isset($this->items[$type]) || !is_array($this->items[$type])) { - return []; + if (isset($this->items[$type]) && is_array($this->items[$type])) { + $results = $this->items[$type]; } - return $this->items[$type]; + if ($this->tranItems !== null && isset($this->tranItems[$type])) { + $results = array_merge($results, $this->tranItems[$type]); + } + + return $results; } /** @@ -329,4 +346,41 @@ class MarkupManager 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->tranItems = null; + } } diff --git a/modules/system/controllers/MailPartials.php b/modules/system/controllers/MailPartials.php new file mode 100644 index 000000000..4e0606d52 --- /dev/null +++ b/modules/system/controllers/MailPartials.php @@ -0,0 +1,38 @@ + 'config_templates_list.yaml', 'layouts' => 'config_layouts_list.yaml']; + public $listConfig = [ + 'templates' => 'config_templates_list.yaml', + 'layouts' => 'config_layouts_list.yaml', + 'partials' => 'config_partials_list.yaml' + ]; + public $formConfig = 'config_form.yaml'; public function __construct() diff --git a/modules/system/controllers/maillayouts/config_form.yaml b/modules/system/controllers/maillayouts/config_form.yaml index 40c44090e..644f0bd89 100644 --- a/modules/system/controllers/maillayouts/config_form.yaml +++ b/modules/system/controllers/maillayouts/config_form.yaml @@ -13,4 +13,4 @@ create: update: redirect: system/mailtemplates - redirectClose: system/mailtemplates \ No newline at end of file + redirectClose: system/mailtemplates diff --git a/modules/system/controllers/mailpartials/config_form.yaml b/modules/system/controllers/mailpartials/config_form.yaml new file mode 100644 index 000000000..ddff13239 --- /dev/null +++ b/modules/system/controllers/mailpartials/config_form.yaml @@ -0,0 +1,16 @@ +# =================================== +# Form Behavior Config +# =================================== + +name: system::lang.mail_templates.partial +form: ~/modules/system/models/mailpartial/fields.yaml +modelClass: System\Models\MailPartial +defaultRedirect: system/mailtemplates + +create: + redirect: system/mailpartials/update/:id + redirectClose: system/mailtemplates + +update: + redirect: system/mailtemplates + redirectClose: system/mailtemplates diff --git a/modules/system/controllers/mailpartials/create.htm b/modules/system/controllers/mailpartials/create.htm new file mode 100644 index 000000000..ddceee22e --- /dev/null +++ b/modules/system/controllers/mailpartials/create.htm @@ -0,0 +1,48 @@ + +
= e(trans('system::lang.mail_templates.return')) ?>
+ + diff --git a/modules/system/controllers/mailpartials/update.htm b/modules/system/controllers/mailpartials/update.htm new file mode 100644 index 000000000..ef93ad422 --- /dev/null +++ b/modules/system/controllers/mailpartials/update.htm @@ -0,0 +1,69 @@ + += $formModel->code ?>
+= e(trans('system::lang.mail_templates.return')) ?>
+ + diff --git a/modules/system/controllers/mailtemplates/_list_partials_toolbar.htm b/modules/system/controllers/mailtemplates/_list_partials_toolbar.htm new file mode 100644 index 000000000..328b48a09 --- /dev/null +++ b/modules/system/controllers/mailtemplates/_list_partials_toolbar.htm @@ -0,0 +1,7 @@ + diff --git a/modules/system/controllers/mailtemplates/config_partials_list.yaml b/modules/system/controllers/mailtemplates/config_partials_list.yaml new file mode 100644 index 000000000..cd22dbe84 --- /dev/null +++ b/modules/system/controllers/mailtemplates/config_partials_list.yaml @@ -0,0 +1,16 @@ +# =================================== +# List Behavior Config +# =================================== + +title: system::lang.mail_partials.menu_label +list: ~/modules/system/models/mailpartial/columns.yaml +modelClass: System\Models\MailPartial +recordUrl: system/mailpartials/update/:id +noRecordsMessage: backend::lang.list.no_records +recordsPerPage: 20 +showSetup: true + +toolbar: + buttons: list_partials_toolbar + search: + prompt: backend::lang.list.search_prompt diff --git a/modules/system/controllers/mailtemplates/index.htm b/modules/system/controllers/mailtemplates/index.htm index dae323ad6..683d31f51 100644 --- a/modules/system/controllers/mailtemplates/index.htm +++ b/modules/system/controllers/mailtemplates/index.htm @@ -2,6 +2,7 @@This is an automatic message. Please do not reply to it.
- -'; - -$text = '{{ content|raw }} - - ---- -This is an automatic message. Please do not reply to it. -'; - - MailLayout::create([ - 'is_locked' => true, - 'name' => 'System', - 'code' => 'system', - 'content_html' => $html, - 'content_css' => $css, - 'content_text' => $text, - ]); + MailLayout::createLayouts(); } } diff --git a/modules/system/lang/en/lang.php b/modules/system/lang/en/lang.php index d9ced019b..205845f22 100644 --- a/modules/system/lang/en/lang.php +++ b/modules/system/lang/en/lang.php @@ -190,9 +190,13 @@ return [ 'menu_description' => 'Modify the mail templates that are sent to users and administrators, manage email layouts.', 'new_template' => 'New Template', 'new_layout' => 'New Layout', + 'new_partial' => 'New Partial', 'template' => 'Template', 'templates' => 'Templates', + 'partial' => 'Partial', + 'partials' => 'Partials', 'menu_layouts_label' => 'Mail Layouts', + 'menu_partials_label' => 'Mail Partials', 'layout' => 'Layout', 'layouts' => 'Layouts', 'no_layout' => '-- No layout --', diff --git a/modules/system/models/MailLayout.php b/modules/system/models/MailLayout.php index 602f5708e..be5fadb75 100644 --- a/modules/system/models/MailLayout.php +++ b/modules/system/models/MailLayout.php @@ -1,6 +1,10 @@ 'required|unique:system_mail_layouts', 'name' => 'required', @@ -47,4 +64,64 @@ class MailLayout extends Model return array_get(self::listCodes(), $code); } + /** + * Loops over each mail layout and ensures the system has a layout, + * if the layout does not exist, it will create one. + * @return void + */ + public static function createLayouts() + { + $dbLayouts = self::lists('code', 'code'); + + $definitions = MailManager::instance()->listRegisteredLayouts(); + foreach ($definitions as $code => $path) { + if (array_key_exists($code, $dbLayouts)) { + continue; + } + + self::createLayoutFromFile($code, $path); + } + } + + /** + * Creates a layout using the contents of a specified file. + * @param string $code New Layout code + * @param string $viewPath View path + * @return void + */ + public static function createLayoutFromFile($code, $viewPath) + { + $sections = self::getTemplateSections($viewPath); + + $name = array_get($sections, 'settings.name', '???'); + + $css = 'a, a:hover { + text-decoration: none; + color: #0862A2; + font-weight: bold; + } + + td, tr, th, table { + padding: 0px; + margin: 0px; + } + + p { + margin: 10px 0; + }'; + + self::create([ + 'is_locked' => true, + 'name' => $name, + 'code' => $code, + 'content_css' => $css, + 'content_html' => array_get($sections, 'html'), + 'content_text' => array_get($sections, 'text') + ]); + } + + protected static function getTemplateSections($code) + { + return MailParser::parse(File::get(View::make($code)->getPath())); + } } diff --git a/modules/system/models/MailPartial.php b/modules/system/models/MailPartial.php new file mode 100644 index 000000000..4f0655262 --- /dev/null +++ b/modules/system/models/MailPartial.php @@ -0,0 +1,90 @@ + 'required|unique:system_mail_partials', + 'name' => 'required', + 'content_html' => 'required', + ]; + + /** + * Loops over each mail layout and ensures the system has a layout, + * if the layout does not exist, it will create one. + * @return void + */ + public static function createPartials() + { + $dbPartials = self::lists('code', 'code'); + + $definitions = MailManager::instance()->listRegisteredPartials(); + foreach ($definitions as $code => $path) { + if (array_key_exists($code, $dbPartials)) { + continue; + } + + self::createPartialFromFile($code, $path); + } + } + + /** + * Creates a layout using the contents of a specified file. + * @param string $code New Partial code + * @param string $viewPath View path + * @return void + */ + public static function createPartialFromFile($code, $viewPath) + { + $sections = self::getTemplateSections($viewPath); + + $name = array_get($sections, 'settings.name', '???'); + + self::create([ + 'name' => $name, + 'code' => $code, + 'is_custom' => 0, + 'content_html' => array_get($sections, 'html'), + 'content_text' => array_get($sections, 'text') + ]); + } + + protected static function getTemplateSections($code) + { + return MailParser::parse(File::get(View::make($code)->getPath())); + } +} diff --git a/modules/system/models/MailTemplate.php b/modules/system/models/MailTemplate.php index 6fbdfc038..6f4c96222 100644 --- a/modules/system/models/MailTemplate.php +++ b/modules/system/models/MailTemplate.php @@ -22,6 +22,19 @@ class MailTemplate extends Model */ protected $table = 'system_mail_templates'; + /** + * @var array Guarded fields + */ + protected $guarded = []; + + /** + * @var array Fillable fields + */ + protected $fillable = []; + + /** + * @var array Validation rules + */ public $rules = [ 'code' => 'required|unique:system_mail_templates', 'subject' => 'required', @@ -40,7 +53,7 @@ class MailTemplate extends Model public static function listAllTemplates() { $fileTemplates = (array) MailManager::instance()->listRegisteredTemplates(); - $dbTemplates = (array) self::lists('description', 'code'); + $dbTemplates = (array) self::lists('code', 'code'); $templates = $fileTemplates + $dbTemplates; ksort($templates); return $templates; @@ -52,6 +65,9 @@ class MailTemplate extends Model */ public static function syncAll() { + MailLayout::createLayouts(); + MailPartial::createPartials(); + $templates = MailManager::instance()->listRegisteredTemplates(); $dbTemplates = self::lists('is_custom', 'code'); $newTemplates = array_diff_key($templates, $dbTemplates); @@ -59,8 +75,8 @@ class MailTemplate extends Model /* * Clean up non-customized templates */ - foreach ($dbTemplates as $code => $is_custom) { - if ($is_custom) { + foreach ($dbTemplates as $code => $isCustom) { + if ($isCustom) { continue; } @@ -72,9 +88,10 @@ class MailTemplate extends Model /* * Create new templates */ - foreach ($newTemplates as $code => $description) { + foreach ($newTemplates as $code) { $sections = self::getTemplateSections($code); $layoutCode = array_get($sections, 'settings.layout', 'default'); + $description = array_get($sections, 'settings.description'); $template = self::make(); $template->code = $code; diff --git a/modules/system/models/mailpartial/columns.yaml b/modules/system/models/mailpartial/columns.yaml new file mode 100644 index 000000000..e6e02eb8a --- /dev/null +++ b/modules/system/models/mailpartial/columns.yaml @@ -0,0 +1,13 @@ +# =================================== +# Column Definitions +# =================================== + +columns: + + name: + label: system::lang.mail_templates.name + searchable: true + + code: + label: system::lang.mail_templates.code + searchable: true diff --git a/modules/system/models/mailpartial/fields.yaml b/modules/system/models/mailpartial/fields.yaml new file mode 100644 index 000000000..bea9592cd --- /dev/null +++ b/modules/system/models/mailpartial/fields.yaml @@ -0,0 +1,34 @@ +# =================================== +# Field Definitions +# =================================== + +fields: + + code: + label: system::lang.mail_templates.code + comment: system::lang.mail_templates.code_comment + span: left + context: create + + name@create: + label: system::lang.mail_templates.name + span: right + + name@update: + label: system::lang.mail_templates.name + +secondaryTabs: + fields: + + content_html: + type: codeeditor + size: giant + tab: system::lang.mail_templates.content_html + language: html + stretch: true + + content_text: + type: textarea + size: giant + tab: system::lang.mail_templates.content_text + stretch: true diff --git a/modules/system/twig/MailComponentNode.php b/modules/system/twig/MailComponentNode.php new file mode 100644 index 000000000..82d735567 --- /dev/null +++ b/modules/system/twig/MailComponentNode.php @@ -0,0 +1,51 @@ + $nodes, 'body' => $body], ['names' => $paramNames], $lineno, $tag); + } + + /** + * Compiles the node to PHP. + * + * @param Twig_Compiler $compiler A Twig_Compiler instance + */ + public function compile(Twig_Compiler $compiler) + { + $compiler->addDebugInfo($this); + + $compiler->write("\$context['__system_component_params'] = [];\n"); + + $compiler + ->addDebugInfo($this) + ->write('ob_start();') + ->subcompile($this->getNode('body')) + ->write("\$context['__system_component_params']['body'] = ob_get_clean();"); + + for ($i = 1; $i < count($this->getNode('nodes')); $i++) { + $compiler->write("\$context['__system_component_params']['".$this->getAttribute('names')[$i-1]."'] = "); + $compiler->subcompile($this->getNode('nodes')->getNode($i)); + $compiler->write(";\n"); + } + + $compiler + ->write("echo \System\Classes\MailManager::instance()->renderPartial(") + ->subcompile($this->getNode('nodes')->getNode(0)) + ->write(", \$context['__system_component_params']") + ->write(");\n") + ; + + $compiler->write("unset(\$context['__system_component_params']);\n"); + } +} diff --git a/modules/system/twig/MailComponentTokenParser.php b/modules/system/twig/MailComponentTokenParser.php new file mode 100644 index 000000000..4edd98b07 --- /dev/null +++ b/modules/system/twig/MailComponentTokenParser.php @@ -0,0 +1,82 @@ +getLine(); + $stream = $this->parser->getStream(); + + $name = $this->parser->getExpressionParser()->parseExpression(); + $paramNames = []; + $nodes = [$name]; + + $end = false; + while (!$end) { + $current = $stream->next(); + + switch ($current->getType()) { + case Twig_Token::NAME_TYPE: + $paramNames[] = $current->getValue(); + $stream->expect(Twig_Token::OPERATOR_TYPE, '='); + $nodes[] = $this->parser->getExpressionParser()->parseExpression(); + break; + + case Twig_Token::BLOCK_END_TYPE: + $end = true; + break; + + default: + throw new Twig_Error_Syntax( + sprintf('Invalid syntax in the partial tag. Line %s', $lineno), + $stream->getCurrent()->getLine(), + $stream->getFilename() + ); + break; + } + } + + $body = $this->parser->subparse([$this, 'decideComponentEnd'], true); + $stream->expect(Twig_Token::BLOCK_END_TYPE); + + return new MailComponentNode(new Twig_Node($nodes), $paramNames, $body, $token->getLine(), $this->getTag()); + } + + public function decideComponentEnd(Twig_Token $token) + { + return $token->test('endcomponent'); + } + + /** + * Gets the tag name associated with this token parser. + * + * @return string The tag name + */ + public function getTag() + { + return 'component'; + } +} diff --git a/modules/system/twig/MailPartialNode.php b/modules/system/twig/MailPartialNode.php new file mode 100644 index 000000000..8b95e3f63 --- /dev/null +++ b/modules/system/twig/MailPartialNode.php @@ -0,0 +1,45 @@ + $nodes], ['names' => $paramNames], $lineno, $tag); + } + + /** + * Compiles the node to PHP. + * + * @param Twig_Compiler $compiler A Twig_Compiler instance + */ + public function compile(Twig_Compiler $compiler) + { + $compiler->addDebugInfo($this); + + $compiler->write("\$context['__system_partial_params'] = [];\n"); + + for ($i = 1; $i < count($this->getNode('nodes')); $i++) { + $compiler->write("\$context['__system_partial_params']['".$this->getAttribute('names')[$i-1]."'] = "); + $compiler->subcompile($this->getNode('nodes')->getNode($i)); + $compiler->write(";\n"); + } + + $compiler + ->write("echo \$this->env->getExtension('Cms\Twig\Extension')->partialFunction(") + ->subcompile($this->getNode('nodes')->getNode(0)) + ->write(", \$context['__system_partial_params']") + ->write(");\n") + ; + + $compiler->write("unset(\$context['__system_partial_params']);\n"); + } +} diff --git a/modules/system/twig/MailPartialTokenParser.php b/modules/system/twig/MailPartialTokenParser.php new file mode 100644 index 000000000..2b68c5a10 --- /dev/null +++ b/modules/system/twig/MailPartialTokenParser.php @@ -0,0 +1,74 @@ +getLine(); + $stream = $this->parser->getStream(); + + $name = $this->parser->getExpressionParser()->parseExpression(); + $paramNames = []; + $nodes = [$name]; + + $end = false; + while (!$end) { + $current = $stream->next(); + + switch ($current->getType()) { + case Twig_Token::NAME_TYPE: + $paramNames[] = $current->getValue(); + $stream->expect(Twig_Token::OPERATOR_TYPE, '='); + $nodes[] = $this->parser->getExpressionParser()->parseExpression(); + break; + + case Twig_Token::BLOCK_END_TYPE: + $end = true; + break; + + default: + throw new Twig_Error_Syntax( + sprintf('Invalid syntax in the partial tag. Line %s', $lineno), + $stream->getCurrent()->getLine(), + $stream->getFilename() + ); + break; + } + } + + return new MailPartialNode(new Twig_Node($nodes), $paramNames, $token->getLine(), $this->getTag()); + } + + /** + * Gets the tag name associated with this token parser. + * + * @return string The tag name + */ + public function getTag() + { + return 'partial'; + } +} diff --git a/modules/system/views/mail/button.htm b/modules/system/views/mail/button.htm new file mode 100644 index 000000000..25f41c220 --- /dev/null +++ b/modules/system/views/mail/button.htm @@ -0,0 +1,5 @@ +name = "Button" +== +{{ body|raw }} +== + diff --git a/modules/system/views/mail/default.htm b/modules/system/views/mail/default.htm new file mode 100644 index 000000000..18c35509b --- /dev/null +++ b/modules/system/views/mail/default.htm @@ -0,0 +1,14 @@ +name = "Default layout" +== +{{ content|raw }} +== + + + + + + {{ content|raw }} + + diff --git a/modules/system/views/mail/system.htm b/modules/system/views/mail/system.htm new file mode 100644 index 000000000..a04ed935c --- /dev/null +++ b/modules/system/views/mail/system.htm @@ -0,0 +1,20 @@ +name = "System layout" +== +{{ content|raw }} + + +--- +This is an automatic message. Please do not reply to it. +== + + + + + + {{ content|raw }} +This is an automatic message. Please do not reply to it.
+ + diff --git a/modules/system/views/mail/table.htm b/modules/system/views/mail/table.htm new file mode 100644 index 000000000..4005c84a6 --- /dev/null +++ b/modules/system/views/mail/table.htm @@ -0,0 +1,5 @@ +name = "Table" +== +{{ body|raw }} +== +