diff --git a/dev/App/User.js b/dev/App/User.js
index 72af43028..69cf3c4e3 100644
--- a/dev/App/User.js
+++ b/dev/App/User.js
@@ -51,6 +51,7 @@ import { NotificationUserStore } from 'Stores/User/Notification';
import { AccountUserStore } from 'Stores/User/Account';
import { ContactUserStore } from 'Stores/User/Contact';
import { IdentityUserStore } from 'Stores/User/Identity';
+import { TemplateUserStore } from 'Stores/User/Template';
import { FolderUserStore } from 'Stores/User/Folder';
import { PgpUserStore } from 'Stores/User/Pgp';
import { MessageUserStore } from 'Stores/User/Message';
@@ -64,6 +65,7 @@ import Remote from 'Remote/User/Fetch';
import { EmailModel } from 'Model/Email';
import { AccountModel } from 'Model/Account';
import { IdentityModel } from 'Model/Identity';
+import { TemplateModel } from 'Model/Template';
import { OpenPgpKeyModel } from 'Model/OpenPgpKey';
import { LoginUserScreen } from 'Screen/User/Login';
@@ -495,6 +497,24 @@ class AppUser extends AbstractApp {
});
}
+ templates() {
+ TemplateUserStore.templates.loading(true);
+
+ Remote.templates((iError, data) => {
+ TemplateUserStore.templates.loading(false);
+
+ if (!iError && isArray(data.Result.Templates)) {
+ delegateRunOnDestroy(TemplateUserStore.templates());
+
+ TemplateUserStore.templates(
+ data.Result.Templates.map(templateData =>
+ TemplateModel.reviveFromJson(templateData)
+ ).filter(v => v)
+ );
+ }
+ });
+ }
+
quota() {
Remote.quota((iError, data) => {
if (
diff --git a/dev/Common/Enums.js b/dev/Common/Enums.js
index 18dff93c9..eafe441df 100644
--- a/dev/Common/Enums.js
+++ b/dev/Common/Enums.js
@@ -21,6 +21,7 @@ export const Capa = {
UserBackground: 'USER_BACKGROUND',
Sieve: 'SIEVE',
AttachmentThumbnails: 'ATTACHMENT_THUMBNAILS',
+ Templates: 'TEMPLATES',
AutoLogout: 'AUTOLOGOUT',
AdditionalAccounts: 'ADDITIONAL_ACCOUNTS',
Identities: 'IDENTITIES'
diff --git a/dev/Model/Template.js b/dev/Model/Template.js
new file mode 100644
index 000000000..942749fdb
--- /dev/null
+++ b/dev/Model/Template.js
@@ -0,0 +1,23 @@
+import ko from 'ko';
+
+import { AbstractModel } from 'Knoin/AbstractModel';
+
+export class TemplateModel extends AbstractModel {
+ /**
+ * @param {string} id
+ * @param {string} name
+ * @param {string} body
+ */
+ constructor(id = '', name = '', body = '') {
+ super();
+
+ this.id = id;
+ this.name = name;
+ this.body = body;
+ this.populated = true;
+
+ this.deleteAccess = ko.observable(false);
+ }
+
+// static reviveFromJson(json) {}
+}
diff --git a/dev/Remote/User/Fetch.js b/dev/Remote/User/Fetch.js
index 9dd547c7d..533084b70 100644
--- a/dev/Remote/User/Fetch.js
+++ b/dev/Remote/User/Fetch.js
@@ -185,6 +185,47 @@ class RemoteUserFetch extends AbstractFetchRemote {
this.defaultRequest(fCallback, 'Filters', {});
}
+ /**
+ * @param {?Function} fCallback
+ */
+ templates(fCallback) {
+ this.defaultRequest(fCallback, 'Templates', {});
+ }
+
+ /**
+ * @param {Function} fCallback
+ * @param {string} sID
+ */
+ templateGetById(fCallback, sID) {
+ this.defaultRequest(fCallback, 'TemplateGetByID', {
+ ID: sID
+ });
+ }
+
+ /**
+ * @param {Function} fCallback
+ * @param {string} sID
+ */
+ templateDelete(fCallback, sID) {
+ this.defaultRequest(fCallback, 'TemplateDelete', {
+ IdToDelete: sID
+ });
+ }
+
+ /**
+ * @param {Function} fCallback
+ * @param {string} sID
+ * @param {string} sName
+ * @param {string} sBody
+ */
+ templateSetup(fCallback, sID, sName, sBody) {
+ this.defaultRequest(fCallback, 'TemplateSetup', {
+ ID: sID,
+ Name: sName,
+ Body: sBody
+ });
+ }
+
/**
* @param {Function} fCallback
* @param {object} params
diff --git a/dev/Screen/User/Settings.js b/dev/Screen/User/Settings.js
index e1bad82b1..70cce520f 100644
--- a/dev/Screen/User/Settings.js
+++ b/dev/Screen/User/Settings.js
@@ -14,6 +14,7 @@ import { ContactsUserSettings } from 'Settings/User/Contacts';
import { AccountsUserSettings } from 'Settings/User/Accounts';
import { FiltersUserSettings } from 'Settings/User/Filters';
import { SecurityUserSettings } from 'Settings/User/Security';
+import { TemplatesUserSettings } from 'Settings/User/Templates';
import { FoldersUserSettings } from 'Settings/User/Folders';
import { ThemesUserSettings } from 'Settings/User/Themes';
import { OpenPgpUserSettings } from 'Settings/User/OpenPgp';
@@ -67,6 +68,15 @@ export class SettingsUserScreen extends AbstractSettingsScreen {
settingsAddViewModel(SecurityUserSettings, 'SettingsSecurity', 'SETTINGS_LABELS/LABEL_SECURITY_NAME', 'security');
}
+ if (Settings.capa(Capa.Templates)) {
+ settingsAddViewModel(
+ TemplatesUserSettings,
+ 'SettingsTemplates',
+ 'SETTINGS_LABELS/LABEL_TEMPLATES_NAME',
+ 'templates'
+ );
+ }
+
settingsAddViewModel(FoldersUserSettings, 'SettingsFolders', 'SETTINGS_LABELS/LABEL_FOLDERS_NAME', 'folders');
if (Settings.capa(Capa.Themes)) {
diff --git a/dev/Settings/Admin/General.js b/dev/Settings/Admin/General.js
index f3d1c8b58..26e1d8e29 100644
--- a/dev/Settings/Admin/General.js
+++ b/dev/Settings/Admin/General.js
@@ -48,6 +48,7 @@ export class GeneralAdminSettings {
capaAdditionalAccounts: Settings.capa(Capa.AdditionalAccounts),
capaIdentities: Settings.capa(Capa.Identities),
capaAttachmentThumbnails: Settings.capa(Capa.AttachmentThumbnails),
+ capaTemplates: Settings.capa(Capa.Templates),
dataFolderAccess: false
});
@@ -125,6 +126,8 @@ export class GeneralAdminSettings {
capaIdentities: fSaveBoolHelper('CapaIdentities'),
+ capaTemplates: fSaveBoolHelper('CapaTemplates'),
+
capaAttachmentThumbnails: fSaveBoolHelper('CapaAttachmentThumbnails'),
capaThemes: fSaveBoolHelper('CapaThemes'),
diff --git a/dev/Settings/User/Templates.js b/dev/Settings/User/Templates.js
new file mode 100644
index 000000000..ca3173509
--- /dev/null
+++ b/dev/Settings/User/Templates.js
@@ -0,0 +1,62 @@
+import ko from 'ko';
+
+import { i18n } from 'Common/Translator';
+
+import { TemplateUserStore } from 'Stores/User/Template';
+import Remote from 'Remote/User/Fetch';
+
+import { showScreenPopup } from 'Knoin/Knoin';
+
+import { TemplatePopupView } from 'View/Popup/Template';
+import { addComputablesTo } from 'Common/Utils';
+
+export class TemplatesUserSettings {
+ constructor() {
+ this.templates = TemplateUserStore.templates;
+
+ addComputablesTo(this, {
+ processText: () => TemplateUserStore.templates.loading() ? i18n('SETTINGS_TEMPLETS/LOADING_PROCESS') : '',
+
+ visibility: () => this.processText() ? 'visible' : 'hidden'
+ });
+
+ this.templateForDeletion = ko.observable(null).deleteAccessHelper();
+ }
+
+ addNewTemplate() {
+ showScreenPopup(TemplatePopupView);
+ }
+
+ editTemplate(oTemplateItem) {
+ if (oTemplateItem) {
+ showScreenPopup(TemplatePopupView, [oTemplateItem]);
+ }
+ }
+
+ deleteTemplate(templateToRemove) {
+ if (templateToRemove && templateToRemove.deleteAccess()) {
+ this.templateForDeletion(null);
+
+ if (templateToRemove) {
+ this.templates.remove((template) => templateToRemove === template);
+
+ Remote.templateDelete(() => {
+ this.reloadTemplates();
+ }, templateToRemove.id);
+ }
+ }
+ }
+
+ reloadTemplates() {
+ rl.app.templates();
+ }
+
+ onBuild(oDom) {
+ oDom.addEventListener('click', event => {
+ const el = event.target.closestWithin('td.e-action', oDom);
+ el && ko.dataFor(el) && this.editTemplate(ko.dataFor(el));
+ });
+
+ this.reloadTemplates();
+ }
+}
diff --git a/dev/Stores/User/Template.js b/dev/Stores/User/Template.js
new file mode 100644
index 000000000..13204eb74
--- /dev/null
+++ b/dev/Stores/User/Template.js
@@ -0,0 +1,28 @@
+import ko from 'ko';
+
+// import Remote from 'Remote/User/Fetch';
+
+export const TemplateUserStore = new class {
+ constructor() {
+ this.templates = ko.observableArray();
+ this.templates.loading = ko.observable(false).extend({ debounce: 100 });
+
+ this.templatesNames = ko.observableArray().extend({ debounce: 1000 });
+ this.templatesNames.skipFirst = true;
+
+ this.templates.subscribe((list) => {
+ this.templatesNames(list.map(item => (item ? item.name : null)).filter(v => v));
+ });
+
+ // this.templatesNames.subscribe((aList) => {
+ // if (this.templatesNames.skipFirst)
+ // {
+ // this.templatesNames.skipFirst = false;
+ // }
+ // else if (aList && aList.length)
+ // {
+ // Remote.templatesSortOrder(null, aList);
+ // }
+ // });
+ }
+};
diff --git a/dev/Styles/@Main.less b/dev/Styles/@Main.less
index 5f5b5ffe7..bb64f98c0 100644
--- a/dev/Styles/@Main.less
+++ b/dev/Styles/@Main.less
@@ -33,6 +33,7 @@
@import "User/Shortcuts.less";
@import "User/FolderList.less";
@import "User/Filter.less";
+@import "User/Template.less";
@import "User/OpenPgpKey.less";
@import "User/Identity.less";
@import "User/AdvancedSearch.less";
@@ -46,6 +47,7 @@
@import "User/Settings.less";
@import "User/SettingsGeneral.less";
@import "User/SettingsAccounts.less";
+@import "User/SettingsTemplates.less";
@import "User/SettingsOpenPGP.less";
@import "User/SettingsFolders.less";
@import "User/SettingsThemes.less";
diff --git a/dev/Styles/User/SettingsTemplates.less b/dev/Styles/User/SettingsTemplates.less
new file mode 100644
index 000000000..9c313a2e6
--- /dev/null
+++ b/dev/Styles/User/SettingsTemplates.less
@@ -0,0 +1,21 @@
+
+.b-settings-templates {
+
+ td + td {
+ width: 150px;
+ }
+ td + td + td {
+ width: 1%;
+ }
+
+ .template-name {
+ display: inline-block;
+ line-height: 22px;
+ word-break: break-all;
+ }
+
+ .delete-template {
+ cursor: pointer;
+ opacity: 0.5;
+ }
+}
diff --git a/dev/Styles/User/Template.less b/dev/Styles/User/Template.less
new file mode 100644
index 000000000..47dc0e3f7
--- /dev/null
+++ b/dev/Styles/User/Template.less
@@ -0,0 +1,10 @@
+.b-template-add-content {
+
+ &.modal {
+ max-width: 750px;
+ }
+
+ .e-template-place {
+ height: 300px;
+ }
+}
diff --git a/dev/View/Popup/Template.js b/dev/View/Popup/Template.js
new file mode 100644
index 000000000..60758a808
--- /dev/null
+++ b/dev/View/Popup/Template.js
@@ -0,0 +1,148 @@
+import { getNotification } from 'Common/Translator';
+import { HtmlEditor } from 'Common/Html';
+
+import Remote from 'Remote/User/Fetch';
+
+import { decorateKoCommands } from 'Knoin/Knoin';
+import { AbstractViewPopup } from 'Knoin/AbstractViews';
+import { TemplateModel } from 'Model/Template';
+
+class TemplatePopupView extends AbstractViewPopup {
+ constructor() {
+ super('Template');
+
+ this.editor = null;
+
+ this.addObservables({
+ signatureDom: null,
+
+ id: '',
+
+ name: '',
+ nameError: false,
+
+ body: '',
+ bodyLoading: false,
+ bodyError: false,
+
+ submitRequest: false,
+ submitError: ''
+ });
+
+ this.name.subscribe(() => this.nameError(false));
+
+ this.body.subscribe(() => this.bodyError(false));
+
+ decorateKoCommands(this, {
+ addTemplateCommand: self => !self.submitRequest()
+ });
+ }
+
+ addTemplateCommand() {
+ this.populateBodyFromEditor();
+
+ this.nameError(!this.name().trim());
+ this.bodyError(!this.body().trim() || ':HTML:' === this.body().trim());
+
+ if (this.nameError() || this.bodyError()) {
+ return false;
+ }
+
+ this.submitRequest(true);
+
+ Remote.templateSetup(
+ iError => {
+ this.submitRequest(false);
+ if (iError) {
+ this.submitError(getNotification(iError));
+ } else {
+ rl.app.templates();
+ this.cancelCommand();
+ }
+ },
+ this.id(),
+ this.name(),
+ this.body()
+ );
+
+ return true;
+ }
+
+ clearPopup() {
+ this.id('');
+
+ this.name('');
+ this.nameError(false);
+
+ this.body('');
+ this.bodyLoading(false);
+ this.bodyError(false);
+
+ this.submitRequest(false);
+ this.submitError('');
+
+ if (this.editor) {
+ this.editor.setPlain('');
+ }
+ }
+
+ populateBodyFromEditor() {
+ if (this.editor) {
+ this.body(this.editor.getDataWithHtmlMark());
+ }
+ }
+
+ editorSetBody(sBody) {
+ if (!this.editor && this.signatureDom()) {
+ this.editor = new HtmlEditor(
+ this.signatureDom(),
+ () => this.populateBodyFromEditor(),
+ () => this.editor.setHtmlOrPlain(sBody)
+ );
+ } else {
+ this.editor.setHtmlOrPlain(sBody);
+ }
+ }
+
+ onShow(template) {
+ this.clearPopup();
+
+ if (template && template.id) {
+ this.id(template.id);
+ this.name(template.name);
+ this.body(template.body);
+
+ if (template.populated) {
+ this.editorSetBody(this.body());
+ } else {
+ this.bodyLoading(true);
+ this.bodyError(false);
+
+ Remote.templateGetById((iError, data) => {
+ this.bodyLoading(false);
+
+ if (
+ !iError &&
+ TemplateModel.validJson(data.Result) &&
+ null != data.Result.Body
+ ) {
+ template.body = data.Result.Body;
+ template.populated = true;
+
+ this.body(template.body);
+ this.bodyError(false);
+ } else {
+ this.body('');
+ this.bodyError(true);
+ }
+
+ this.editorSetBody(this.body());
+ }, this.id());
+ }
+ } else {
+ this.editorSetBody('');
+ }
+ }
+}
+
+export { TemplatePopupView, TemplatePopupView as default };
diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions.php
index 91c3ea399..849b6480f 100644
--- a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions.php
+++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions.php
@@ -1932,6 +1932,10 @@ class Actions
$aResult[] = Enumerations\Capa::IDENTITIES;
}
+ if ($oConfig->Get('capa', 'x-templates', true)) {
+ $aResult[] = Enumerations\Capa::TEMPLATES;
+ }
+
if ($oConfig->Get('webmail', 'allow_themes', false)) {
$aResult[] = Enumerations\Capa::THEMES;
}
diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Admin.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Admin.php
index bd50d5878..755192ce6 100644
--- a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Admin.php
+++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Admin.php
@@ -83,6 +83,9 @@ trait Admin
case Capa::IDENTITIES:
$this->setConfigFromParams($oConfig, $sParamName, 'webmail', 'allow_additional_identities', 'bool');
break;
+ case Capa::TEMPLATES:
+ $this->setConfigFromParams($oConfig, $sParamName, 'capa', 'x-templates', 'bool');
+ break;
case Capa::ATTACHMENT_THUMBNAILS:
$this->setConfigFromParams($oConfig, $sParamName, 'interface', 'show_attachment_thumbnail', 'bool');
break;
@@ -147,6 +150,7 @@ trait Admin
$this->setCapaFromParams($oConfig, 'CapaAdditionalAccounts', Capa::ADDITIONAL_ACCOUNTS);
$this->setCapaFromParams($oConfig, 'CapaIdentities', Capa::IDENTITIES);
+ $this->setCapaFromParams($oConfig, 'CapaTemplates', Capa::TEMPLATES);
$this->setCapaFromParams($oConfig, 'CapaOpenPGP', Capa::OPEN_PGP);
$this->setCapaFromParams($oConfig, 'CapaThemes', Capa::THEMES);
$this->setCapaFromParams($oConfig, 'CapaUserBackground', Capa::USER_BACKGROUND);
diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Templates.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Templates.php
new file mode 100644
index 000000000..f257b3a58
--- /dev/null
+++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Templates.php
@@ -0,0 +1,215 @@
+getAccountFromToken();
+
+ if (!$this->GetCapa(false, Capa::TEMPLATES, $oAccount))
+ {
+ return $this->FalseResponse(__FUNCTION__);
+ }
+
+ $oTemplate = new Template();
+ if (!$oTemplate->FromJSON($this->GetActionParams(), true))
+ {
+ throw new ClientException(Notifications::InvalidInputArgument);
+ }
+
+ if ('' === $oTemplate->Id())
+ {
+ $oTemplate->GenerateID();
+ }
+
+ $aTemplatesForSave = array();
+ $aTemplates = $this->GetTemplates($oAccount);
+
+
+ foreach ($aTemplates as $oItem)
+ {
+ if ($oItem && $oItem->Id() !== $oTemplate->Id())
+ {
+ $aTemplatesForSave[] = $oItem;
+ }
+ }
+
+ $aTemplatesForSave[] = $oTemplate;
+
+ return $this->DefaultResponse(__FUNCTION__, $this->SetTemplates($oAccount, $aTemplatesForSave));
+ }
+
+ /**
+ * @throws \MailSo\Base\Exceptions\Exception
+ */
+ public function DoTemplateDelete() : array
+ {
+ $oAccount = $this->getAccountFromToken();
+
+ if (!$this->GetCapa(false, Capa::TEMPLATES, $oAccount))
+ {
+ return $this->FalseResponse(__FUNCTION__);
+ }
+
+ $sId = \trim($this->GetActionParam('IdToDelete', ''));
+ if (empty($sId))
+ {
+ throw new ClientException(Notifications::UnknownError);
+ }
+
+ $aNew = array();
+ $aTemplates = $this->GetTemplates($oAccount);
+ foreach ($aTemplates as $oItem)
+ {
+ if ($oItem && $sId !== $oItem->Id())
+ {
+ $aNew[] = $oItem;
+ }
+ }
+
+ return $this->DefaultResponse(__FUNCTION__, $this->SetTemplates($oAccount, $aNew));
+ }
+
+ /**
+ * @throws \MailSo\Base\Exceptions\Exception
+ */
+ public function DoTemplateGetByID() : array
+ {
+ $oAccount = $this->getAccountFromToken();
+
+ if (!$this->GetCapa(false, Capa::TEMPLATES, $oAccount))
+ {
+ return $this->FalseResponse(__FUNCTION__);
+ }
+
+ $sId = \trim($this->GetActionParam('ID', ''));
+ if (empty($sId))
+ {
+ throw new ClientException(Notifications::UnknownError);
+ }
+
+ $oTemplate = false;
+ $aTemplates = $this->GetTemplates($oAccount);
+
+ foreach ($aTemplates as $oItem)
+ {
+ if ($oItem && $sId === $oItem->Id())
+ {
+ $oTemplate = $oItem;
+ break;
+ }
+ }
+
+ $oTemplate->SetPopulateAlways(true);
+ return $this->DefaultResponse(__FUNCTION__, $oTemplate);
+ }
+
+ /**
+ * @throws \MailSo\Base\Exceptions\Exception
+ */
+ public function DoTemplates() : array
+ {
+ $oAccount = $this->getAccountFromToken();
+
+ if (!$this->GetCapa(false, Capa::TEMPLATES, $oAccount))
+ {
+ return $this->FalseResponse(__FUNCTION__);
+ }
+
+ return $this->DefaultResponse(__FUNCTION__, array(
+ 'Templates' => $this->GetTemplates($oAccount)
+ ));
+ }
+
+ private function GetTemplates(?Account $oAccount) : array
+ {
+ $aTemplates = array();
+ if ($oAccount)
+ {
+ $aData = array();
+
+ $sData = $this->StorageProvider(true)->Get($oAccount,
+ \RainLoop\Providers\Storage\Enumerations\StorageType::CONFIG,
+ 'templates'
+ );
+
+ if ('' !== $sData && '[' === \substr($sData, 0, 1))
+ {
+ $aData = \json_decode($sData, true);
+ }
+
+ if (\is_array($aData) && 0 < \count($aData))
+ {
+ foreach ($aData as $aItem)
+ {
+ $oItem = new Template();
+ $oItem->FromJSON($aItem);
+
+ if ($oItem && $oItem->Validate())
+ {
+ \array_push($aTemplates, $oItem);
+ }
+ }
+ }
+
+ if (1 < \count($aTemplates))
+ {
+ $sOrder = $this->StorageProvider()->Get($oAccount,
+ \RainLoop\Providers\Storage\Enumerations\StorageType::CONFIG,
+ 'templates_order'
+ );
+
+ $aOrder = empty($sOrder) ? array() : \json_decode($sOrder, true);
+ if (\is_array($aOrder) && 1 < \count($aOrder))
+ {
+ \usort($aTemplates, function ($a, $b) use ($aOrder) {
+ return \array_search($a->Id(), $aOrder) < \array_search($b->Id(), $aOrder) ? -1 : 1;
+ });
+ }
+ }
+ }
+
+ return $aTemplates;
+ }
+
+ private function GetTemplateByID(Account $oAccount, string $sID) : ?\RainLoop\Model\Identity
+ {
+ $aTemplates = $this->GetTemplates($oAccount);
+ foreach ($aTemplates as $oIdentity)
+ {
+ if ($oIdentity && $sID === $oIdentity->Id())
+ {
+ return $oIdentity;
+ }
+ }
+
+ return isset($aTemplates[0]) ? $aTemplates[0] : null;
+ }
+
+ private function SetTemplates(Account $oAccount, array $aTemplates = array()) : bool
+ {
+ $aResult = array();
+ foreach ($aTemplates as $oItem)
+ {
+ $aResult[] = $oItem->ToSimpleJSON();
+ }
+
+ return $this->StorageProvider(true)->Put($oAccount,
+ \RainLoop\Providers\Storage\Enumerations\StorageType::CONFIG,
+ 'templates',
+ \json_encode($aResult)
+ );
+ }
+}
diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/User.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/User.php
index 68ee0e6a4..e2ae3c639 100644
--- a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/User.php
+++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/User.php
@@ -14,6 +14,7 @@ trait User
use Filters;
use Folders;
use Messages;
+ use Templates;
/**
* @throws \MailSo\Base\Exceptions\Exception
diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Config/Application.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Config/Application.php
index 4ec63078d..e50cf2e83 100644
--- a/snappymail/v/0.0.0/app/libraries/RainLoop/Config/Application.php
+++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Config/Application.php
@@ -194,6 +194,7 @@ class Application extends \RainLoop\Config\AbstractConfig
'reload' => array(true),
'search' => array(true),
'search_adv' => array(true),
+ 'x-templates' => array(false),
'dangerous_actions' => array(true),
'message_actions' => array(true),
'messagelist_actions' => array(true),
diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Model/Template.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Model/Template.php
new file mode 100644
index 000000000..331414d03
--- /dev/null
+++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Model/Template.php
@@ -0,0 +1,106 @@
+sId = $sId;
+ $this->sName = $sName;
+ $this->sBody = $sBody;
+ $this->bPopulateAlways = false;
+ }
+
+ public function Id() : string
+ {
+ return $this->sId;
+ }
+
+ public function Name() : string
+ {
+ return $this->sName;
+ }
+
+ public function Body() : string
+ {
+ return $this->sBody;
+ }
+
+ public function SetPopulateAlways(bool $bPopulateAlways)
+ {
+ $this->bPopulateAlways = $bPopulateAlways;
+ }
+
+ public function FromJSON(array $aData, bool $bJson = false) : bool
+ {
+ if (isset($aData['ID'], $aData['Name'], $aData['Body']))
+ {
+ $this->sId = $aData['ID'];
+ $this->sName = $aData['Name'];
+ $this->sBody = $aData['Body'];
+
+ return true;
+ }
+
+ return false;
+ }
+
+ public function ToSimpleJSON() : array
+ {
+ return array(
+ 'ID' => $this->Id(),
+ 'Name' => $this->Name(),
+ 'Body' => $this->Body()
+ );
+ }
+
+ public function jsonSerialize()
+ {
+ $sBody = $this->Body();
+ $bPopulated = true;
+ if ($bPopulated && !$this->bPopulateAlways) {
+ if (1024 * 5 < \strlen($sBody) || true) {
+ $bPopulated = false;
+ $sBody = '';
+ }
+ }
+ return array(
+ '@Object' => 'Object/Template',
+ 'id' => $this->Id(),
+ 'name' => $this->Name(),
+ 'body' => $sBody,
+ 'populated' => $bPopulated
+ );
+ }
+
+ public function GenerateID() : bool
+ {
+ return $this->sId = \MailSo\Base\Utils::Md5Rand();
+ }
+
+ public function Validate() : bool
+ {
+ return 0 < \strlen($this->sBody);
+ }
+}
diff --git a/snappymail/v/0.0.0/app/localization/de-DE/admin.json b/snappymail/v/0.0.0/app/localization/de-DE/admin.json
index ad620c8ac..18510391b 100644
--- a/snappymail/v/0.0.0/app/localization/de-DE/admin.json
+++ b/snappymail/v/0.0.0/app/localization/de-DE/admin.json
@@ -32,6 +32,7 @@
"LABEL_ATTACHMENT_SIZE_LIMIT": "Größenlimit für Anhänge",
"LABEL_ALLOW_ADDITIONAL_ACCOUNTS": "Zusätzliche Konten erlauben",
"LABEL_ALLOW_IDENTITIES": "Mehrere Identitäten erlauben",
+ "LABEL_ALLOW_TEMPLATES": "Vorlagen erlauben",
"ALERT_DATA_ACCESS": "Data folder is accessible. Please configure your web server to hide the data folder from external access. Read more here:",
"ALERT_WARNING": "Warnung!",
"HTML_ALERT_WEAK_PASSWORD": "Sie verwenden das Standard-Admin-Passwort.\n
\nBitte ändern<\/a><\/strong> Sie\naus Sicherheitsgründen das Passwort jetzt.\n"
diff --git a/snappymail/v/0.0.0/app/localization/de-DE/user.json b/snappymail/v/0.0.0/app/localization/de-DE/user.json
index f7558aa27..08775b6cd 100644
--- a/snappymail/v/0.0.0/app/localization/de-DE/user.json
+++ b/snappymail/v/0.0.0/app/localization/de-DE/user.json
@@ -355,6 +355,7 @@
"LABEL_ACCOUNTS_NAME": "Konten",
"LABEL_IDENTITIES_NAME": "Identitäten",
"LABEL_FILTERS_NAME": "Filter",
+ "LABEL_TEMPLATES_NAME": "Vorlagen",
"LABEL_SECURITY_NAME": "Sicherheit",
"LABEL_THEMES_NAME": "Themen"
},
diff --git a/snappymail/v/0.0.0/app/localization/en/admin.json b/snappymail/v/0.0.0/app/localization/en/admin.json
index c51e22066..977102bef 100644
--- a/snappymail/v/0.0.0/app/localization/en/admin.json
+++ b/snappymail/v/0.0.0/app/localization/en/admin.json
@@ -32,6 +32,7 @@
"LABEL_ATTACHMENT_SIZE_LIMIT": "Attachment size limit",
"LABEL_ALLOW_ADDITIONAL_ACCOUNTS": "Allow additional accounts",
"LABEL_ALLOW_IDENTITIES": "Allow multiple identities",
+ "LABEL_ALLOW_TEMPLATES": "Allow templates",
"ALERT_DATA_ACCESS": "Data folder is accessible. Please configure your web server to hide the data folder from external access. Read more here:",
"ALERT_WARNING": "Warning!",
"HTML_ALERT_WEAK_PASSWORD": "You are using the default admin password.\n
\nFor security reasons please\nchange<\/a><\/strong>\npassword to something else now.\n"
diff --git a/snappymail/v/0.0.0/app/localization/en/user.json b/snappymail/v/0.0.0/app/localization/en/user.json
index 6f77e0c8f..cd089d89f 100644
--- a/snappymail/v/0.0.0/app/localization/en/user.json
+++ b/snappymail/v/0.0.0/app/localization/en/user.json
@@ -355,6 +355,7 @@
"LABEL_ACCOUNTS_NAME": "Accounts",
"LABEL_IDENTITIES_NAME": "Identities",
"LABEL_FILTERS_NAME": "Filters",
+ "LABEL_TEMPLATES_NAME": "Templates",
"LABEL_SECURITY_NAME": "Security",
"LABEL_THEMES_NAME": "Themes"
},
diff --git a/snappymail/v/0.0.0/app/localization/es-ES/admin.json b/snappymail/v/0.0.0/app/localization/es-ES/admin.json
index 2d0f954cc..447ba4719 100644
--- a/snappymail/v/0.0.0/app/localization/es-ES/admin.json
+++ b/snappymail/v/0.0.0/app/localization/es-ES/admin.json
@@ -32,6 +32,7 @@
"LABEL_ATTACHMENT_SIZE_LIMIT": "Tamaño máximo para adjuntos",
"LABEL_ALLOW_ADDITIONAL_ACCOUNTS": "Permitir cuentas adicionales",
"LABEL_ALLOW_IDENTITIES": "Permitir múltiples identidades",
+ "LABEL_ALLOW_TEMPLATES": "Permitir plantillas",
"ALERT_DATA_ACCESS": "Data folder is accessible. Please configure your web server to hide the data folder from external access. Read more here:",
"ALERT_WARNING": "¡Atención!",
"HTML_ALERT_WEAK_PASSWORD": "Estas utilizando la contraseña por defecto.\n
\nDebido a razones de seguridad, debes\ncambiar<\/a><\/strong>\ntu contraseña inmediatamente.\n"
diff --git a/snappymail/v/0.0.0/app/localization/es-ES/user.json b/snappymail/v/0.0.0/app/localization/es-ES/user.json
index 4b5c242ab..51917baad 100644
--- a/snappymail/v/0.0.0/app/localization/es-ES/user.json
+++ b/snappymail/v/0.0.0/app/localization/es-ES/user.json
@@ -355,6 +355,7 @@
"LABEL_ACCOUNTS_NAME": "Cuentas",
"LABEL_IDENTITIES_NAME": "Identidades",
"LABEL_FILTERS_NAME": "Filtros",
+ "LABEL_TEMPLATES_NAME": "Plantillas",
"LABEL_SECURITY_NAME": "Seguridad",
"LABEL_THEMES_NAME": "Temas"
},
diff --git a/snappymail/v/0.0.0/app/localization/fr-FR/admin.json b/snappymail/v/0.0.0/app/localization/fr-FR/admin.json
index 3ef67f1bf..d05c9d47c 100644
--- a/snappymail/v/0.0.0/app/localization/fr-FR/admin.json
+++ b/snappymail/v/0.0.0/app/localization/fr-FR/admin.json
@@ -32,6 +32,7 @@
"LABEL_ATTACHMENT_SIZE_LIMIT": "Taille limite des pièces jointes",
"LABEL_ALLOW_ADDITIONAL_ACCOUNTS": "Autoriser les comptes supplémentaires",
"LABEL_ALLOW_IDENTITIES": "Autoriser les identités multiples",
+ "LABEL_ALLOW_TEMPLATES": "Autoriser les modèles",
"ALERT_DATA_ACCESS": "Data folder is accessible. Please configure your web server to hide the data folder from external access. Read more here:",
"ALERT_WARNING": "ATTENTION !",
"HTML_ALERT_WEAK_PASSWORD": "Vous utilisez le mot de passe administrateur par défaut.\n
\nPour des raisons de sécurité, veuillez\nchanger le mot de passe <\/a><\/strong>\ndès maintenant.\n"
diff --git a/snappymail/v/0.0.0/app/localization/fr-FR/user.json b/snappymail/v/0.0.0/app/localization/fr-FR/user.json
index d1fbd2051..dcedeb78b 100644
--- a/snappymail/v/0.0.0/app/localization/fr-FR/user.json
+++ b/snappymail/v/0.0.0/app/localization/fr-FR/user.json
@@ -355,6 +355,7 @@
"LABEL_ACCOUNTS_NAME": "Comptes",
"LABEL_IDENTITIES_NAME": "Identités",
"LABEL_FILTERS_NAME": "Filtres",
+ "LABEL_TEMPLATES_NAME": "Modèles",
"LABEL_SECURITY_NAME": "Sécurité",
"LABEL_THEMES_NAME": "Thèmes"
},
diff --git a/snappymail/v/0.0.0/app/localization/hu-HU/admin.json b/snappymail/v/0.0.0/app/localization/hu-HU/admin.json
index 50f1839d1..6760f59df 100644
--- a/snappymail/v/0.0.0/app/localization/hu-HU/admin.json
+++ b/snappymail/v/0.0.0/app/localization/hu-HU/admin.json
@@ -32,6 +32,7 @@
"LABEL_ATTACHMENT_SIZE_LIMIT": "Melléklet méret korlát",
"LABEL_ALLOW_ADDITIONAL_ACCOUNTS": "További fiókok engedélyezése",
"LABEL_ALLOW_IDENTITIES": "Több identitás engedélyezése",
+ "LABEL_ALLOW_TEMPLATES": "Sablonok engedélyezése",
"ALERT_DATA_ACCESS": "A RainLoop adatmappája hozzáférhető. Kérlek állítsd be úgy a webszervert, hogy az adatmappát rejtse el a külső hozzáférés elől. További tudnivalók itt:",
"ALERT_WARNING": "Figyelmeztetés!",
"HTML_ALERT_WEAK_PASSWORD": "Az alapértelmezett admin jelszót használod\n
\nBiztonsági okokból kérlek\nváltoztasd meg<\/a><\/strong>\na jelszót valami másra!\n"
diff --git a/snappymail/v/0.0.0/app/localization/hu-HU/user.json b/snappymail/v/0.0.0/app/localization/hu-HU/user.json
index bf4006354..9d5456563 100644
--- a/snappymail/v/0.0.0/app/localization/hu-HU/user.json
+++ b/snappymail/v/0.0.0/app/localization/hu-HU/user.json
@@ -355,6 +355,7 @@
"LABEL_ACCOUNTS_NAME": "Fiókok",
"LABEL_IDENTITIES_NAME": "Identitások",
"LABEL_FILTERS_NAME": "Szűrők",
+ "LABEL_TEMPLATES_NAME": "Sablonok",
"LABEL_SECURITY_NAME": "Biztonság",
"LABEL_THEMES_NAME": "Témák"
},
diff --git a/snappymail/v/0.0.0/app/localization/nl-NL/admin.json b/snappymail/v/0.0.0/app/localization/nl-NL/admin.json
index 2e5b4aeb6..373012a93 100644
--- a/snappymail/v/0.0.0/app/localization/nl-NL/admin.json
+++ b/snappymail/v/0.0.0/app/localization/nl-NL/admin.json
@@ -32,6 +32,7 @@
"LABEL_ATTACHMENT_SIZE_LIMIT": "Maximale bijlage grootte",
"LABEL_ALLOW_ADDITIONAL_ACCOUNTS": "Sta extra accounts toe",
"LABEL_ALLOW_IDENTITIES": "Meerdere identiteiten toestaan",
+ "LABEL_ALLOW_TEMPLATES": "Sta templates toe",
"ALERT_DATA_ACCESS": "Data folder is accessible. Please configure your web server to hide the data folder from external access. Read more here:",
"ALERT_WARNING": "Waarschuwing!",
"HTML_ALERT_WEAK_PASSWORD": "U gebruikt het standaard beheer wachtwoord.\n
\nWijzig<\/a><\/strong>\na.u.b. voor uw veiligheid direct het wachtwoord.\n"
diff --git a/snappymail/v/0.0.0/app/localization/nl-NL/user.json b/snappymail/v/0.0.0/app/localization/nl-NL/user.json
index 38c28daa4..75998e2e3 100644
--- a/snappymail/v/0.0.0/app/localization/nl-NL/user.json
+++ b/snappymail/v/0.0.0/app/localization/nl-NL/user.json
@@ -355,6 +355,7 @@
"LABEL_ACCOUNTS_NAME": "Accounts",
"LABEL_IDENTITIES_NAME": "Identiteiten",
"LABEL_FILTERS_NAME": "Filters",
+ "LABEL_TEMPLATES_NAME": "Templates",
"LABEL_SECURITY_NAME": "Beveiliging",
"LABEL_THEMES_NAME": "Thema's"
},
diff --git a/snappymail/v/0.0.0/app/localization/sv-SE/admin.json b/snappymail/v/0.0.0/app/localization/sv-SE/admin.json
index 869b09111..f8811f357 100644
--- a/snappymail/v/0.0.0/app/localization/sv-SE/admin.json
+++ b/snappymail/v/0.0.0/app/localization/sv-SE/admin.json
@@ -32,6 +32,7 @@
"LABEL_ATTACHMENT_SIZE_LIMIT": "Bilagor storlek",
"LABEL_ALLOW_ADDITIONAL_ACCOUNTS": "Tillåt ytterligare konton",
"LABEL_ALLOW_IDENTITIES": "Tillåt multiidentiteter",
+ "LABEL_ALLOW_TEMPLATES": "Tillåt mallar",
"ALERT_DATA_ACCESS": "RainLoops datamapp är åtkomstbar. Var vänlig konfigurera din webb-server för att förhindra extern åtkomst och synlighet. Läs mer här: ",
"ALERT_WARNING": "Varning!",
"HTML_ALERT_WEAK_PASSWORD": "Du använder standardlösenord.\n
\nFör säkerhetsskäl\nändra<\/a><\/strong>\nlösenord till något annat\n"
diff --git a/snappymail/v/0.0.0/app/localization/sv-SE/user.json b/snappymail/v/0.0.0/app/localization/sv-SE/user.json
index e224ed464..c9565a92d 100644
--- a/snappymail/v/0.0.0/app/localization/sv-SE/user.json
+++ b/snappymail/v/0.0.0/app/localization/sv-SE/user.json
@@ -355,6 +355,7 @@
"LABEL_ACCOUNTS_NAME": "Konton",
"LABEL_IDENTITIES_NAME": "Identiteter",
"LABEL_FILTERS_NAME": "Filter",
+ "LABEL_TEMPLATES_NAME": "Templates",
"LABEL_SECURITY_NAME": "Säkerhet",
"LABEL_THEMES_NAME": "Teman"
},
diff --git a/snappymail/v/0.0.0/app/localization/zh-CN/admin.json b/snappymail/v/0.0.0/app/localization/zh-CN/admin.json
index 8aba60779..e00830713 100644
--- a/snappymail/v/0.0.0/app/localization/zh-CN/admin.json
+++ b/snappymail/v/0.0.0/app/localization/zh-CN/admin.json
@@ -32,6 +32,7 @@
"LABEL_ATTACHMENT_SIZE_LIMIT": "附件大小限制",
"LABEL_ALLOW_ADDITIONAL_ACCOUNTS": "允许额外的账户",
"LABEL_ALLOW_IDENTITIES": "允许多重身份",
+ "LABEL_ALLOW_TEMPLATES": "允许使用模板",
"ALERT_DATA_ACCESS": "数据文件夹可被访问。请配置您的 Web 服务器以阻止从外部访问数据文件夹。获取更多帮助:",
"ALERT_WARNING": "警告!",
"HTML_ALERT_WEAK_PASSWORD": "您正在使用默认的管理员密码\n
\n安全起见,请立即将密码\n更改<\/a><\/strong>\n为其他的字符串。\n"
diff --git a/snappymail/v/0.0.0/app/localization/zh-CN/user.json b/snappymail/v/0.0.0/app/localization/zh-CN/user.json
index 50406c228..07e7ff891 100644
--- a/snappymail/v/0.0.0/app/localization/zh-CN/user.json
+++ b/snappymail/v/0.0.0/app/localization/zh-CN/user.json
@@ -355,6 +355,7 @@
"LABEL_ACCOUNTS_NAME": "账户",
"LABEL_IDENTITIES_NAME": "身份",
"LABEL_FILTERS_NAME": "筛选条件",
+ "LABEL_TEMPLATES_NAME": "模版",
"LABEL_SECURITY_NAME": "安全",
"LABEL_THEMES_NAME": "主题"
},
diff --git a/snappymail/v/0.0.0/app/templates/Views/Admin/AdminSettingsGeneral.html b/snappymail/v/0.0.0/app/templates/Views/Admin/AdminSettingsGeneral.html
index c39b593b7..82dfd2e1f 100644
--- a/snappymail/v/0.0.0/app/templates/Views/Admin/AdminSettingsGeneral.html
+++ b/snappymail/v/0.0.0/app/templates/Views/Admin/AdminSettingsGeneral.html
@@ -128,5 +128,18 @@
}">
+
diff --git a/snappymail/v/0.0.0/app/templates/Views/User/PopupsTemplate.html b/snappymail/v/0.0.0/app/templates/Views/User/PopupsTemplate.html
new file mode 100644
index 000000000..8c8c8db75
--- /dev/null
+++ b/snappymail/v/0.0.0/app/templates/Views/User/PopupsTemplate.html
@@ -0,0 +1,40 @@
+
diff --git a/snappymail/v/0.0.0/app/templates/Views/User/SettingsTemplates.html b/snappymail/v/0.0.0/app/templates/Views/User/SettingsTemplates.html
new file mode 100644
index 000000000..6ac8b5d49
--- /dev/null
+++ b/snappymail/v/0.0.0/app/templates/Views/User/SettingsTemplates.html
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+ ⬍
+
+ |
+
+
+ |
+
+ 🗑
+ |
+
+
+
+