Convert all boolean table columnns to use is_ prefix

Add email template/layout system
This commit is contained in:
Sam Georges 2014-06-06 21:38:34 +10:00
parent 9a923e3b98
commit 2b820a20d6
52 changed files with 1085 additions and 53 deletions

View File

@ -1,3 +1,7 @@
* **Build 101** (2014-06-06)
- Added a global traceLog() helper for help with debugging.
- New settings area added to manage Email templates and layouts.
* **Build 99** (2014-06-05)
- Plugins can now be removed, refreshed and disabled via the back-end.

View File

@ -7,6 +7,7 @@ use BackendMenu;
use BackendAuth;
use Backend\Classes\WidgetManager;
use October\Rain\Support\ModuleServiceProvider;
use System\Models\EmailTemplate;
class ServiceProvider extends ModuleServiceProvider
{
@ -70,6 +71,16 @@ class ServiceProvider extends ModuleServiceProvider
'backend.manage_users' => ['label' => 'Manage other administrators', 'tab' => 'System'],
]);
});
/*
* Register email templates
*/
EmailTemplate::registerCallback(function($template){
$template->registerEmailTemplates([
'backend::emails.invite' => 'Invitation for newly created administrators.',
'backend::emails.restore' => 'Password reset instructions for backend-end administrators.',
]);
});
}
/**

View File

@ -7279,6 +7279,18 @@ label {
.form-control.align-right {
text-align: right;
}
.form-preview {
padding: 15px;
margin-bottom: 20px;
background: white;
border: 1px solid #eee;
}
.form-preview .form-group {
padding-bottom: 10px;
}
.form-preview > .form-group:last-child {
padding-bottom: 0;
}
.form-elements:before,
.form-tabless-fields:before,
.form-elements:after,
@ -7769,6 +7781,15 @@ label {
.select2-drop .select2-results > li > div {
padding: 5px 7px 5px;
}
.list-preview {
padding: 20px 0 0;
margin-bottom: 20px;
background: white;
border: 1px solid #e2e2e2;
}
.list-preview .control-list:last-child > table {
margin-bottom: 0;
}
.control-list p.no-data {
padding: 18px 20px;
margin: 0 20px;
@ -8515,7 +8536,7 @@ table.table.data tr.list-tree-level-25 td.list-data-column-1 {
.modal-body {
padding-bottom: 0;
}
.modal-body p:last-child {
.modal-body > p:last-child {
margin-bottom: 20px;
}
.control-popup.fade .modal-dialog {

View File

@ -194,7 +194,7 @@
var e = $.Event('showing.oc.inspector')
this.$el.trigger(e, [{callback: displayPopover}])
if (e.isDefaultPrevented())
if (e.isDefaultPrevented())
return
if (!e.isPropagationStopped())

View File

@ -15,14 +15,14 @@
* - data-closable - enables the Close Tab feature
* - data-pane-classes - a list of CSS classes to apply new pane elements
*
* Example with data attributes (data-control="tab"):
* Example with data attributes (data-control="tab"):
*
* <div class="control-tabs master" data-control="tab" data-closable>
* <ul class="nav nav-tabs">
* <li class="active"><a href="#home">Home</a></li>
* <li class="active"><a href="#home">Home</a></li>
* </ul>
* <div class="tab-content">
* <div class="tab-pane active">Home</div>
* <div class="tab-pane active">Home</div>
* </div>
* </div>
*

View File

@ -22,6 +22,25 @@ label {
}
}
//
// Form containers
//
.form-preview {
padding: 15px;
margin-bottom: 20px;
background: white;
border: 1px solid #eee;
.form-group {
padding-bottom: 10px;
}
>.form-group:last-child {
padding-bottom: 0;
}
}
//
// Nice forms
//

View File

@ -51,6 +51,26 @@
// </table>
// </div>
//
//
// List containers
//
.list-preview {
padding: 20px 0 0;
margin-bottom: 20px;
background: white;
border: 1px solid @color-list-border;
.control-list:last-child > table {
margin-bottom: 0;
}
}
//
// List control
//
.control-list {
p.no-data {
padding: 18px 20px;

View File

@ -31,7 +31,7 @@
.modal-body {
padding-bottom: 0;
p:last-child {
> p:last-child {
margin-bottom: 20px;
}
}

View File

@ -128,12 +128,13 @@ class FormController extends ControllerBehavior
$this->initForm($model);
$this->controller->formBeforeSave($model);
$this->controller->formBeforeCreateSave($model);
$this->controller->formBeforeCreate($model);
$this->setModelAttributes($model, $this->formWidget->getSaveData());
$model->push($this->formWidget->getSessionKey());
$this->controller->formAfterSave($model);
$this->controller->formAfterCreate($model);
Flash::success($this->getLang('create[flash-save]', 'backend::lang.form.create_success'));
@ -177,12 +178,13 @@ class FormController extends ControllerBehavior
$this->initForm($model, 'update');
$this->controller->formBeforeSave($model);
$this->controller->formBeforeEditSave($model);
$this->controller->formBeforeUpdate($model);
$this->setModelAttributes($model, $this->formWidget->getSaveData());
$model->push($this->formWidget->getSessionKey());
$this->controller->formAfterSave($model);
$this->controller->formAfterUpdate($model);
Flash::success($this->getLang('update[flash-save]', 'backend::lang.form.update_success'));
@ -409,16 +411,10 @@ class FormController extends ControllerBehavior
//
/**
* Called before the creation form is saved.
* Called before the creation or updating form is saved.
* @param Model
*/
public function formBeforeCreateSave($model) {}
/**
* Called after the creation form is saved.
* @param Model
*/
public function formAfterCreateSave($model) {}
public function formBeforeSave($model) {}
/**
* Called after the creation or updating form is saved.
@ -426,23 +422,29 @@ class FormController extends ControllerBehavior
*/
public function formAfterSave($model) {}
/**
* Called before the creation form is saved.
* @param Model
*/
public function formBeforeCreate($model) {}
/**
* Called after the creation form is saved.
* @param Model
*/
public function formAfterCreate($model) {}
/**
* Called before the updating form is saved.
* @param Model
*/
public function formBeforeEditSave($model) {}
/**
* Called before the creation or updating form is saved.
* @param Model
*/
public function formBeforeSave($model) {}
public function formBeforeUpdate($model) {}
/**
* Called after the updating form is saved.
* @param Model
*/
public function formAfterEditSave($model) {}
public function formAfterUpdate($model) {}
/**
* Called after the form model is deleted.

View File

@ -60,6 +60,10 @@ class NavigationManager
$this->pluginManager = PluginManager::instance();
}
/**
* Loads the menu items from modules and plugins
* @return void
*/
protected function loadItems()
{
/*

View File

@ -26,6 +26,11 @@ class Index extends Controller
BackendMenu::setContextOwner('October.Backend');
new ReportContainer($this);
/* @todo Remove line if year >= 2015 */ if (\Schema::hasColumn('backend_users', 'activated')) \Schema::table('backend_users', function($table) { $table->renameColumn('activated', 'is_activated'); });
/* @todo Remove line if year >= 2015 */ if (\Schema::hasColumn('backend_user_throttle', 'suspended')) \Schema::table('backend_user_throttle', function($table) { $table->renameColumn('suspended', 'is_suspended'); });
/* @todo Remove line if year >= 2015 */ if (\Schema::hasColumn('backend_user_throttle', 'banned')) \Schema::table('backend_user_throttle', function($table) { $table->renameColumn('banned', 'is_banned'); });
/* @todo Remove line if year >= 2015 */ if (\Schema::hasColumn('deferred_bindings', 'bind')) \Schema::table('deferred_bindings', function($table) { $table->renameColumn('bind', 'is_bind'); });
/* @todo Remove line if year >= 2015 */ if (\Schema::hasColumn('system_files', 'public')) \Schema::table('system_files', function($table) { $table->renameColumn('public', 'is_public'); });
}
public function index()

