parent
52e61f6a3b
commit
4663531de2
|
|
@ -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',
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
subject = "Welcome to October CMS"
|
||||
layout = "system"
|
||||
description = "Invite new admin to the site"
|
||||
==
|
||||
|
||||
Hi {{ name }},
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
subject = "Password Reset"
|
||||
layout = "system"
|
||||
description = "Reset an admin password"
|
||||
==
|
||||
|
||||
Hello {{ name }},
|
||||
|
|
|
|||
|
|
@ -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
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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 '<!-- Missing partial: '.$code.' -->';
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,38 @@
|
|||
<?php namespace System\Controllers;
|
||||
|
||||
use Str;
|
||||
use Lang;
|
||||
use File;
|
||||
use Flash;
|
||||
use Backend;
|
||||
use Redirect;
|
||||
use BackendMenu;
|
||||
use Backend\Classes\Controller;
|
||||
use ApplicationException;
|
||||
use System\Classes\SettingsManager;
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Mail partials controller
|
||||
*
|
||||
* @package october\system
|
||||
* @author Alexey Bobkov, Samuel Georges
|
||||
*/
|
||||
class MailPartials extends Controller
|
||||
{
|
||||
public $implement = [
|
||||
'Backend.Behaviors.FormController',
|
||||
];
|
||||
|
||||
public $requiredPermissions = ['system.manage_mail_templates'];
|
||||
|
||||
public $formConfig = 'config_form.yaml';
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
BackendMenu::setContext('October.System', 'system', 'settings');
|
||||
SettingsManager::setContext('October.System', 'mail_templates');
|
||||
}
|
||||
}
|
||||
|
|
@ -29,7 +29,12 @@ class MailTemplates extends Controller
|
|||
|
||||
public $requiredPermissions = ['system.manage_mail_templates'];
|
||||
|
||||
public $listConfig = ['templates' => '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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
<?php Block::put('breadcrumb') ?>
|
||||
<ul>
|
||||
<li><a href="<?= Backend::url('system/mailtemplates') ?>"><?= e(trans('system::lang.mail_templates.menu_partials_label')) ?></a></li>
|
||||
<li><?= e(trans($this->pageTitle)) ?></li>
|
||||
</ul>
|
||||
<?php Block::endPut() ?>
|
||||
|
||||
<?php if (!$this->fatalError): ?>
|
||||
|
||||
<?= Form::open(['class'=>'layout']) ?>
|
||||
|
||||
<div class="layout-row">
|
||||
<?= $this->formRender() ?>
|
||||
</div>
|
||||
|
||||
<div class="form-buttons p-t">
|
||||
<div class="loading-indicator-container">
|
||||
<button
|
||||
type="submit"
|
||||
data-request="onSave"
|
||||
data-hotkey="ctrl+s, cmd+s"
|
||||
data-load-indicator="<?= e(trans('system::lang.mail_templates.creating_partial')) ?>"
|
||||
class="btn btn-primary">
|
||||
<?= e(trans('backend::lang.form.create')) ?>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
data-request="onSave"
|
||||
data-request-data="close:1"
|
||||
data-hotkey="ctrl+enter, cmd+enter"
|
||||
data-load-indicator="<?= e(trans('system::lang.mail_templates.creating_partial')) ?>"
|
||||
class="btn btn-default">
|
||||
<?= e(trans('backend::lang.form.create_and_close')) ?>
|
||||
</button>
|
||||
<span class="btn-text">
|
||||
<?= e(trans('backend::lang.form.or')) ?> <a href="<?= Backend::url('system/mailtemplates') ?>"><?= e(trans('backend::lang.form.close')) ?></a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?= Form::close() ?>
|
||||
|
||||
<?php else: ?>
|
||||
|
||||
<p class="flash-message static error"><?= e(trans($this->fatalError)) ?></p>
|
||||
<p><a href="<?= Backend::url('system/mailtemplates') ?>" class="btn btn-default"><?= e(trans('system::lang.mail_templates.return')) ?></a></p>
|
||||
|
||||
<?php endif ?>
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
<?php Block::put('breadcrumb') ?>
|
||||
<ul>
|
||||
<li><a href="<?= Backend::url('system/mailtemplates') ?>"><?= e(trans('system::lang.mail_templates.menu_partials_label')) ?></a></li>
|
||||
<li><?= e(trans($this->pageTitle)) ?></li>
|
||||
</ul>
|
||||
<?php Block::endPut() ?>
|
||||
|
||||
<?php if (!$this->fatalError): ?>
|
||||
|
||||
<?= Form::open(['class'=>'layout']) ?>
|
||||
|
||||
<div class="layout-row min-size">
|
||||
<div class="scoreboard">
|
||||
<div data-control="toolbar">
|
||||
<div class="scoreboard-item title-value">
|
||||
<h4><?= e(trans('system::lang.mail_templates.layout')) ?></h4>
|
||||
<p><?= $formModel->code ?></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layout-row">
|
||||
<?= $this->formRender() ?>
|
||||
</div>
|
||||
|
||||
<div class="form-buttons p-t">
|
||||
<div class="loading-indicator-container">
|
||||
<button
|
||||
type="submit"
|
||||
data-request="onSave"
|
||||
data-request-data="redirect:0"
|
||||
data-hotkey="ctrl+s, cmd+s"
|
||||
data-load-indicator="<?= e(trans('system::lang.mail_templates.saving_layout')) ?>"
|
||||
class="btn btn-primary">
|
||||
<?= e(trans('backend::lang.form.save')) ?>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
data-request="onSave"
|
||||
data-request-data="close:1"
|
||||
data-hotkey="ctrl+enter, cmd+enter"
|
||||
data-load-indicator="<?= e(trans('system::lang.mail_templates.saving_layout')) ?>"
|
||||
class="btn btn-default">
|
||||
<?= e(trans('backend::lang.form.save_and_close')) ?>
|
||||
</button>
|
||||
<?php if (!$formModel->is_locked): ?>
|
||||
<button
|
||||
type="button"
|
||||
class="oc-icon-trash-o btn-icon danger pull-right"
|
||||
data-request="onDelete"
|
||||
data-load-indicator="<?= e(trans('system::lang.mail_templates.deleting_layout')) ?>"
|
||||
data-request-confirm="<?= e(trans('system::lang.mail_templates.delete_layout_confirm')) ?>">
|
||||
</button>
|
||||
<?php endif ?>
|
||||
<span class="btn-text">
|
||||
<?= e(trans('backend::lang.form.or')) ?> <a href="<?= Backend::url('system/mailtemplates') ?>"><?= e(trans('backend::lang.form.cancel')) ?></a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?= Form::close() ?>
|
||||
|
||||
<?php else: ?>
|
||||
|
||||
<p class="flash-message static error"><?= e(trans($this->fatalError)) ?></p>
|
||||
<p><a href="<?= Backend::url('system/mailtemplates') ?>" class="btn btn-default"><?= e(trans('system::lang.mail_templates.return')) ?></a></p>
|
||||
|
||||
<?php endif ?>
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
<div data-control="toolbar">
|
||||
<a
|
||||
href="<?= Backend::url('system/mailpartials/create') ?>"
|
||||
class="btn btn-primary oc-icon-plus">
|
||||
<?= e(trans('system::lang.mail_templates.new_partial')) ?>
|
||||
</a>
|
||||
</div>
|
||||
|
|
@ -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
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
<ul class="nav nav-tabs">
|
||||
<li class="active"><a href="#templates"><?= e(trans('system::lang.mail_templates.templates')) ?></a></li>
|
||||
<li><a href="#layouts"><?= e(trans('system::lang.mail_templates.layouts')) ?></a></li>
|
||||
<li><a href="#partials"><?= e(trans('system::lang.mail_templates.partials')) ?></a></li>
|
||||
</ul>
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane active">
|
||||
|
|
@ -10,5 +11,8 @@
|
|||
<div class="tab-pane">
|
||||
<?= $this->listRender('layouts') ?>
|
||||
</div>
|
||||
<div class="tab-pane">
|
||||
<?= $this->listRender('partials') ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
use October\Rain\Database\Schema\Blueprint;
|
||||
use October\Rain\Database\Updates\Migration;
|
||||
|
||||
class DbSystemMailPartials extends Migration
|
||||
{
|
||||
public function up()
|
||||
{
|
||||
Schema::create('system_mail_partials', function (Blueprint $table) {
|
||||
$table->engine = 'InnoDB';
|
||||
$table->increments('id');
|
||||
$table->string('name')->nullable();
|
||||
$table->string('code')->nullable();
|
||||
$table->text('content_html')->nullable();
|
||||
$table->text('content_text')->nullable();
|
||||
$table->boolean('is_custom')->default(0);
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('system_mail_partials');
|
||||
}
|
||||
}
|
||||
|
|
@ -5,74 +5,8 @@ use System\Models\MailLayout;
|
|||
|
||||
class SeedSetupMailLayouts extends Seeder
|
||||
{
|
||||
|
||||
public function run()
|
||||
{
|
||||
|
||||
$css = 'a, a:hover {
|
||||
text-decoration: none;
|
||||
color: #0862A2;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
td, tr, th, table {
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 10px 0;
|
||||
}';
|
||||
|
||||
$html = '<html>
|
||||
<head>
|
||||
<style type="text/css" media="screen">
|
||||
{{ css|raw }}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
{{ content|raw }}
|
||||
</body>
|
||||
</html>';
|
||||
|
||||
$text = '{{ content|raw }}';
|
||||
|
||||
MailLayout::create([
|
||||
'is_locked' => true,
|
||||
'name' => 'Default',
|
||||
'code' => 'default',
|
||||
'content_html' => $html,
|
||||
'content_css' => $css,
|
||||
'content_text' => $text,
|
||||
]);
|
||||
|
||||
$html = '<html>
|
||||
<head>
|
||||
<style type="text/css" media="screen">
|
||||
{{ css|raw }}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
{{ content|raw }}
|
||||
<hr />
|
||||
<p>This is an automatic message. Please do not reply to it.</p>
|
||||
</body>
|
||||
</html>';
|
||||
|
||||
$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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 --',
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
<?php namespace System\Models;
|
||||
|
||||
use File;
|
||||
use View;
|
||||
use Model;
|
||||
use System\Classes\MailManager;
|
||||
use October\Rain\Mail\MailParser;
|
||||
use ApplicationException;
|
||||
|
||||
/**
|
||||
|
|
@ -18,6 +22,19 @@ class MailLayout extends Model
|
|||
*/
|
||||
protected $table = 'system_mail_layouts';
|
||||
|
||||
/**
|
||||
* @var array Guarded fields
|
||||
*/
|
||||
protected $guarded = [];
|
||||
|
||||
/**
|
||||
* @var array Fillable fields
|
||||
*/
|
||||
protected $fillable = [];
|
||||
|
||||
/**
|
||||
* @var array Validation rules
|
||||
*/
|
||||
public $rules = [
|
||||
'code' => '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()));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,90 @@
|
|||
<?php namespace System\Models;
|
||||
|
||||
use Twig;
|
||||
use File;
|
||||
use View;
|
||||
use Model;
|
||||
use Markdown;
|
||||
use System\Classes\MailManager;
|
||||
use October\Rain\Mail\MailParser;
|
||||
use ApplicationException;
|
||||
|
||||
/**
|
||||
* Mail partial
|
||||
*
|
||||
* @package october\system
|
||||
* @author Alexey Bobkov, Samuel Georges
|
||||
*/
|
||||
class MailPartial extends Model
|
||||
{
|
||||
use \October\Rain\Database\Traits\Validation;
|
||||
|
||||
/**
|
||||
* @var string The database table used by the model.
|
||||
*/
|
||||
protected $table = 'system_mail_partials';
|
||||
|
||||
/**
|
||||
* @var array Guarded fields
|
||||
*/
|
||||
protected $guarded = [];
|
||||
|
||||
/**
|
||||
* @var array Fillable fields
|
||||
*/
|
||||
protected $fillable = [];
|
||||
|
||||
/**
|
||||
* @var array Validation rules
|
||||
*/
|
||||
public $rules = [
|
||||
'code' => '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()));
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
<?php namespace System\Twig;
|
||||
|
||||
use Twig_Node;
|
||||
use Twig_Compiler;
|
||||
|
||||
/**
|
||||
* Represents a component node
|
||||
*
|
||||
* @package october\cms
|
||||
* @author Alexey Bobkov, Samuel Georges
|
||||
*/
|
||||
class MailComponentNode extends Twig_Node
|
||||
{
|
||||
public function __construct(Twig_Node $nodes, $paramNames, $body, $lineno, $tag = 'component')
|
||||
{
|
||||
parent::__construct(['nodes' => $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");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
<?php namespace System\Twig;
|
||||
|
||||
use Twig_Node;
|
||||
use Twig_Token;
|
||||
use Twig_TokenParser;
|
||||
use Twig_Error_Syntax;
|
||||
|
||||
/**
|
||||
* Parser for the `{% component %}` Twig tag.
|
||||
*
|
||||
* {% component "sidebar" %}
|
||||
*
|
||||
* {% component "sidebar" name='John' %}
|
||||
*
|
||||
* {% component "sidebar" name='John', year=2013 %}
|
||||
*
|
||||
* @package october\system
|
||||
* @author Alexey Bobkov, Samuel Georges
|
||||
*/
|
||||
class MailComponentTokenParser extends Twig_TokenParser
|
||||
{
|
||||
/**
|
||||
* Parses a token and returns a node.
|
||||
*
|
||||
* @param Twig_Token $token A Twig_Token instance
|
||||
* @return Twig_Node A Twig_Node instance
|
||||
*/
|
||||
public function parse(Twig_Token $token)
|
||||
{
|
||||
$lineno = $token->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';
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
<?php namespace System\Twig;
|
||||
|
||||
use Twig_Node;
|
||||
use Twig_Compiler;
|
||||
|
||||
/**
|
||||
* Represents a partial node
|
||||
*
|
||||
* @package october\cms
|
||||
* @author Alexey Bobkov, Samuel Georges
|
||||
*/
|
||||
class MailPartialNode extends Twig_Node
|
||||
{
|
||||
public function __construct(Twig_Node $nodes, $paramNames, $lineno, $tag = 'partial')
|
||||
{
|
||||
parent::__construct(['nodes' => $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");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
<?php namespace System\Twig;
|
||||
|
||||
use Twig_Node;
|
||||
use Twig_Token;
|
||||
use Twig_TokenParser;
|
||||
use Twig_Error_Syntax;
|
||||
|
||||
/**
|
||||
* Parser for the `{% partial %}` Twig tag.
|
||||
*
|
||||
* {% partial "sidebar" %}
|
||||
*
|
||||
* {% partial "sidebar" name='John' %}
|
||||
*
|
||||
* {% partial "sidebar" name='John', year=2013 %}
|
||||
*
|
||||
* @package october\system
|
||||
* @author Alexey Bobkov, Samuel Georges
|
||||
*/
|
||||
class MailPartialTokenParser extends Twig_TokenParser
|
||||
{
|
||||
/**
|
||||
* Parses a token and returns a node.
|
||||
*
|
||||
* @param Twig_Token $token A Twig_Token instance
|
||||
* @return Twig_Node A Twig_Node instance
|
||||
*/
|
||||
public function parse(Twig_Token $token)
|
||||
{
|
||||
$lineno = $token->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';
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
name = "Button"
|
||||
==
|
||||
{{ body|raw }}
|
||||
==
|
||||
<button>{{ body|raw }}</button>
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
name = "Default layout"
|
||||
==
|
||||
{{ content|raw }}
|
||||
==
|
||||
<html>
|
||||
<head>
|
||||
<style type="text/css" media="screen">
|
||||
{{ css|raw }}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
{{ content|raw }}
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
name = "System layout"
|
||||
==
|
||||
{{ content|raw }}
|
||||
|
||||
|
||||
---
|
||||
This is an automatic message. Please do not reply to it.
|
||||
==
|
||||
<html>
|
||||
<head>
|
||||
<style type="text/css" media="screen">
|
||||
{{ css|raw }}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
{{ content|raw }}
|
||||
<hr />
|
||||
<p>This is an automatic message. Please do not reply to it.</p>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
name = "Table"
|
||||
==
|
||||
{{ body|raw }}
|
||||
==
|
||||
<div class="table">{{ body|raw }}</div>
|
||||
Loading…
Reference in New Issue