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\n
change<\/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\n
cambiar<\/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\n
changer 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\n
vá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
\n
Wijzig<\/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 @@ +
+
+ +     + +
+ + + + + + + + + + + + +
+ + + + + + 🗑 +
+