View File

@ -21,7 +21,7 @@ class DbBackendUsers extends Migration
$table->string('persist_code')->nullable();
$table->string('reset_password_code')->nullable()->index();
$table->text('permissions')->nullable();
$table->boolean('activated')->default(0);
$table->boolean('is_activated')->default(0);
$table->timestamp('activated_at')->nullable();
$table->timestamp('last_login')->nullable();
$table->timestamps();

View File

@ -15,10 +15,10 @@ class DbBackendUserThrottle extends Migration
$table->integer('user_id')->unsigned();
$table->string('ip_address')->nullable();
$table->integer('attempts')->default(0);
$table->boolean('suspended')->default(0);
$table->boolean('banned')->default(0);
$table->timestamp('last_attempt_at')->nullable();
$table->boolean('is_suspended')->default(0);
$table->timestamp('suspended_at')->nullable();
$table->boolean('is_banned')->default(0);
$table->timestamp('banned_at')->nullable();
});
}

View File

@ -35,7 +35,7 @@ class SeedSetupAdmin extends Seeder
'first_name' => static::$firstName,
'last_name' => static::$lastName,
'permissions' => ['superuser' => 1],
'activated' => true
'is_activated' => true
]);
$user->addGroup($group);

View File

@ -191,7 +191,7 @@ class FileUpload extends FormWidgetBase
$file = new File();
$file->data = $uploadedFile;
$file->public = $fileRelation->isPublic();
$file->is_public = $fileRelation->isPublic();
$file->save();
$fileRelation->add($file, $this->sessionKey);

