Introduce mail branding settings
This commit is contained in:
parent
181be5979a
commit
31ffadbc4c
|
|
@ -435,6 +435,15 @@ class ServiceProvider extends ModuleServiceProvider
|
|||
'permissions' => ['system.manage_mail_settings'],
|
||||
'order' => 620
|
||||
],
|
||||
'mail_brand_settings' => [
|
||||
'label' => 'system::lang.mail_brand.menu_label',
|
||||
'description' => 'system::lang.mail_brand.menu_description',
|
||||
'category' => SettingsManager::CATEGORY_MAIL,
|
||||
'icon' => 'icon-paint-brush',
|
||||
'url' => Backend::url('system/mailbrandsettings'),
|
||||
'permissions' => ['system.manage_mail_settings'],
|
||||
'order' => 630
|
||||
],
|
||||
'event_logs' => [
|
||||
'label' => 'system::lang.event_log.menu_label',
|
||||
'description' => 'system::lang.event_log.menu_description',
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
.field-colorpicker {
|
||||
float: right;
|
||||
margin-top: -10px;
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
|
||||
$(document).on('change', '.field-colorpicker', function() {
|
||||
$('#brandSettingsForm').request('onRefresh', {
|
||||
data: { 'fields': ['_mail_preview'] }
|
||||
})
|
||||
})
|
||||
|
||||
function createPreviewContainer(el, content) {
|
||||
var newiframe
|
||||
|
||||
// Shadow DOM ignores media queries
|
||||
// if (document.body.attachShadow) {
|
||||
if (false) {
|
||||
var shadow = el.attachShadow({ mode: 'open' })
|
||||
shadow.innerHTML = content
|
||||
}
|
||||
else {
|
||||
newiframe = document.createElement('iframe')
|
||||
|
||||
'srcdoc' in newiframe
|
||||
? newiframe.srcdoc = content
|
||||
: newiframe.src = 'data:text/html;charset=UTF-8,' + content
|
||||
|
||||
var parent = el.parentNode
|
||||
parent.replaceChild(newiframe, el)
|
||||
|
||||
newiframe.style.width = '100%'
|
||||
newiframe.setAttribute('frameborder', 0)
|
||||
|
||||
newiframe.onload = adjustIframeHeight
|
||||
}
|
||||
|
||||
function adjustIframeHeight() {
|
||||
newiframe.style.height = '500px'
|
||||
newiframe.style.height = 1 + (newiframe.contentWindow.document.getElementsByTagName('body')[0].scrollHeight) +'px'
|
||||
}
|
||||
|
||||
$(document).render(adjustIframeHeight)
|
||||
$(window).resize(adjustIframeHeight)
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ use Twig;
|
|||
use Markdown;
|
||||
use System\Models\MailPartial;
|
||||
use System\Models\MailTemplate;
|
||||
use System\Models\MailBrandSetting;
|
||||
use System\Helpers\View as ViewHelper;
|
||||
use System\Classes\PluginManager;
|
||||
use System\Classes\MarkupManager;
|
||||
|
|
@ -48,7 +49,12 @@ class MailManager
|
|||
/**
|
||||
* @var bool Internal marker for rendering mode
|
||||
*/
|
||||
protected $isHtmlRenderMode;
|
||||
protected $isHtmlRenderMode = false;
|
||||
|
||||
/**
|
||||
* @var bool Internal marker for booting custom twig extensions.
|
||||
*/
|
||||
protected $isTwigStarted = false;
|
||||
|
||||
/**
|
||||
* This function hijacks the `addContent` method of the `October\Rain\Mail\Mailer`
|
||||
|
|
@ -66,12 +72,7 @@ class MailManager
|
|||
/*
|
||||
* Start twig transaction
|
||||
*/
|
||||
$markupManager = MarkupManager::instance();
|
||||
$markupManager->beginTransaction();
|
||||
$markupManager->registerTokenParsers([
|
||||
new MailPartialTokenParser,
|
||||
new MailComponentTokenParser
|
||||
]);
|
||||
$this->startTwig();
|
||||
|
||||
/*
|
||||
* Inject global view variables
|
||||
|
|
@ -84,55 +85,98 @@ class MailManager
|
|||
/*
|
||||
* Subject
|
||||
*/
|
||||
$customSubject = $message->getSwiftMessage()->getSubject();
|
||||
if (empty($customSubject)) {
|
||||
$swiftMessage = $message->getSwiftMessage();
|
||||
|
||||
if (empty($swiftMessage->getSubject())) {
|
||||
$message->subject(Twig::parse($template->subject, $data));
|
||||
}
|
||||
|
||||
if (!isset($data['subject'])) {
|
||||
$data['subject'] = $swiftMessage->getSubject();
|
||||
}
|
||||
|
||||
/*
|
||||
* HTML contents
|
||||
*/
|
||||
$html = $this->renderHtmlContents($template, $data);
|
||||
$html = $this->renderTemplate($template, $data);
|
||||
|
||||
$message->setBody($html, 'text/html');
|
||||
|
||||
/*
|
||||
* Text contents
|
||||
*/
|
||||
$text = $this->renderTextContents($template, $data);
|
||||
$text = $this->renderTextTemplate($template, $data);
|
||||
|
||||
$message->addPart($text, 'text/plain');
|
||||
|
||||
/*
|
||||
* End twig transaction
|
||||
*/
|
||||
$markupManager->endTransaction();
|
||||
$this->stopTwig();
|
||||
}
|
||||
|
||||
//
|
||||
// Rendering
|
||||
//
|
||||
|
||||
public function renderHtmlContents($template, $data)
|
||||
/**
|
||||
* Render the Markdown template into HTML.
|
||||
*
|
||||
* @param string $content
|
||||
* @param array $data
|
||||
* @return string
|
||||
*/
|
||||
public function render($content, $data = [])
|
||||
{
|
||||
if (!$content) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$html = $this->renderTwig($content, $data);
|
||||
|
||||
$html = Markdown::parse($html);
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
public function renderTemplate($template, $data = [])
|
||||
{
|
||||
$this->isHtmlRenderMode = true;
|
||||
|
||||
$templateHtml = $template->content_html;
|
||||
|
||||
$html = Twig::parse($templateHtml, $data);
|
||||
$html = Markdown::parse($html);
|
||||
$html = $this->render($template->content_html, $data);
|
||||
|
||||
if ($template->layout) {
|
||||
$html = Twig::parse($template->layout->content_html, [
|
||||
$html = $this->renderTwig($template->layout->content_html, [
|
||||
'content' => $html,
|
||||
'css' => $template->layout->content_css
|
||||
'css' => $template->layout->content_css,
|
||||
'brandCss' => MailBrandSetting::compileCss()
|
||||
] + (array) $data);
|
||||
}
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
public function renderTextContents($template, $data)
|
||||
/**
|
||||
* Render the Markdown template into text.
|
||||
*
|
||||
* @param string $view
|
||||
* @param array $data
|
||||
* @return string
|
||||
*/
|
||||
public function renderText($content, $data = [])
|
||||
{
|
||||
if (!$content) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$text = $this->renderTwig($content, $data);
|
||||
|
||||
$text = html_entity_decode(preg_replace("/[\r\n]{2,}/", "\n\n", $text), ENT_QUOTES, 'UTF-8');
|
||||
|
||||
return $text;
|
||||
}
|
||||
|
||||
public function renderTextTemplate($template, $data = [])
|
||||
{
|
||||
$this->isHtmlRenderMode = false;
|
||||
|
||||
|
|
@ -142,9 +186,10 @@ class MailManager
|
|||
$templateText = $template->content_html;
|
||||
}
|
||||
|
||||
$text = Twig::parse($templateText, $data);
|
||||
$text = $this->renderText($templateText, $data);
|
||||
|
||||
if ($template->layout) {
|
||||
$text = Twig::parse($template->layout->content_text, [
|
||||
$text = $this->renderTwig($template->layout->content_text, [
|
||||
'content' => $text
|
||||
] + (array) $data);
|
||||
}
|
||||
|
|
@ -166,7 +211,7 @@ class MailManager
|
|||
}
|
||||
}
|
||||
|
||||
public function renderHtmlPartial($partial, $params)
|
||||
protected function renderHtmlPartial($partial, $params)
|
||||
{
|
||||
$content = $partial->content_html;
|
||||
|
||||
|
|
@ -174,12 +219,12 @@ class MailManager
|
|||
return '';
|
||||
}
|
||||
|
||||
$params['body'] = Markdown::parse(array_get($params, 'body'));
|
||||
$params['body'] = array_get($params, 'body');
|
||||
|
||||
return Twig::parse($content, $params);
|
||||
return $this->renderTwig($content, $params);
|
||||
}
|
||||
|
||||
public function renderTextPartial($partial, $params)
|
||||
protected function renderTextPartial($partial, $params)
|
||||
{
|
||||
$content = $partial->content_text ?: $partial->content_html;
|
||||
|
||||
|
|
@ -187,7 +232,53 @@ class MailManager
|
|||
return '';
|
||||
}
|
||||
|
||||
return Twig::parse($content, $params);
|
||||
return $this->renderTwig($content, $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;
|
||||
}
|
||||
|
||||
protected function startTwig()
|
||||
{
|
||||
if ($this->isTwigStarted) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->isTwigStarted = true;
|
||||
|
||||
$markupManager = MarkupManager::instance();
|
||||
$markupManager->beginTransaction();
|
||||
$markupManager->registerTokenParsers([
|
||||
new MailPartialTokenParser,
|
||||
new MailComponentTokenParser
|
||||
]);
|
||||
}
|
||||
|
||||
protected function stopTwig()
|
||||
{
|
||||
if (!$this->isTwigStarted) {
|
||||
return;
|
||||
}
|
||||
|
||||
$markupManager = MarkupManager::instance();
|
||||
$markupManager->endTransaction();
|
||||
|
||||
$this->isTwigStarted = false;
|
||||
}
|
||||
|
||||
//
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ class MarkupManager
|
|||
/**
|
||||
* @var array Transaction based extension items
|
||||
*/
|
||||
protected $tranItems;
|
||||
protected $transactionItems;
|
||||
|
||||
/**
|
||||
* @var bool Manager is in transaction mode
|
||||
|
|
@ -114,7 +114,7 @@ class MarkupManager
|
|||
*/
|
||||
public function registerExtensions($type, array $definitions)
|
||||
{
|
||||
$items = $this->transactionMode ? 'tranItems' : 'items';
|
||||
$items = $this->transactionMode ? 'transactionItems' : 'items';
|
||||
|
||||
if (is_null($this->$items)) {
|
||||
$this->$items = [];
|
||||
|
|
@ -182,8 +182,8 @@ class MarkupManager
|
|||
$results = $this->items[$type];
|
||||
}
|
||||
|
||||
if ($this->tranItems !== null && isset($this->tranItems[$type])) {
|
||||
$results = array_merge($results, $this->tranItems[$type]);
|
||||
if ($this->transactionItems !== null && isset($this->transactionItems[$type])) {
|
||||
$results = array_merge($results, $this->transactionItems[$type]);
|
||||
}
|
||||
|
||||
return $results;
|
||||
|
|
@ -381,6 +381,6 @@ class MarkupManager
|
|||
{
|
||||
$this->transactionMode = false;
|
||||
|
||||
$this->tranItems = null;
|
||||
$this->transactionItems = null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,79 @@
|
|||
<?php namespace System\Controllers;
|
||||
|
||||
use File;
|
||||
use Flash;
|
||||
use Backend;
|
||||
use BackendMenu;
|
||||
use ApplicationException;
|
||||
use System\Models\MailBrandSetting;
|
||||
use System\Classes\SettingsManager;
|
||||
use System\Classes\MailManager;
|
||||
use Backend\Classes\Controller;
|
||||
use System\Models\MailLayout;
|
||||
use System\Models\MailTemplate;
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Mail brand customization controller
|
||||
*
|
||||
* @package october\backend
|
||||
* @author Alexey Bobkov, Samuel Georges
|
||||
*
|
||||
*/
|
||||
class MailBrandSettings extends Controller
|
||||
{
|
||||
public $implement = [
|
||||
\Backend\Behaviors\FormController::class
|
||||
];
|
||||
|
||||
public $formConfig = 'config_form.yaml';
|
||||
|
||||
public $requiredPermissions = ['system.manage_mail_templates'];
|
||||
|
||||
public $bodyClass = 'compact-container';
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->pageTitle = 'Customize mail appearance';
|
||||
|
||||
BackendMenu::setContext('October.System', 'system', 'settings');
|
||||
SettingsManager::setContext('October.System', 'mail_brand_settings');
|
||||
}
|
||||
|
||||
public function index()
|
||||
{
|
||||
$this->addJs('/modules/system/assets/js/mailbrandsettings/mailbrandsettings.js', 'core');
|
||||
$this->addCss('/modules/system/assets/css/mailbrandsettings/mailbrandsettings.css', 'core');
|
||||
|
||||
$setting = MailBrandSetting::instance();
|
||||
|
||||
if ($setting->exists) {
|
||||
return $this->update($setting->id);
|
||||
}
|
||||
else {
|
||||
return $this->create();
|
||||
}
|
||||
}
|
||||
|
||||
public function renderSampleMessage()
|
||||
{
|
||||
$layout = new MailLayout;
|
||||
$layout->fillFromCode('default');
|
||||
|
||||
$template = new MailTemplate;
|
||||
$template->layout = $layout;
|
||||
$template->content_html = File::get(base_path('modules/system/models/mailbrandsetting/sample_template.htm'));
|
||||
|
||||
return MailManager::instance()->renderTemplate($template);
|
||||
}
|
||||
|
||||
public function formCreateModelObject()
|
||||
{
|
||||
return MailBrandSetting::instance();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
<div id="<?= $this->getId('mailPreviewContainer') ?>"></div>
|
||||
<script type="text/template" id="<?= $this->getId('mailPreviewTemplate') ?>"><?= $this->renderSampleMessage() ?></script>
|
||||
|
||||
<script>
|
||||
(function($){
|
||||
var previewContents = $('#<?= $this->getId('mailPreviewTemplate') ?>').html()
|
||||
previewFrame = $('#<?= $this->getId('mailPreviewContainer') ?>').get(0)
|
||||
|
||||
createPreviewContainer(previewFrame, previewContents)
|
||||
})(window.jQuery)
|
||||
</script>
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
# ===================================
|
||||
# Form Behavior Config
|
||||
# ===================================
|
||||
|
||||
# Record name
|
||||
name: system::lang.mail_brand.menu_label
|
||||
|
||||
# Fields are defined by extension
|
||||
form: ~/modules/system/models/mailbrandsetting/fields.yaml
|
||||
|
||||
# Model Class name
|
||||
modelClass: System\Models\MailBrandSetting
|
||||
|
||||
# Default redirect location
|
||||
defaultRedirect: system/themes
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
<?php Block::put('breadcrumb') ?>
|
||||
<ul>
|
||||
<li><?= e(trans($this->pageTitle)) ?></li>
|
||||
</ul>
|
||||
<?php Block::endPut() ?>
|
||||
|
||||
<?php if (!$this->fatalError): ?>
|
||||
|
||||
<?php Block::put('form-contents') ?>
|
||||
<div class="layout">
|
||||
|
||||
<div class="layout-row">
|
||||
<?= $this->formRenderOutsideFields() ?>
|
||||
<?= $this->formRenderPrimaryTabs() ?>
|
||||
</div>
|
||||
|
||||
<div class="form-buttons">
|
||||
<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('backend::lang.form.saving')) ?>"
|
||||
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('backend::lang.form.saving')) ?>"
|
||||
class="btn btn-default">
|
||||
<?= e(trans('backend::lang.form.save_and_close')) ?>
|
||||
</button>
|
||||
<span class="btn-text">
|
||||
<?= e(trans('backend::lang.form.or')) ?> <a href="<?= Backend::url('backend/users') ?>"><?= e(trans('backend::lang.form.cancel')) ?></a>
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
class="oc-icon-trash-o btn-icon danger pull-right"
|
||||
data-request="onDelete"
|
||||
data-load-indicator="<?= e(trans('backend::lang.form.deleting')) ?>"
|
||||
data-request-confirm="<?= e(trans('backend::lang.user.delete_confirm')) ?>">
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<?php Block::endPut() ?>
|
||||
|
||||
<?php Block::put('form-sidebar') ?>
|
||||
<div class="hide-tabs"><?= $this->formRenderSecondaryTabs() ?></div>
|
||||
<?php Block::endPut() ?>
|
||||
|
||||
<?php Block::put('body') ?>
|
||||
<?= Form::open(['id' => 'brandSettingsForm', 'class'=>'layout stretch']) ?>
|
||||
<?= $this->makeLayout('form-with-sidebar') ?>
|
||||
<?= Form::close() ?>
|
||||
<?php Block::endPut() ?>
|
||||
|
||||
<?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 ?>
|
||||
|
|
@ -224,6 +224,10 @@ return [
|
|||
'sending' => 'Sending test message...',
|
||||
'return' => 'Return to template list'
|
||||
],
|
||||
'mail_brand' => [
|
||||
'menu_label' => 'Customize mail templates',
|
||||
'menu_description' => 'Modify the colors and appearance of mail templates.',
|
||||
],
|
||||
'install' => [
|
||||
'project_label' => 'Attach to Project',
|
||||
'plugin_label' => 'Install Plugin',
|
||||
|
|
|
|||
|
|
@ -0,0 +1,156 @@
|
|||
<?php namespace System\Models;
|
||||
|
||||
use App;
|
||||
use Url;
|
||||
use Str;
|
||||
use File;
|
||||
use Lang;
|
||||
use Model;
|
||||
use Cache;
|
||||
use Config;
|
||||
use Less_Parser;
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Mail brand settings
|
||||
*
|
||||
* @package october\system
|
||||
* @author Alexey Bobkov, Samuel Georges
|
||||
*/
|
||||
class MailBrandSetting extends Model
|
||||
{
|
||||
use \System\Traits\ViewMaker;
|
||||
use \October\Rain\Database\Traits\Validation;
|
||||
|
||||
/**
|
||||
* @var array Behaviors implemented by this model.
|
||||
*/
|
||||
public $implement = [
|
||||
\System\Behaviors\SettingsModel::class
|
||||
];
|
||||
|
||||
/**
|
||||
* @var string Unique code
|
||||
*/
|
||||
public $settingsCode = 'system_mail_brand_settings';
|
||||
|
||||
/**
|
||||
* @var mixed Settings form field defitions
|
||||
*/
|
||||
public $settingsFields = 'fields.yaml';
|
||||
|
||||
const CACHE_KEY = 'system::mailbrand.custom_css';
|
||||
|
||||
const WHITE_COLOR = '#fff';
|
||||
const BODY_BG = '#f5f8fa';
|
||||
const PRIMARY_BG = '#3097d1';
|
||||
const POSITIVE_BG = '#2ab27b';
|
||||
const NEGATIVE_BG = '#bf5329';
|
||||
const HEADER_COLOR = '#bbbfc3';
|
||||
const HEADING_COLOR = '#2f3133';
|
||||
const TEXT_COLOR = '#74787e';
|
||||
const LINK_COLOR = '#3869d4';
|
||||
const FOOTER_COLOR = '#aeaeae';
|
||||
const BORDER_COLOR = '#edeff2';
|
||||
const PROMOTION_BORDER_COLOR = '#9ba2ab';
|
||||
|
||||
/**
|
||||
* Validation rules
|
||||
*/
|
||||
public $rules = [
|
||||
];
|
||||
|
||||
/**
|
||||
* Initialize the seed data for this model. This only executes when the
|
||||
* model is first created or reset to default.
|
||||
* @return void
|
||||
*/
|
||||
public function initSettingsData()
|
||||
{
|
||||
$config = App::make('config');
|
||||
|
||||
$vars = static::getCssVars();
|
||||
|
||||
foreach ($vars as $var => $default) {
|
||||
$this->{$var} = $config->get('brand.mail.'.Str::studly($var), $default);
|
||||
}
|
||||
}
|
||||
|
||||
public function afterSave()
|
||||
{
|
||||
Cache::forget(self::CACHE_KEY);
|
||||
}
|
||||
|
||||
public static function renderCss()
|
||||
{
|
||||
if (Cache::has(self::CACHE_KEY)) {
|
||||
return Cache::get(self::CACHE_KEY);
|
||||
}
|
||||
|
||||
try {
|
||||
$customCss = self::compileCss();
|
||||
Cache::forever(self::CACHE_KEY, $customCss);
|
||||
}
|
||||
catch (Exception $ex) {
|
||||
$customCss = '/* ' . $ex->getMessage() . ' */';
|
||||
}
|
||||
|
||||
return $customCss;
|
||||
}
|
||||
|
||||
protected static function getCssVars()
|
||||
{
|
||||
$vars = [
|
||||
'body_bg' => self::BODY_BG,
|
||||
'content_bg' => self::WHITE_COLOR,
|
||||
'content_inner_bg' => self::WHITE_COLOR,
|
||||
'button_text_color' => self::WHITE_COLOR,
|
||||
'button_primary_bg' => self::PRIMARY_BG,
|
||||
'button_positive_bg' => self::POSITIVE_BG,
|
||||
'button_negative_bg' => self::NEGATIVE_BG,
|
||||
'header_color' => self::HEADER_COLOR,
|
||||
'heading_color' => self::HEADING_COLOR,
|
||||
'text_color' => self::TEXT_COLOR,
|
||||
'link_color' => self::LINK_COLOR,
|
||||
'footer_color' => self::FOOTER_COLOR,
|
||||
'body_border_color' => self::BORDER_COLOR,
|
||||
'subcopy_border_color' => self::BORDER_COLOR,
|
||||
'table_border_color' => self::BORDER_COLOR,
|
||||
'panel_bg' => self::BORDER_COLOR,
|
||||
'promotion_bg' => self::WHITE_COLOR,
|
||||
'promotion_border_color' => self::PROMOTION_BORDER_COLOR,
|
||||
];
|
||||
|
||||
return $vars;
|
||||
}
|
||||
|
||||
protected static function makeCssVars()
|
||||
{
|
||||
$vars = static::getCssVars();
|
||||
|
||||
$result = [];
|
||||
|
||||
foreach ($vars as $var => $default) {
|
||||
// panel_bg -> panel-bg
|
||||
$cssVar = str_replace('_', '-', $var);
|
||||
|
||||
$result[$cssVar] = self::get($var, $default);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public static function compileCss()
|
||||
{
|
||||
$parser = new Less_Parser(['compress' => true]);
|
||||
$basePath = base_path('modules/system/models/mailbrandsetting');
|
||||
|
||||
$parser->ModifyVars(static::makeCssVars());
|
||||
|
||||
$parser->parse(File::get($basePath . '/custom.less'));
|
||||
|
||||
$css = $parser->getCss();
|
||||
|
||||
return $css;
|
||||
}
|
||||
}
|
||||
|
|
@ -79,45 +79,55 @@ class MailLayout extends Model
|
|||
continue;
|
||||
}
|
||||
|
||||
self::createLayoutFromFile($code, $path);
|
||||
$layout = new static;
|
||||
$layout->code = $code;
|
||||
$layout->is_locked = true;
|
||||
$layout->fillFromView($path);
|
||||
$layout->save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
public function fillFromCode($code = null)
|
||||
{
|
||||
$sections = self::getTemplateSections($viewPath);
|
||||
$definitions = MailManager::instance()->listRegisteredLayouts();
|
||||
|
||||
$name = array_get($sections, 'settings.name', '???');
|
||||
|
||||
$css = 'a, a:hover {
|
||||
text-decoration: none;
|
||||
color: #0862A2;
|
||||
font-weight: bold;
|
||||
if ($code === null) {
|
||||
$code = $this->code;
|
||||
}
|
||||
|
||||
td, tr, th, table {
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
if (!$definition = array_get($definitions, $code)) {
|
||||
throw new ApplicationException('Unable to find a registered layout with code: '.$code);
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 10px 0;
|
||||
}';
|
||||
$this->fillFromView($definition);
|
||||
}
|
||||
|
||||
self::create([
|
||||
'is_locked' => true,
|
||||
'name' => $name,
|
||||
'code' => $code,
|
||||
'content_css' => $css,
|
||||
'content_html' => array_get($sections, 'html'),
|
||||
'content_text' => array_get($sections, 'text')
|
||||
]);
|
||||
public function fillFromView($path)
|
||||
{
|
||||
$sections = self::getTemplateSections($path);
|
||||
|
||||
$css = '
|
||||
@media only screen and (max-width: 600px) {
|
||||
.inner-body {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.footer {
|
||||
width: 100% !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 500px) {
|
||||
.button {
|
||||
width: 100% !important;
|
||||
}
|
||||
}
|
||||
';
|
||||
|
||||
$this->name = array_get($sections, 'settings.name', '???');
|
||||
$this->content_css = $css;
|
||||
$this->content_html = array_get($sections, 'html');
|
||||
$this->content_text = array_get($sections, 'text');
|
||||
}
|
||||
|
||||
protected static function getTemplateSections($code)
|
||||
|
|
|
|||
|
|
@ -43,6 +43,13 @@ class MailPartial extends Model
|
|||
'content_html' => 'required',
|
||||
];
|
||||
|
||||
public function afterFetch()
|
||||
{
|
||||
if (!$this->is_custom) {
|
||||
$this->fillFromCode();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loops over each mail layout and ensures the system has a layout,
|
||||
* if the layout does not exist, it will create one.
|
||||
|
|
@ -58,29 +65,36 @@ class MailPartial extends Model
|
|||
continue;
|
||||
}
|
||||
|
||||
self::createPartialFromFile($code, $path);
|
||||
$partial = new static;
|
||||
$partial->code = $code;
|
||||
$partial->is_custom = 0;
|
||||
$partial->fillFromView($path);
|
||||
$partial->save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
public function fillFromCode($code = null)
|
||||
{
|
||||
$sections = self::getTemplateSections($viewPath);
|
||||
$definitions = MailManager::instance()->listRegisteredPartials();
|
||||
|
||||
$name = array_get($sections, 'settings.name', '???');
|
||||
if ($code === null) {
|
||||
$code = $this->code;
|
||||
}
|
||||
|
||||
self::create([
|
||||
'name' => $name,
|
||||
'code' => $code,
|
||||
'is_custom' => 0,
|
||||
'content_html' => array_get($sections, 'html'),
|
||||
'content_text' => array_get($sections, 'text')
|
||||
]);
|
||||
if (!$definition = array_get($definitions, $code)) {
|
||||
throw new ApplicationException('Unable to find a registered partial with code: '.$code);
|
||||
}
|
||||
|
||||
$this->fillFromView($definition);
|
||||
}
|
||||
|
||||
public function fillFromView($path)
|
||||
{
|
||||
$sections = self::getTemplateSections($path);
|
||||
|
||||
$this->name = array_get($sections, 'settings.name', '???');
|
||||
$this->content_html = array_get($sections, 'html');
|
||||
$this->content_text = array_get($sections, 'text');
|
||||
}
|
||||
|
||||
protected static function getTemplateSections($code)
|
||||
|
|
|
|||
|
|
@ -112,6 +112,7 @@ class MailTemplate extends Model
|
|||
public function fillFromView()
|
||||
{
|
||||
$sections = self::getTemplateSections($this->code);
|
||||
|
||||
$this->content_html = $sections['html'];
|
||||
$this->content_text = $sections['text'];
|
||||
$this->subject = array_get($sections, 'settings.subject', 'No subject');
|
||||
|
|
|
|||
|
|
@ -0,0 +1,285 @@
|
|||
/* Base */
|
||||
|
||||
body, body *:not(html):not(style):not(br):not(tr):not(code) {
|
||||
font-family: Avenir, Helvetica, sans-serif;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: @body-bg;
|
||||
color: #74787E;
|
||||
height: 100%;
|
||||
hyphens: auto;
|
||||
line-height: 1.4;
|
||||
margin: 0;
|
||||
-moz-hyphens: auto;
|
||||
-ms-word-break: break-all;
|
||||
width: 100% !important;
|
||||
-webkit-hyphens: auto;
|
||||
-webkit-text-size-adjust: none;
|
||||
word-break: break-all;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
p,
|
||||
ul,
|
||||
ol,
|
||||
blockquote {
|
||||
line-height: 1.4;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
a {
|
||||
color: @link-color;
|
||||
}
|
||||
|
||||
a img {
|
||||
border: none;
|
||||
}
|
||||
|
||||
/* Typography */
|
||||
|
||||
h1 {
|
||||
color: #2F3133;
|
||||
font-size: 19px;
|
||||
font-weight: bold;
|
||||
margin-top: 0;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
h2 {
|
||||
color: #2F3133;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
margin-top: 0;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
h3 {
|
||||
color: #2F3133;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
margin-top: 0;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
p {
|
||||
color: #74787E;
|
||||
font-size: 16px;
|
||||
line-height: 1.5em;
|
||||
margin-top: 0;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
p.sub {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
/* Layout */
|
||||
|
||||
.wrapper {
|
||||
background-color: @body-bg;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
-premailer-cellpadding: 0;
|
||||
-premailer-cellspacing: 0;
|
||||
-premailer-width: 100%;
|
||||
}
|
||||
|
||||
.content {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
-premailer-cellpadding: 0;
|
||||
-premailer-cellspacing: 0;
|
||||
-premailer-width: 100%;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
|
||||
.header {
|
||||
padding: 25px 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.header a {
|
||||
color: #bbbfc3;
|
||||
font-size: 19px;
|
||||
font-weight: bold;
|
||||
text-decoration: none;
|
||||
text-shadow: 0 1px 0 white;
|
||||
}
|
||||
|
||||
/* Body */
|
||||
|
||||
.body {
|
||||
background-color: @content-bg;
|
||||
border-bottom: 1px solid @body-border-color;
|
||||
border-top: 1px solid @body-border-color;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
-premailer-cellpadding: 0;
|
||||
-premailer-cellspacing: 0;
|
||||
-premailer-width: 100%;
|
||||
}
|
||||
|
||||
.inner-body {
|
||||
background-color: @content-inner-bg;
|
||||
margin: 0 auto;
|
||||
padding: 0;
|
||||
width: 570px;
|
||||
-premailer-cellpadding: 0;
|
||||
-premailer-cellspacing: 0;
|
||||
-premailer-width: 570px;
|
||||
}
|
||||
|
||||
/* Subcopy */
|
||||
|
||||
.subcopy {
|
||||
border-top: 1px solid @subcopy-border-color;
|
||||
margin-top: 25px;
|
||||
padding-top: 25px;
|
||||
}
|
||||
|
||||
.subcopy p {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
|
||||
.footer {
|
||||
margin: 0 auto;
|
||||
padding: 0;
|
||||
text-align: center;
|
||||
width: 570px;
|
||||
-premailer-cellpadding: 0;
|
||||
-premailer-cellspacing: 0;
|
||||
-premailer-width: 570px;
|
||||
}
|
||||
|
||||
.footer p {
|
||||
color: #AEAEAE;
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Tables */
|
||||
|
||||
.table table {
|
||||
margin: 30px auto;
|
||||
width: 100%;
|
||||
-premailer-cellpadding: 0;
|
||||
-premailer-cellspacing: 0;
|
||||
-premailer-width: 100%;
|
||||
}
|
||||
|
||||
.table th {
|
||||
border-bottom: 1px solid #EDEFF2;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
.table td {
|
||||
color: #74787E;
|
||||
font-size: 15px;
|
||||
line-height: 18px;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.content-cell {
|
||||
padding: 35px;
|
||||
}
|
||||
|
||||
/* Buttons */
|
||||
|
||||
.action {
|
||||
margin: 30px auto;
|
||||
padding: 0;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
-premailer-cellpadding: 0;
|
||||
-premailer-cellspacing: 0;
|
||||
-premailer-width: 100%;
|
||||
}
|
||||
|
||||
.button {
|
||||
border-radius: 3px;
|
||||
box-shadow: 0 2px 3px rgba(0, 0, 0, 0.16);
|
||||
color: @button-text-color;
|
||||
display: inline-block;
|
||||
text-decoration: none;
|
||||
-webkit-text-size-adjust: none;
|
||||
}
|
||||
|
||||
.button-primary {
|
||||
background-color: @button-primary-bg;
|
||||
border-top: 10px solid @button-primary-bg;
|
||||
border-right: 18px solid @button-primary-bg;
|
||||
border-bottom: 10px solid @button-primary-bg;
|
||||
border-left: 18px solid @button-primary-bg;
|
||||
}
|
||||
|
||||
.button-positive {
|
||||
background-color: @button-positive-bg;
|
||||
border-top: 10px solid @button-positive-bg;
|
||||
border-right: 18px solid @button-positive-bg;
|
||||
border-bottom: 10px solid @button-positive-bg;
|
||||
border-left: 18px solid @button-positive-bg;
|
||||
}
|
||||
|
||||
.button-negative {
|
||||
background-color: @button-negative-bg;
|
||||
border-top: 10px solid @button-negative-bg;
|
||||
border-right: 18px solid @button-negative-bg;
|
||||
border-bottom: 10px solid @button-negative-bg;
|
||||
border-left: 18px solid @button-negative-bg;
|
||||
}
|
||||
|
||||
/* Panels */
|
||||
|
||||
.panel {
|
||||
margin: 0 0 21px;
|
||||
}
|
||||
|
||||
.panel-content {
|
||||
background-color: #EDEFF2;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.panel-item {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.panel-item p:last-of-type {
|
||||
margin-bottom: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
/* Promotions */
|
||||
|
||||
.promotion {
|
||||
background-color: #FFFFFF;
|
||||
border: 2px dashed #9BA2AB;
|
||||
margin: 0;
|
||||
margin-bottom: 25px;
|
||||
margin-top: 25px;
|
||||
padding: 24px;
|
||||
width: 100%;
|
||||
-premailer-cellpadding: 0;
|
||||
-premailer-cellspacing: 0;
|
||||
-premailer-width: 100%;
|
||||
}
|
||||
|
||||
.promotion h1 {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.promotion p {
|
||||
font-size: 15px;
|
||||
text-align: center;
|
||||
}
|
||||
|
|
@ -0,0 +1,122 @@
|
|||
# ===================================
|
||||
# Field Definitions
|
||||
# ===================================
|
||||
|
||||
fields:
|
||||
|
||||
_mail_preview:
|
||||
type: partial
|
||||
path: field_mail_preview
|
||||
|
||||
secondaryTabs:
|
||||
fields:
|
||||
|
||||
_section_background:
|
||||
label: Background
|
||||
type: section
|
||||
|
||||
body_bg:
|
||||
label: Body background
|
||||
type: colorpicker
|
||||
availableColors: []
|
||||
|
||||
content_bg:
|
||||
label: Content background
|
||||
type: colorpicker
|
||||
availableColors: []
|
||||
|
||||
content_inner_bg:
|
||||
label: Inner content background
|
||||
type: colorpicker
|
||||
availableColors: []
|
||||
|
||||
_section_buttons:
|
||||
label: Buttons
|
||||
type: section
|
||||
|
||||
button_text_color:
|
||||
label: Button text color
|
||||
type: colorpicker
|
||||
availableColors: []
|
||||
|
||||
button_primary_bg:
|
||||
label: Primary button background
|
||||
type: colorpicker
|
||||
availableColors: []
|
||||
|
||||
button_positive_bg:
|
||||
label: Positive button background
|
||||
type: colorpicker
|
||||
availableColors: []
|
||||
|
||||
button_negative_bg:
|
||||
label: Negative button background
|
||||
type: colorpicker
|
||||
availableColors: []
|
||||
|
||||
_section_type:
|
||||
label: Typography
|
||||
type: section
|
||||
|
||||
header_color:
|
||||
label: Site name color
|
||||
type: colorpicker
|
||||
availableColors: []
|
||||
|
||||
heading_color:
|
||||
label: Heading color
|
||||
type: colorpicker
|
||||
availableColors: []
|
||||
|
||||
text_color:
|
||||
label: Text color
|
||||
type: colorpicker
|
||||
availableColors: []
|
||||
|
||||
link_color:
|
||||
label: Link color
|
||||
type: colorpicker
|
||||
availableColors: []
|
||||
|
||||
footer_color:
|
||||
label: Footer color
|
||||
type: colorpicker
|
||||
availableColors: []
|
||||
|
||||
_section_borders:
|
||||
label: Borders
|
||||
type: section
|
||||
|
||||
body_border_color:
|
||||
label: Body border color
|
||||
type: colorpicker
|
||||
availableColors: []
|
||||
|
||||
subcopy_border_color:
|
||||
label: Subcopy border color
|
||||
type: colorpicker
|
||||
availableColors: []
|
||||
|
||||
table_border_color:
|
||||
label: Table border color
|
||||
type: colorpicker
|
||||
availableColors: []
|
||||
|
||||
_section_components:
|
||||
label: Components
|
||||
type: section
|
||||
|
||||
panel_bg:
|
||||
label: Panel background
|
||||
type: colorpicker
|
||||
availableColors: []
|
||||
|
||||
promotion_bg:
|
||||
label: Promotion background
|
||||
type: colorpicker
|
||||
availableColors: []
|
||||
|
||||
promotion_border_color:
|
||||
label: Promotion border color
|
||||
type: colorpicker
|
||||
availableColors: []
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
{% component 'header' %}
|
||||
October CMS
|
||||
{% endcomponent %}
|
||||
|
||||
# Heading 1
|
||||
|
||||
This is a paragraph filled with Lorem Ipsum and a link.
|
||||
Cumque dicta <a>doloremque eaque</a>, enim error laboriosam pariatur possimus tenetur veritatis voluptas.
|
||||
|
||||
## Heading 2
|
||||
|
||||
{% component 'table' %}
|
||||
| Item | Description | Price |
|
||||
| ------------- |:-------------:| --------:|
|
||||
| Item 1 | Centered | $10 |
|
||||
| Item 2 | Right-Aligned | $20 |
|
||||
{% endcomponent %}
|
||||
|
||||
### Heading 3
|
||||
|
||||
This is a paragraph filled with Lorem Ipsum and a link.
|
||||
Cumque dicta <a>doloremque eaque</a>, enim error laboriosam pariatur possimus tenetur veritatis voluptas.
|
||||
|
||||
{% component 'button' url='javascript:;' %}
|
||||
Primary button
|
||||
{% endcomponent %}
|
||||
|
||||
{% component 'button' type='positive' url='javascript:;' %}
|
||||
Positive button
|
||||
{% endcomponent %}
|
||||
|
||||
{% component 'button' type='negative' url='javascript:;' %}
|
||||
Negative button
|
||||
{% endcomponent %}
|
||||
|
||||
{% component 'panel' %}
|
||||
How awesome is this panel?
|
||||
{% endcomponent %}
|
||||
|
||||
Some more text
|
||||
|
||||
{% component 'promotion' %}
|
||||
Coupon code: OCTOBER
|
||||
{% endcomponent %}
|
||||
|
||||
Thanks,
|
||||
October CMS
|
||||
|
||||
{% component 'subcopy' %}
|
||||
This is the subcopy of the email
|
||||
{% endcomponent %}
|
||||
|
||||
{% component 'footer' %}
|
||||
© 2017 October CMS. All rights reserved.
|
||||
{% endcomponent %}
|
||||
Loading…
Reference in New Issue