View File

@ -16,11 +16,18 @@ class RichEditor extends FormWidgetBase
*/
public $defaultAlias = 'richeditor';
/**
* @var boolean Determines whether content has HEAD and HTML tags.
*/
public $fullPage = false;
/**
* {@inheritDoc}
*/
public function render()
{
$this->fullPage = $this->getConfig('fullPage', $this->fullPage);
$this->prepareVars();
return $this->makePartial('richeditor');
}
@ -30,6 +37,7 @@ class RichEditor extends FormWidgetBase
*/
public function prepareVars()
{
$this->vars['fullPage'] = $this->fullPage;
$this->vars['stretch'] = $this->formField->stretch;
$this->vars['size'] = $this->formField->size;
$this->vars['name'] = $this->formField->getName();

View File

@ -1,4 +1,5 @@
subject = "Welcome to October CMS"
layout = "system"
==
Hi {{ name }},

View File

@ -1,4 +1,5 @@
subject = "Password Reset"
layout = "system"
==
Hello {{ name }},

View File

@ -3,7 +3,9 @@
/**
*
* DEPRECATED WARNING: This class is deprecated and should be deleted
* if the current year is equal to or greater than 2015 @todo.
* if the current year is equal to or greater than 2015.
*
* @todo Delete this file if year >= 2015.
*
*/

View File

@ -4,9 +4,7 @@
<h4 class="modal-title">File was changed</h4>
</div>
<div class="modal-body">
<div class="alert alert-warning">
<p>The file you're editing has been changed on disk by another user. You can either reload the file and lose your changes or override the file on the disk.</p>
</div>
<p>The file you're editing has been changed on disk by another user. You can either reload the file and lose your changes or override the file on the disk.</p>
</div>
<div class="modal-footer">
<button

View File

@ -144,8 +144,9 @@ class ServiceProvider extends ModuleServiceProvider
*/
BackendAuth::registerCallback(function($manager) {
$manager->registerPermissions('October.System', [
'system.manage_settings' => ['label' => 'Manage system settings', 'tab' => 'System'],
'system.manage_updates' => ['label' => 'Manage software updates', 'tab' => 'System'],
'system.manage_settings' => ['label' => 'Manage system settings', 'tab' => 'System'],
'system.manage_updates' => ['label' => 'Manage software updates', 'tab' => 'System'],
'system.manage_email_templates' => ['label' => 'Manage email templates', 'tab' => 'System'],
]);
});
@ -154,7 +155,7 @@ class ServiceProvider extends ModuleServiceProvider
*/
SettingsManager::instance()->registerCallback(function($manager){
$manager->registerSettingItems('October.System', [
'email' => [
'email_settings' => [
'label' => 'system::lang.email.menu_label',
'description' => 'system::lang.email.menu_description',
'category' => 'System',
@ -162,6 +163,14 @@ class ServiceProvider extends ModuleServiceProvider
'class' => 'System\Models\EmailSettings',
'sort' => 100
],
'email_templates' => [
'label' => 'system::lang.email_templates.menu_label',
'description' => 'system::lang.email_templates.menu_description',
'category' => 'System',
'icon' => 'icon-envelope-square',
'url' => Backend::url('system/emailtemplates'),
'sort' => 100
],
]);
});

View File

@ -104,6 +104,17 @@ abstract class PluginBase extends ServiceProviderBase
return [];
}
/**
* Registers any email templates implemented by this plugin.
* The templates must be returned in the following format:
* ['acme.blog::emails.welcome' => 'This is a description of the welcome template'],
* ['acme.blog::emails.forgot_password' => 'This is a description of the forgot password template'],
*/
public function registerEmailTemplates()
{
return [];
}
/**
* Registers a new console (artisan) command
* @param $key The command name

View File

@ -0,0 +1,39 @@
<?php namespace System\Controllers;
use Str;
use Lang;
use File;
use Flash;
use Backend;
use Redirect;
use BackendMenu;
use Backend\Classes\Controller;
use System\Classes\ApplicationException;
use Exception;
/**
* Email layouts controller
*
* @package october\system
* @author Alexey Bobkov, Samuel Georges
*
*/
class EmailLayouts extends Controller
{
public $implement = [
'Backend.Behaviors.FormController',
];
public $requiredPermissions = ['system.manage_email_templates'];
public $formConfig = 'config_form.yaml';
public function __construct()
{
parent::__construct();
BackendMenu::setContext('October.System', 'system', 'settings');
}
}

View File

@ -0,0 +1,56 @@
<?php namespace System\Controllers;
use Str;
use Lang;
use File;
use Flash;
use Backend;
use Redirect;
use BackendMenu;
use Backend\Classes\Controller;
use System\Models\EmailTemplate;
use System\Classes\ApplicationException;
use Exception;
/**
* Email templates controller
*
* @package october\system
* @author Alexey Bobkov, Samuel Georges
*
*/
class EmailTemplates extends Controller
{
public $implement = [
'Backend.Behaviors.FormController',
'Backend.Behaviors.ListController'
];
public $requiredPermissions = ['system.manage_email_templates'];
public $listConfig = ['templates' => 'config_templates_list.yaml', 'layouts' => 'config_layouts_list.yaml'];
public $formConfig = 'config_form.yaml';
public function __construct()
{
parent::__construct();
BackendMenu::setContext('October.System', 'system', 'settings');
}
public function index()
{
/* @todo Remove line if year >= 2015 */ if (!\System\Models\EmailLayout::whereCode('default')->count()) { \Eloquent::unguard(); with(new \System\Database\Seeds\SeedSetupEmailLayouts)->run(); }
EmailTemplate::syncAll();
$this->getClassExtension('Backend.Behaviors.ListController')->index();
$this->bodyClass = null;
}
public function formBeforeSave($model)
{
$model->is_custom = true;
}
}

View File

@ -62,6 +62,9 @@ class Updates extends Controller
return $this->getClassExtension('Backend.Behaviors.ListController')->index();
}
/**
* {@inheritDoc}
*/
public function listInjectRowClass($record, $definition = null)
{
if ($record->disabledByConfig)

View File

@ -0,0 +1,16 @@
# ===================================
# Form Behavior Config
# ===================================
name: system::lang.email_templates.layout
form: @/modules/system/models/emaillayout/fields.yaml
modelClass: System\Models\EmailLayout
defaultRedirect: system/emailtemplates
create:
redirect: system/emailtemplates/update/:id
redirectClose: system/emailtemplates
update:
redirect: system/emailtemplates
redirectClose: system/emailtemplates

View File

@ -0,0 +1,49 @@
<?php Block::put('breadcrumb') ?>
<ul>
<li><a href="<?= Backend::url('system/settings') ?>"><?= e(trans('system::lang.settings.menu_label')) ?></a></li>
<li><a href="<?= Backend::url('system/emailtemplates') ?>"><?= e(trans('system::lang.email_templates.menu_layouts_label')) ?></a></li>
<li><?= e($this->pageTitle) ?></li>
</ul>
<?php Block::endPut() ?>
<?php if (!$this->fatalError): ?>
<?= Form::open(['class'=>'layout-item stretch layout-column']) ?>
<?= $this->formRender() ?>
<div class="form-buttons layout-item fix">
<div class="loading-indicator-container">
<button
type="submit"
data-request="onSave"
data-hotkey="ctrl+s"
data-hotkey-mac="cmd+s"
data-load-indicator="Creating Layout..."
class="btn btn-primary">
Create
</button>
<button
type="button"
data-request="onSave"
data-request-data="close:1"
data-hotkey="ctrl+enter"
data-hotkey-mac="cmd+enter"
data-load-indicator="Creating Layout..."
class="btn btn-default">
Create and Close
</button>
<span class="btn-text">
or <a href="<?= Backend::url('system/emailtemplates') ?>">Cancel</a>
</span>
</div>
</div>
<?= Form::close() ?>
<?php else: ?>
<p class="flash-message static error"><?= e($this->fatalError) ?></p>
<p><a href="<?= Backend::url('system/emailtemplates') ?>" class="btn btn-default">Return to template list</a></p>
<?php endif ?>

View File

@ -0,0 +1,68 @@
<?php Block::put('breadcrumb') ?>
<ul>
<li><a href="<?= Backend::url('system/settings') ?>"><?= e(trans('system::lang.settings.menu_label')) ?></a></li>
<li><a href="<?= Backend::url('system/emailtemplates') ?>"><?= e(trans('system::lang.email_templates.menu_layouts_label')) ?></a></li>
<li><?= e($this->pageTitle) ?></li>
</ul>
<?php Block::endPut() ?>
<?php if (!$this->fatalError): ?>
<div class="scoreboard">
<div data-control="toolbar">
<div class="scoreboard-item title-value">
<h4><?= e(trans('system::lang.email_templates.template')) ?></h4>
<p><?= $formModel->code ?></p>
</div>
</div>
</div>
<?= Form::open(['class'=>'layout-item stretch layout-column']) ?>
<?= $this->formRender() ?>
<div class="form-buttons layout-item fix">
<div class="loading-indicator-container">
<button
type="submit"
data-request="onSave"
data-request-data="redirect:0"
data-hotkey="ctrl+s"
data-hotkey-mac="cmd+s"
data-load-indicator="Saving Layout..."
class="btn btn-primary">
<u>S</u>ave
</button>
<button
type="button"
data-request="onSave"
data-request-data="close:1"
data-hotkey="ctrl+enter"
data-hotkey-mac="cmd+enter"
data-load-indicator="Saving Layout..."
class="btn btn-default">
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="Deleting Layout..."
data-request-confirm="Do you really want to delete this layout?">
</button>
<?php endif ?>
<span class="btn-text">
or <a href="<?= Backend::url('system/emailtemplates') ?>">Cancel</a>
</span>
</div>
</div>
<?= Form::close() ?>
<?php else: ?>
<p class="flash-message static error"><?= e($this->fatalError) ?></p>
<p><a href="<?= Backend::url('system/emailtemplates') ?>" class="btn btn-default">Return to template list</a></p>
<?php endif ?>

View File

@ -0,0 +1,7 @@
<div data-control="toolbar">
<a
href="<?= Backend::url('system/emaillayouts/create') ?>"
class="btn btn-primary oc-icon-plus">
<?= e(trans('system::lang.email_templates.new_layout')) ?>
</a>
</div>

View File

@ -0,0 +1,7 @@
<div data-control="toolbar">
<a
href="<?= Backend::url('system/emailtemplates/create') ?>"
class="btn btn-primary oc-icon-plus">
<?= e(trans('system::lang.email_templates.new_template')) ?>
</a>
</div>

View File

@ -0,0 +1,16 @@
# ===================================
# Form Behavior Config
# ===================================
name: system::lang.email_templates.template
form: @/modules/system/models/emailtemplate/fields.yaml
modelClass: System\Models\EmailTemplate
defaultRedirect: system/emailtemplates
create:
redirect: system/emailtemplates/update/:id
redirectClose: system/emailtemplates
update:
redirect: system/emailtemplates
redirectClose: system/emailtemplates

View File

@ -0,0 +1,15 @@
# ===================================
# List Behavior Config
# ===================================
title: system::lang.email_templates.menu_label
list: @/modules/system/models/emaillayout/columns.yaml
modelClass: System\Models\EmailLayout
recordUrl: system/emaillayouts/update/:id
noRecordsMessage: backend::lang.list.no_records
showSetup: false
toolbar:
buttons: list_layouts_toolbar
search:
prompt: backend::lang.list.search_prompt

View File

@ -0,0 +1,16 @@
# ===================================
# List Behavior Config
# ===================================
title: system::lang.email_templates.menu_label
list: @/modules/system/models/emailtemplate/columns.yaml
modelClass: System\Models\EmailTemplate
recordUrl: system/emailtemplates/update/:id
noRecordsMessage: backend::lang.list.no_records
recordsPerPage: 20
showSetup: false
toolbar:
buttons: list_templates_toolbar
search:
prompt: backend::lang.list.search_prompt

View File

@ -0,0 +1,49 @@
<?php Block::put('breadcrumb') ?>
<ul>
<li><a href="<?= Backend::url('system/settings') ?>"><?= e(trans('system::lang.settings.menu_label')) ?></a></li>
<li><a href="<?= Backend::url('system/emailtemplates') ?>"><?= e(trans('system::lang.email_templates.menu_label')) ?></a></li>
<li><?= e($this->pageTitle) ?></li>
</ul>
<?php Block::endPut() ?>
<?php if (!$this->fatalError): ?>
<?= Form::open(['class'=>'layout-item stretch layout-column']) ?>
<?= $this->formRender() ?>
<div class="form-buttons layout-item fix">
<div class="loading-indicator-container">
<button
type="submit"
data-request="onSave"
data-hotkey="ctrl+s"
data-hotkey-mac="cmd+s"
data-load-indicator="Creating Template..."
class="btn btn-primary">
Create
</button>
<button
type="button"
data-request="onSave"
data-request-data="close:1"
data-hotkey="ctrl+enter"
data-hotkey-mac="cmd+enter"
data-load-indicator="Creating Template..."
class="btn btn-default">
Create and Close
</button>
<span class="btn-text">
or <a href="<?= Backend::url('system/emailtemplates') ?>">Cancel</a>
</span>
</div>
</div>
<?= Form::close() ?>
<?php else: ?>
<p class="flash-message static error"><?= e($this->fatalError) ?></p>
<p><a href="<?= Backend::url('system/emailtemplates') ?>" class="btn btn-default">Return to template list</a></p>
<?php endif ?>

View File

@ -0,0 +1,27 @@
<?php Block::put('breadcrumb') ?>
<ul>
<li><a href="<?= Backend::url('system/settings') ?>"><?= e(trans('system::lang.settings.menu_label')) ?></a></li>
<li><?= e($this->pageTitle) ?></li>
</ul>
<?php Block::endPut() ?>
<div class="control-tabs secondary" data-control="tab">
<ul class="nav nav-tabs">
<li class="active"><a href="#templates"><?= e(trans('system::lang.email_templates.templates')) ?></a></li>
<li><a href="#templates"><?= e(trans('system::lang.email_templates.layouts')) ?></a></li>
</ul>
<div class="tab-content">
<div class="tab-pane active">
<div class="list-preview">
<?= $this->listRender('templates') ?>
</div>
</div>
<div class="tab-pane">
<div class="list-preview">
<?= $this->listRender('layouts') ?>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,66 @@
<?php Block::put('breadcrumb') ?>
<ul>
<li><a href="<?= Backend::url('system/settings') ?>"><?= e(trans('system::lang.settings.menu_label')) ?></a></li>
<li><a href="<?= Backend::url('system/emailtemplates') ?>"><?= e(trans('system::lang.email_templates.menu_label')) ?></a></li>
<li><?= e($this->pageTitle) ?></li>
</ul>
<?php Block::endPut() ?>
<?php if (!$this->fatalError): ?>
<div class="scoreboard">
<div data-control="toolbar">
<div class="scoreboard-item title-value">
<h4><?= e(trans('system::lang.email_templates.template')) ?></h4>
<p><?= $formModel->code ?></p>
</div>
</div>
</div>
<?= Form::open(['class'=>'layout-item stretch layout-column']) ?>
<?= $this->formRender() ?>
<div class="form-buttons layout-item fix">
<div class="loading-indicator-container">
<button
type="submit"
data-request="onSave"
data-request-data="redirect:0"
data-hotkey="ctrl+s"
data-hotkey-mac="cmd+s"
data-load-indicator="Saving Template..."
class="btn btn-primary">
<u>S</u>ave
</button>
<button
type="button"
data-request="onSave"
data-request-data="close:1"
data-hotkey="ctrl+enter"
data-hotkey-mac="cmd+enter"
data-load-indicator="Saving Template..."
class="btn btn-default">
Save and Close
</button>
<button
type="button"
class="oc-icon-trash-o btn-icon danger pull-right"
data-request="onDelete"
data-load-indicator="Deleting Template..."
data-request-confirm="Do you really want to delete this template?">
</button>
<span class="btn-text">
or <a href="<?= Backend::url('system/emailtemplates') ?>">Cancel</a>
</span>
</div>
</div>
<?= Form::close() ?>
<?php else: ?>
<p class="flash-message static error"><?= e($this->fatalError) ?></p>
<p><a href="<?= Backend::url('system/emailtemplates') ?>" class="btn btn-default">Return to template list</a></p>
<?php endif ?>

View File

@ -11,19 +11,23 @@
<p><?= e(trans('system::lang.plugins.selected_amount', ['amount'=>count($checked)])) ?></p>
<div class="form-group">
<!-- Checkbox -->
<div class="checkbox custom-checkbox">
<input
type="checkbox"
name="disable"
value="1"
id="pluginDisable">
<label for="pluginDisable">
<?= e(trans('system::lang.plugins.disabled_label')) ?>
</label>
<p class="help-block"><?= e(trans('system::lang.plugins.disabled_help')) ?></p>
<div class="form-preview">
<div class="form-group">
<!-- Checkbox -->
<div class="checkbox custom-checkbox">
<input
type="checkbox"
name="disable"
value="1"
id="pluginDisable">
<label for="pluginDisable">
<?= e(trans('system::lang.plugins.disabled_label')) ?>
</label>
<p class="help-block"><?= e(trans('system::lang.plugins.disabled_help')) ?></p>
</div>
</div>
</div>
<?php foreach ($checked as $id): ?>

View File

@ -17,7 +17,7 @@ class DbDeferredBindings extends Migration
$table->string('slave_type')->index();
$table->string('slave_id')->index();
$table->string('session_key');
$table->boolean('bind')->default(true);
$table->boolean('is_bind')->default(true);
$table->timestamps();
});
}

View File

@ -21,7 +21,7 @@ class DbSystemFiles extends Migration
$table->string('field')->nullable()->index();
$table->string('attachment_id')->index()->nullable();
$table->string('attachment_type')->index()->nullable();
$table->boolean('public')->default(true);
$table->boolean('is_public')->default(true);
$table->integer('sort_order')->nullable();
$table->timestamps();
});

View File

@ -0,0 +1,31 @@
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class DbSystemEmailTemplates extends Migration
{
public function up()
{
Schema::create('system_email_templates', function(Blueprint $table)
{
$table->engine = 'InnoDB';
$table->increments('id');
$table->string('code')->nullable();
$table->string('subject')->nullable();
$table->text('description')->nullable();
$table->text('content_html')->nullable();
$table->text('content_text')->nullable();
$table->integer('layout_id')->index()->nullable();
$table->boolean('is_custom')->default(0);
$table->timestamps();
});
}
public function down()
{
Schema::drop('system_email_templates');
}
}

View File

@ -0,0 +1,30 @@
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class DbSystemEmailLayouts extends Migration
{
public function up()
{
Schema::create('system_email_layouts', 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->text('content_css')->nullable();
$table->boolean('is_locked')->default(0);
$table->timestamps();
});
}
public function down()
{
Schema::drop('system_email_layouts');
}
}

View File

@ -0,0 +1,21 @@
<?php namespace System\Database\Seeds;
use Seeder;
use Eloquent;
class DatabaseSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
Eloquent::unguard();
$this->call('System\Database\Seeds\SeedSetupEmailLayouts');
}
}

View File

@ -0,0 +1,79 @@
<?php namespace System\Database\Seeds;
use Seeder;
use System\Models\EmailLayout;
class SeedSetupEmailLayouts 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 }}
</style>
</head>
<body>
{{ message }}
</body>
</html>';
$text = '{{message}}';
EmailLayout::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 }}
</style>
</head>
<body>
{{ message }}
<hr />
<p>This is an automatic message. Please do not reply to it.</p>
</body>
</html>';
$text = '{{message}}
---
This is an automatic message. Please do not reply to it.
';
EmailLayout::create([
'is_locked' => true,
'name' => 'System',
'code' => 'system',
'content_html' => $html,
'content_css' => $css,
'content_text' => $text,
]);
}
}

View File

@ -75,6 +75,27 @@ return [
'sendmail_path' => 'Sendmail Path',
'sendmail_path_comment' => 'Please specify the path of the sendmail program.',
],
'email_templates' => [
'menu_label' => 'Email Templates',
'menu_description' => 'Modify the email templates that are sent to users and administrators.',
'new_template' => 'New Template',
'new_layout' => 'New Layout',
'template' => 'Template',
'templates' => 'Templates',
'menu_layouts_label' => 'Email Layouts',
'layout' => 'Layout',
'layouts' => 'Layouts',
'name' => 'Name',
'name_comment' => 'Unique name used to refer to this template',
'code' => 'Code',
'code_comment' => 'Unique code used to refer to this template',
'subject' => 'Subject',
'subject_comment' => 'Email message subject',
'description' => 'Description',
'content_html' => 'HTML',
'content_css' => 'CSS',
'content_text' => 'Plaintext',
],
'install' => [
'project_label' => 'Attach to Project',
'plugin_label' => 'Install Plugin',

View File

@ -0,0 +1,25 @@
<?php namespace System\Models;
use Model;
use System\Classes\ApplicationException;
class EmailLayout extends Model
{
/**
* @var string The database table used by the model.
*/
protected $table = 'system_email_layouts';
public $rules = [
'code' => 'required|unique:system_email_layouts',
'name' => 'required',
'content_html' => 'required',
];
public function beforeDelete()
{
if ($this->is_locked)
throw new ApplicationException('Cannot delete this template because it is locked');
}
}

View File

@ -0,0 +1,148 @@
<?php namespace System\Models;
use File;
use View;
use Model;
use October\Rain\Mail\MailParser;
use System\Classes\PluginManager;
class EmailTemplate extends Model
{
/**
* @var string The database table used by the model.
*/
protected $table = 'system_email_templates';
public $rules = [
'code' => 'required|unique:system_email_templates',
'subject' => 'required',
'description' => 'required',
'content_html' => 'required',
];
public $belongsTo = [
'layout' => ['System\Models\EmailLayout']
];
/**
* @var array Cache of registration callbacks.
*/
private static $callbacks = [];
protected static $registeredTemplates;
public static function syncAll()
{
$templates = self::make()->listRegisteredTemplates();
$dbTemplates = self::lists('is_custom', 'code');
$newTemplates = array_diff_key($templates, $dbTemplates);
/*
* Clean up non-customized templates
*/
foreach ($dbTemplates as $code => $is_custom) {
if ($is_custom)
continue;
if (!array_key_exists($code, $templates))
self::whereCode($code)->delete();
}
/*
* Create new templates
*/
if (count($newTemplates))
$categories = EmailLayout::lists('id', 'code');
foreach ($newTemplates as $code => $description) {
$sections = self::getTemplateSections($code);
$layoutCode = array_get($sections, 'settings.layout', 'default');
$template = self::make();
$template->code = $code;
$template->description = $description;
$template->is_custom = false;
$template->layout_id = isset($categories[$layoutCode]) ? $categories[$layoutCode] : null;
$template->forceSave();
}
}
public function afterFetch()
{
if (!$this->is_custom) {
$sections = self::getTemplateSections($this->code);
$this->content_html = $sections['html'];
$this->content_text = $sections['text'];
$this->subject = array_get($sections, 'settings.subject', 'No subject');
}
}
protected static function getTemplateSections($code)
{
return MailParser::parse(File::get(View::make($code)->getPath()));
}
//
// Registration
//
/**
* Loads registered email templates from modules and plugins
* @return void
*/
public function loadRegisteredTemplates()
{
foreach (static::$callbacks as $callback) {
$callback($this);
}
$plugins = PluginManager::instance()->getPlugins();
foreach ($plugins as $pluginId => $pluginObj) {
$templates = $pluginObj->registerEmailTemplates();
if (!is_array($templates))
continue;
$this->registerEmailTemplates($templates);
}
}
/**
* Returns a list of the registered templates.
* @return array
*/
public function listRegisteredTemplates()
{
if (self::$registeredTemplates === null)
$this->loadRegisteredTemplates();
return self::$registeredTemplates;
}
/**
* Registers a callback function that defines email templates.
* The callback function should register templates by calling the manager's
* registerEmailTemplates() function. Thi instance is passed to the
* callback function as an argument. Usage:
* <pre>
* EmailTemplate::registerCallback(function($template){
* $template->registerEmailTemplates([...]);
* });
* </pre>
* @param callable $callback A callable function.
*/
public static function registerCallback(callable $callback)
{
self::$callbacks[] = $callback;
}
/**
* Registers email views and manageable templates.
*/
public function registerEmailTemplates(array $definitions)
{
if (!static::$registeredTemplates)
static::$registeredTemplates = [];
static::$registeredTemplates = array_merge(static::$registeredTemplates, $definitions);
}
}

View File

@ -0,0 +1,13 @@
# ===================================
# Column Definitions
# ===================================
columns:
name:
label: Name
searchable: true
code:
label: Code
searchable: true

View File

@ -0,0 +1,43 @@
# ===================================
# Field Definitions
# ===================================
fields:
code:
label: system::lang.email_templates.code
comment: system::lang.email_templates.code_comment
span: left
context: create
name:
label: system::lang.email_templates.name
span: right
# context: create
# @todo Fix duplicate key problem
# name:
# label: system::lang.email_templates.name
# context: update
secondaryTabs:
fields:
content_html:
type: codeeditor
size: giant
tab: system::lang.email_templates.content_html
options:
language: html
content_css:
type: codeeditor
size: giant
tab: system::lang.email_templates.content_css
options:
language: css
content_text:
type: textarea
size: giant
tab: system::lang.email_templates.content_text

View File

@ -3,8 +3,8 @@
# ===================================
tabs:
fields:
send_mode:
label: system::lang.email.method
type: dropdown

View File

@ -0,0 +1,21 @@
# ===================================
# Column Definitions
# ===================================
columns:
code:
label: Code
searchable: true
subject:
label: Subject
searchable: true
description:
label: Description
searchable: true
layout:
relation: layout
select: @name

View File

@ -0,0 +1,46 @@
# ===================================
# Field Definitions
# ===================================
fields:
layout:
label: system::lang.email_templates.layout
type: relation
options:
emptyOption: -- No layout --
code:
label: system::lang.email_templates.code
comment: system::lang.email_templates.code_comment
span: left
context: create
subject:
label: system::lang.email_templates.subject
comment: system::lang.email_templates.subject_comment
span: right
# context: create
# @todo Fix duplicate key problem
# subject:
# label: system::lang.email_templates.subject
# context: update
description:
label: system::lang.email_templates.description
type: textarea
size: tiny
secondaryTabs:
fields:
content_html:
type: richeditor
size: huge
tab: system::lang.email_templates.content_html
content_text:
type: textarea
size: huge
tab: system::lang.email_templates.content_text

View File

@ -76,8 +76,8 @@ trait PropertyContainer
/**
* Returns a defined property value or default if one is not set.
* @param $name The property name to look for.
* @param $default A default value to return if no name is found.
* @param string $name The property name to look for.
* @param string $default A default value to return if no name is found.
* @return string The property value or the default specified.
*/
public function property($name, $default = null)