diff --git a/dev/Common/Enums.js b/dev/Common/Enums.js index 3322fae3f..b6ef4a508 100644 --- a/dev/Common/Enums.js +++ b/dev/Common/Enums.js @@ -379,6 +379,8 @@ export const Notification = { CantSaveFilters: 351, CantGetFilters: 352, + CantActivateFiltersScript: 351, // TODO: 353 + CantDeleteFiltersScript: 351, // TODO: 354 FiltersAreNotCorrect: 355, CantCreateFolder: 400, diff --git a/dev/Model/SieveScript.js b/dev/Model/SieveScript.js index 4dd1866b2..1749a1f31 100644 --- a/dev/Model/SieveScript.js +++ b/dev/Model/SieveScript.js @@ -10,21 +10,23 @@ class SieveScriptModel extends AbstractModel this.addObservables({ name: '', - nameError: false, - nameFocused: false, - active: false, - body: '', + nameError: false, + bodyError: false, deleteAccess: false, - canBeDeleted: false + canBeDeleted: false, + hasChanges: false }); this.filters = ko.observableArray([]); +// this.saving = ko.observable(false).extend({ throttle: 200 }); this.addSubscribables({ - name: sValue => this.nameError(!sValue) + name: () => this.hasChanges(true), + filters: () => this.hasChanges(true), + body: () => this.hasChanges(true) }); } @@ -34,20 +36,17 @@ class SieveScriptModel extends AbstractModel } verify() { - if (!this.name()) { - this.nameError(true); - return false; - } - this.nameError(false); - return true; + this.nameError(!this.name().trim()); + this.bodyError(this.allowFilters() ? !this.filters().length : !this.body().trim()); + return !this.nameError() && !this.bodyError(); } toJson() { return { name: this.name(), - active: this.active ? '1' : '0', - body: this.body, -// filters: this.filters() + active: this.active() ? '1' : '0', + body: this.body(), + filters: this.filters().map(item => item.toJson()) }; } @@ -66,12 +65,14 @@ class SieveScriptModel extends AbstractModel static reviveFromJson(json) { const script = super.reviveFromJson(json); if (script) { - script.filters([]); if (script.allowFilters() && Array.isNotEmpty(json.filters)) { script.filters( json.filters.map(aData => FilterModel.reviveFromJson(aData)).filter(v => v) ); + } else { + script.filters([]); } + script.hasChanges(false); } return script; } diff --git a/dev/Remote/User/Fetch.js b/dev/Remote/User/Fetch.js index 9dcc3b12b..a5cd64d6d 100644 --- a/dev/Remote/User/Fetch.js +++ b/dev/Remote/User/Fetch.js @@ -236,6 +236,30 @@ class RemoteUserFetch extends AbstractFetchRemote { }); } + /** + * @param {?Function} fCallback + * @param {SieveScriptModel} script + */ + filtersScriptSave(fCallback, script) { + this.defaultRequest(fCallback, 'FiltersScriptSave', script.toJson()); + } + + /** + * @param {?Function} fCallback + * @param {string} name + */ + filtersScriptActivate(fCallback, name) { + this.defaultRequest(fCallback, 'FiltersScriptActivate', {name:name}); + } + + /** + * @param {?Function} fCallback + * @param {string} name + */ + filtersScriptDelete(fCallback, name) { + this.defaultRequest(fCallback, 'FiltersScriptDelete', {name:name}); + } + /** * @param {?Function} fCallback */ diff --git a/dev/Settings/User/Filters.js b/dev/Settings/User/Filters.js index 3b55b7dc2..90895a997 100644 --- a/dev/Settings/User/Filters.js +++ b/dev/Settings/User/Filters.js @@ -1,22 +1,22 @@ import ko from 'ko'; +import { delegateRunOnDestroy } from 'Common/UtilsUser'; import { StorageResultType, Notification } from 'Common/Enums'; import { getNotification } from 'Common/Translator'; -import FilterStore from 'Stores/User/Filter'; import SieveStore from 'Stores/User/Sieve'; import Remote from 'Remote/User/Fetch'; -import { FilterModel } from 'Model/Filter'; import { SieveScriptModel } from 'Model/SieveScript'; import { showScreenPopup } from 'Knoin/Knoin'; class FiltersUserSettings { constructor() { - this.filters = FilterStore.filters; this.sieve = SieveStore; + this.scripts = SieveStore.scripts; + this.loading = ko.observable(false).extend({ throttle: 200 }); ko.addObservablesTo(this, { serverError: false, @@ -33,23 +33,17 @@ class FiltersUserSettings { } updateList() { - if (!this.filters.loading()) { - this.filters.loading(true); + if (!this.loading()) { + this.loading(true); Remote.filtersGet((result, data) => { - this.filters.loading(false); + this.loading(false); this.serverError(false); this.scripts([]); - if (StorageResultType.Success === result && data && data.Result && Array.isArray(data.Result.Filters)) { + if (StorageResultType.Success === result && data && data.Result) { this.serverError(false); - this.filters( - data.Result.Filters.map(aItem => FilterModel.reviveFromJson(aItem)).filter(v => v) - ); - - FilterStore.modules(data.Result.Capa); - SieveStore.capa(data.Result.Capa); /* this.scripts( @@ -60,20 +54,9 @@ class FiltersUserSettings { value = SieveScriptModel.reviveFromJson(value); value && this.scripts.push(value) }); - - FilterStore.raw(data.Result.Scripts['rainloop.user.raw'].body); - FilterStore.capa(data.Result.Capa.join(' ')); -// this.filterRaw.active(data.Result.Scripts['rainloop.user.raw'].active); -// this.filterRaw.allow(!!data.Result.RawIsAllow); } else { - this.filters([]); - FilterStore.modules({}); - + this.scripts([]); SieveStore.capa([]); - - FilterStore.raw(''); - FilterStore.capa({}); - this.serverError(true); this.serverErrorDesc( data && data.ErrorCode ? getNotification(data.ErrorCode) : getNotification(Notification.CantGetFilters) @@ -106,13 +89,42 @@ class FiltersUserSettings { ]); } - deleteScript() { - // TODO + deleteScript(script) { + if (!script.active()) { + Remote.filtersScriptDelete( + (result, data) => { + if (StorageResultType.Success === result && data && data.Result) { + this.scripts.remove(script); + delegateRunOnDestroy(script); + } else { + this.saveError(true); + this.saveErrorText((data && data.ErrorCode) + ? (data.ErrorMessageAdditional || getNotification(data.ErrorCode)) + : getNotification(Notification.CantActivateFiltersScript) + ); + } + }, + script.name() + ); + } } toggleScript(script) { - // TODO: activate/deactivate script - script.active(!script.active()); + let name = script.active() ? '' : script.name(); + Remote.filtersScriptActivate( + (result, data) => { + if (StorageResultType.Success === result && data && data.Result) { + this.scripts().forEach(script => script.active(script.name() === name)); + } else { + this.saveError(true); + this.saveErrorText((data && data.ErrorCode) + ? (data.ErrorMessageAdditional || getNotification(data.ErrorCode)) + : getNotification(Notification.CantActivateFiltersScript) + ); + } + }, + name + ); } onBuild(oDom) { diff --git a/dev/Stores/User/Filter.js b/dev/Stores/User/Filter.js deleted file mode 100644 index 119fc9df7..000000000 --- a/dev/Stores/User/Filter.js +++ /dev/null @@ -1,18 +0,0 @@ -import ko from 'ko'; - -class FilterUserStore { - constructor() { - ko.addObservablesTo(this, { - capa: '', - modules: [], - raw: '' - }); - - this.filters = ko.observableArray([]); - - this.filters.loading = ko.observable(false).extend({ throttle: 200 }); - this.filters.saving = ko.observable(false).extend({ throttle: 200 }); - } -} - -export default new FilterUserStore(); diff --git a/dev/Styles/SettingsFilters.less b/dev/Styles/SettingsFilters.less index cf3534c73..ecbfa26b6 100644 --- a/dev/Styles/SettingsFilters.less +++ b/dev/Styles/SettingsFilters.less @@ -50,3 +50,8 @@ html.rl-mobile .b-settings-filters { .filter-item .filter-sub-name { color: #aaa; } + +.b-filter-script textarea { + height: 300px; + font-family: Monaco, Menlo, Consolas, 'Courier New', monospace; +} diff --git a/dev/View/Popup/Filter.js b/dev/View/Popup/Filter.js index a7331c9b3..84d1e7ad5 100644 --- a/dev/View/Popup/Filter.js +++ b/dev/View/Popup/Filter.js @@ -4,8 +4,8 @@ import { FiltersAction, FilterConditionField, FilterConditionType } from 'Common import { defaultOptionsAfterRender } from 'Common/Utils'; import { i18n, initOnStartOrLangChange } from 'Common/Translator'; -import FilterStore from 'Stores/User/Filter'; import FolderStore from 'Stores/User/Folder'; +import SieveStore from 'Stores/User/Sieve'; import { popup, command } from 'Knoin/Knoin'; import { AbstractViewNext } from 'Knoin/AbstractViewNext'; @@ -25,7 +25,7 @@ class FilterPopupView extends AbstractViewNext { selectedFolderValue: '' }); - this.modules = FilterStore.modules; + this.modules = SieveStore.capa; this.fTrueCallback = null; diff --git a/dev/View/Popup/SieveScript.js b/dev/View/Popup/SieveScript.js index 80014d074..e96649c66 100644 --- a/dev/View/Popup/SieveScript.js +++ b/dev/View/Popup/SieveScript.js @@ -3,12 +3,13 @@ import ko from 'ko'; import { delegateRunOnDestroy } from 'Common/UtilsUser'; import { StorageResultType, Notification } from 'Common/Enums'; import { getNotification } from 'Common/Translator'; +import { i18nToNodes } from 'Common/Translator'; import Remote from 'Remote/User/Fetch'; -import FilterStore from 'Stores/User/Filter'; import FilterModel from 'Model/Filter'; +import SieveStore from 'Stores/User/Sieve'; -import { popup, showScreenPopup, command } from 'Knoin/Knoin'; +import { popup, showScreenPopup/*, command*/ } from 'Knoin/Knoin'; import { AbstractViewNext } from 'Knoin/AbstractViewNext'; @popup({ @@ -19,78 +20,46 @@ class SieveScriptPopupView extends AbstractViewNext { constructor() { super(); -// this.filters = FilterStore.filters; - this.filters = ko.observableArray([]); - this.filters.loading = ko.observable(false).extend({ throttle: 200 }); - this.filters.saving = ko.observable(false).extend({ throttle: 200 }); - - this.modules = FilterStore.modules; - - this.script = { - filters: FilterStore.filters, - saving: () => FilterStore.filters.saving() - }; - ko.addObservablesTo(this, { isNew: true, - serverError: false, - serverErrorDesc: '', + saveError: false, saveErrorText: '', - haveChanges: false, - filterRaw: '' + rawActive: false, + script: null }); - this.serverError.subscribe(value => value || this.serverErrorDesc(''), this); - -// this.filterRaw = FilterStore.raw; - this.filterRaw.capa = FilterStore.capa; - this.filterRaw.active = ko.observable(false); - this.filterRaw.allow = ko.observable(false); - this.filterRaw.error = ko.observable(false); + this.sieveCapabilities = SieveStore.capa.join(' '); + this.saving = false; this.filterForDeletion = ko.observable(null).deleteAccessHelper(); - - this.filters.subscribe(() => this.haveChanges(true)); - - this.filterRaw.subscribe(() => { - this.haveChanges(true); - this.filterRaw.error(false); - }); - - this.haveChanges.subscribe(() => this.saveErrorText('')); - - this.filterRaw.active.subscribe(() => { - this.haveChanges(true); - this.filterRaw.error(false); - }); } - @command((self) => self.haveChanges()) +// @command() saveScriptCommand() { - if (!this.filters.saving()) { - if (this.filterRaw.active() && !this.filterRaw().trim()) { - this.filterRaw.error(true); + let script = this.script(); + if (!this.saving/* && script.hasChanges()*/) { + if (!script.verify()) { return false; } - this.filters.saving(true); - this.saveErrorText(''); + this.saving = true; + this.saveError(false); - Remote.filtersSave( + Remote.filtersScriptSave( (result, data) => { - this.filters.saving(false); + this.saving = false; if (StorageResultType.Success === result && data && data.Result) { - this.haveChanges(false); - } else if (data && data.ErrorCode) { - this.saveErrorText(data.ErrorMessageAdditional || getNotification(data.ErrorCode)); + script.hasChanges(false); } else { - this.saveErrorText(getNotification(Notification.CantSaveFilters)); + this.saveError(true); + this.saveErrorText((data && data.ErrorCode) + ? (data.ErrorMessageAdditional || getNotification(data.ErrorCode)) + : getNotification(Notification.CantSaveFilters) + ); } }, - this.filters(), - this.filterRaw(), - this.filterRaw.active() + script ); } @@ -98,19 +67,18 @@ class SieveScriptPopupView extends AbstractViewNext { } deleteFilter(filter) { - this.filters.remove(filter); + this.script().filters.remove(filter); delegateRunOnDestroy(filter); } addFilter() { + /* this = SieveScriptModel */ const filter = new FilterModel(); - filter.generateID(); showScreenPopup(require('View/Popup/Filter'), [ filter, () => { this.filters.push(filter); - this.filterRaw.active(false); }, false ]); @@ -118,19 +86,16 @@ class SieveScriptPopupView extends AbstractViewNext { editFilter(filter) { const clonedFilter = filter.cloneSelf(); - showScreenPopup(require('View/Popup/Filter'), [ clonedFilter, () => { - const filters = this.filters(), + const script = this.script(), + filters = script.filters(), index = filters.indexOf(filter); - - if (-1 < index && filters[index]) { + if (-1 < index) { delegateRunOnDestroy(filters[index]); filters[index] = clonedFilter; - - this.filters(filters); - this.haveChanges(true); + script.filters(filters); } }, true @@ -141,36 +106,22 @@ class SieveScriptPopupView extends AbstractViewNext { oDom.addEventListener('click', event => { const el = event.target.closestWithin('.filter-item .e-action', oDom), filter = el && ko.dataFor(el); - filter && this.editFilter(ko.dataFor(el)); + filter && this.editFilter(filter); }); } onShow(oScript, fTrueCallback, bEdit) { - this.clearPopup(); - this.fTrueCallback = fTrueCallback; - this.filters(oScript.filters()); - - this.filterRaw(oScript.body()); - this.filterRaw.active(!oScript.allowFilters()); - this.filterRaw.error(false); - + this.script(oScript); + this.rawActive(!oScript.allowFilters()); this.isNew(!bEdit); - - if (!bEdit && oScript) { -// oScript.nameFocused(true); - } + this.saveError(false); } onShowWithDelay() { + // Sometimes not everything is translated, try again + i18nToNodes(this.viewModelDom); } - - clearPopup() { - this.isNew(true); - this.fTrueCallback = null; - this.filters([]); - } - } export { SieveScriptPopupView, SieveScriptPopupView as default }; diff --git a/snappymail/v/0.0.0/app/libraries/MailSo/Sieve/ManageSieveClient.php b/snappymail/v/0.0.0/app/libraries/MailSo/Sieve/ManageSieveClient.php index 8e20157fe..69752b485 100644 --- a/snappymail/v/0.0.0/app/libraries/MailSo/Sieve/ManageSieveClient.php +++ b/snappymail/v/0.0.0/app/libraries/MailSo/Sieve/ManageSieveClient.php @@ -326,6 +326,17 @@ class ManageSieveClient extends \MailSo\Net\NetClient return $this; } + /** + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Sieve\Exceptions\NegativeResponseException + */ + public function RenameScript(string $sOldName, string $sNewName) : self + { + $this->sendRequestWithCheck('RENAMESCRIPT "'.$sOldName.'" "'.$sNewName.'"'); + + return $this; + } + /** * @throws \MailSo\Net\Exceptions\Exception * @throws \MailSo\Sieve\Exceptions\NegativeResponseException diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Filters.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Filters.php index 34fa59fab..f8d21ebf0 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Filters.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Filters.php @@ -76,6 +76,64 @@ trait Filters $aFilters, $sRaw, $bRawIsActive)); } + public function DoFiltersScriptSave() : array + { + $oAccount = $this->getAccountFromToken(); + + if (!$this->GetCapa(false, false, Capa::FILTERS, $oAccount)) { + return $this->FalseResponse(__FUNCTION__); + } + + $sName = $this->GetActionParam('name', ''); + + $aFilters = array(); + if (\RainLoop\Providers\Filters\SieveStorage::SIEVE_FILE_NAME === $sName) { + $aIncFilters = $this->GetActionParam('filters', array()); + foreach ($aIncFilters as $aFilter) { + if (\is_array($aFilter)) { + $oFilter = new \RainLoop\Providers\Filters\Classes\Filter(); + if ($oFilter->FromJSON($aFilter)) { + $aFilters[] = $oFilter; + } + } + } + } + + if ($this->GetActionParam('active', false)) { +// $this->FiltersProvider()->ActivateScript($oAccount, $sName); + } + + return $this->DefaultResponse(__FUNCTION__, $this->FiltersProvider()->Save( + $oAccount, $sName, $aFilters, $this->GetActionParam('body', '') + )); + } + + public function DoFiltersScriptActivate() : array + { + $oAccount = $this->getAccountFromToken(); + + if (!$this->GetCapa(false, false, Capa::FILTERS, $oAccount)) { + return $this->FalseResponse(__FUNCTION__); + } + + return $this->DefaultResponse(__FUNCTION__, $this->FiltersProvider()->ActivateScript( + $oAccount, $this->GetActionParam('name', '') + )); + } + + public function DoFiltersScriptDelete() : array + { + $oAccount = $this->getAccountFromToken(); + + if (!$this->GetCapa(false, false, Capa::FILTERS, $oAccount)) { + return $this->FalseResponse(__FUNCTION__); + } + + return $this->DefaultResponse(__FUNCTION__, $this->FiltersProvider()->DeleteScript( + $oAccount, $this->GetActionParam('name', '') + )); + } + protected function FiltersProvider() : \RainLoop\Providers\Filters { if (!$this->oFiltersProvider) { diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Notifications.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Notifications.php index fe339fba6..2c532aa74 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Notifications.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Notifications.php @@ -35,6 +35,8 @@ class Notifications const CantSaveFilters = 351; const CantGetFilters = 352; + const CantActivateFiltersScript = 351; // TODO: 353 + const CantDeleteFiltersScript = 351; // TODO: 354 const FiltersAreNotCorrect = 355; const CantCreateFolder = 400; diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/Filters.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/Filters.php index 6382e4041..379a9b280 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/Filters.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/Filters.php @@ -14,47 +14,73 @@ class Filters extends \RainLoop\Providers\AbstractProvider $this->oDriver = $oDriver; } + private static function handleException(\Throwable $oException, int $defNotification) : void + { + if ($oException instanceof \MailSo\Net\Exceptions\SocketCanNotConnectToHostException) { + throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::ConnectionError, $oException); + } + + if ($oException instanceof \MailSo\Sieve\Exceptions\NegativeResponseException) { + throw new \RainLoop\Exceptions\ClientException( + \RainLoop\Notifications::ClientViewError, $oException, \implode("\r\n", $oException->GetResponses()) + ); + } + + throw new \RainLoop\Exceptions\ClientException($defNotification, $oException); + } + public function Load(\RainLoop\Model\Account $oAccount, bool $bAllowRaw = false) : array { try { return $this->IsActive() ? $this->oDriver->Load($oAccount, $bAllowRaw) : array(); } - catch (\MailSo\Net\Exceptions\SocketCanNotConnectToHostException $oException) - { - throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::ConnectionError, $oException); - } catch (\Throwable $oException) { - throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::CantGetFilters, $oException); + static::handleException($oException, \RainLoop\Notifications::CantGetFilters); } - - return false; } - public function Save(\RainLoop\Model\Account $oAccount, array $aFilters, string $sRaw = '', bool $bRawIsActive = false) : bool + public function Save(\RainLoop\Model\Account $oAccount, string $sScriptName, array $aFilters, string $sRaw = '') : bool { try { - return $this->IsActive() ? $this->oDriver->Save( - $oAccount, $aFilters, $sRaw, $bRawIsActive) : false; - } - catch (\MailSo\Net\Exceptions\SocketCanNotConnectToHostException $oException) - { - throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::ConnectionError, $oException); - } - catch (\MailSo\Sieve\Exceptions\NegativeResponseException $oException) - { - throw new \RainLoop\Exceptions\ClientException( - \RainLoop\Notifications::ClientViewError, $oException, - \implode("\r\n", $oException->GetResponses())); + return $this->IsActive() + ? $this->oDriver->Save($oAccount, $sScriptName, $aFilters, $sRaw) + : false; } catch (\Throwable $oException) { - throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::CantSaveFilters, $oException); + static::handleException($oException. \RainLoop\Notifications::CantSaveFilters); } + } - return false; + public function ActivateScript(\RainLoop\Model\Account $oAccount, string $sScriptName) + { + try + { + return $this->IsActive() + ? $this->oDriver->Activate($oAccount, $sScriptName) + : false; + } + catch (\Throwable $oException) + { + static::handleException($oException. \RainLoop\Notifications::CantActivateFiltersScript); + } + } + + public function DeleteScript(\RainLoop\Model\Account $oAccount, string $sScriptName) + { + try + { + return $this->IsActive() + ? $this->oDriver->Delete($oAccount, $sScriptName) + : false; + } + catch (\Throwable $oException) + { + static::handleException($oException. \RainLoop\Notifications::CantDeleteFiltersScript); + } } public function IsActive() : bool diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/Filters/FiltersInterface.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/Filters/FiltersInterface.php index 5e5974b85..598b13790 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/Filters/FiltersInterface.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/Filters/FiltersInterface.php @@ -6,5 +6,9 @@ interface FiltersInterface { public function Load(\RainLoop\Model\Account $oAccount, bool $bAllowRaw = false) : array; - public function Save(\RainLoop\Model\Account $oAccount, array $aFilters) : bool; + public function Save(\RainLoop\Model\Account $oAccount, string $sScriptName, array $aFilters, string $sRaw = '') : bool; + + public function Activate(\RainLoop\Model\Account $oAccount, string $sScriptName) : bool; + + public function Delete(\RainLoop\Model\Account $oAccount, string $sScriptName) : bool; } diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/Filters/SieveStorage.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/Filters/SieveStorage.php index eabd77d81..264f3d556 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/Filters/SieveStorage.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Providers/Filters/SieveStorage.php @@ -39,52 +39,67 @@ class SieveStorage implements FiltersInterface $this->bUtf8FolderName = !!$this->oConfig->Get('labs', 'sieve_utf8_folder_name', true); } + protected function getConnection(\RainLoop\Model\Account $oAccount) : ?\MailSo\Sieve\ManageSieveClient + { + $oSieveClient = new \MailSo\Sieve\ManageSieveClient(); + $oSieveClient->SetLogger($this->oLogger); + $oSieveClient->SetTimeOuts(10, (int) \RainLoop\Api::Config()->Get('labs', 'sieve_timeout', 10)); + return $oAccount->SieveConnectAndLoginHelper($this->oPlugins, $oSieveClient, $this->oConfig) + ? $oSieveClient + : null; + } + public function Load(\RainLoop\Model\Account $oAccount, bool $bAllowRaw = false) : array { $aModules = array(); - $aFilters = array(); $aScripts = array(); - $oSieveClient = new \MailSo\Sieve\ManageSieveClient(); - $oSieveClient->SetLogger($this->oLogger); - $oSieveClient->SetTimeOuts(10, (int) $this->oConfig->Get('labs', 'sieve_timeout', 10)); - - if ($oAccount->SieveConnectAndLoginHelper($this->oPlugins, $oSieveClient, $this->oConfig)) { + $oSieveClient = $this->getConnection($oAccount); + if ($oSieveClient) { $aModules = $oSieveClient->Modules(); + \sort($aModules); + $aList = $oSieveClient->ListScripts(); - if (!empty($aList[self::SIEVE_FILE_NAME])) { - $sS = $oSieveClient->GetScript(self::SIEVE_FILE_NAME); - if ($sS) { - $aFilters = $this->fileStringToCollection($sS); - } - } - - if ($bAllowRaw) { - foreach ($aList as $name => $active) { - if ($name != self::SIEVE_FILE_NAME) { + foreach ($aList as $name => $active) { + if ($name != self::SIEVE_FILE_NAME) { + if ($bAllowRaw) { $aScripts[$name] = array( '@Object' => 'Object/SieveScript', 'name' => $name, 'active' => $active, 'body' => $oSieveClient->GetScript($name) // \trim() ? ); - } else { - $aScripts[$name] = array( - '@Object' => 'Object/SieveScript', - 'name' => $name, - 'active' => $active, - 'body' => $oSieveClient->GetScript($name), // \trim() ? - 'filters' => $aFilters - ); } + } else { + $sS = $oSieveClient->GetScript(self::SIEVE_FILE_NAME); + if ($sS) { + $aFilters = $this->fileStringToCollection($sS); + } + $aScripts[$name] = array( + '@Object' => 'Object/SieveScript', + 'name' => $name, + 'active' => $active, + 'body' => $oSieveClient->GetScript($name), // \trim() ? + 'filters' => $aFilters + ); } } $oSieveClient->LogoutAndDisconnect(); } - if (!isset($aList[self::SIEVE_FILE_NAME_RAW])) { + if (!isset($aList[self::SIEVE_FILE_NAME])) { + $aScripts[$name] = array( + '@Object' => 'Object/SieveScript', + 'name' => self::SIEVE_FILE_NAME, + 'active' => false, + 'body' => '', + 'filters' => [] + ); + } + + if ($bAllowRaw && !isset($aList[self::SIEVE_FILE_NAME_RAW])) { $aScripts[$name] = array( '@Object' => 'Object/SieveScript', 'name' => self::SIEVE_FILE_NAME_RAW, @@ -96,55 +111,52 @@ class SieveStorage implements FiltersInterface \ksort($aScripts); return array( - 'RawIsAllow' => $bAllowRaw, - 'Filters' => $aFilters, 'Capa' => $aModules, 'Scripts' => $aScripts ); } - public function Save(\RainLoop\Model\Account $oAccount, array $aFilters, string $sRaw = '', bool $bRawIsActive = false) : bool + public function Save(\RainLoop\Model\Account $oAccount, string $sScriptName, array $aFilters, string $sRaw = '') : bool { - $oSieveClient = new \MailSo\Sieve\ManageSieveClient(); - $oSieveClient->SetLogger($this->oLogger); - $oSieveClient->SetTimeOuts(10, (int) \RainLoop\Api::Config()->Get('labs', 'sieve_timeout', 10)); - - if ($oAccount->SieveConnectAndLoginHelper($this->oPlugins, $oSieveClient, $this->oConfig)) - { - $aList = $oSieveClient->ListScripts(); - - if ($bRawIsActive) - { - if (!empty($sRaw)) - { - $oSieveClient->PutScript(self::SIEVE_FILE_NAME_RAW, $sRaw); - $oSieveClient->SetActiveScript(self::SIEVE_FILE_NAME_RAW); - } - else if (isset($aList[self::SIEVE_FILE_NAME_RAW])) - { - $oSieveClient->DeleteScript(self::SIEVE_FILE_NAME_RAW); + if (self::SIEVE_FILE_NAME === $sScriptName) { + $sRaw = $this->collectionToFileString($aFilters); + } + $oSieveClient = $this->getConnection($oAccount); + if ($oSieveClient) { + if (empty($sRaw)) { + $aList = $oSieveClient->ListScripts(); + if (isset($aList[$sScriptName])) { + $oSieveClient->DeleteScript($sScriptName); } + } else { + $oSieveClient->PutScript($sScriptName, $sRaw); } - else - { - $sUserFilter = $this->collectionToFileString($aFilters); - - if (!empty($sUserFilter)) - { - $oSieveClient->PutScript(self::SIEVE_FILE_NAME, $sUserFilter); - $oSieveClient->SetActiveScript(self::SIEVE_FILE_NAME); - } - else if (isset($aList[self::SIEVE_FILE_NAME])) - { - $oSieveClient->DeleteScript(self::SIEVE_FILE_NAME); - } - } - $oSieveClient->LogoutAndDisconnect(); - return true; } + return false; + } + /** + * If $sScriptName is the empty string (i.e., ""), then any active script is disabled. + */ + public function Activate(\RainLoop\Model\Account $oAccount, string $sScriptName) : bool + { + $oSieveClient = $this->getConnection($oAccount); + if ($oSieveClient) { + $oSieveClient->SetActiveScript(\trim($sScriptName)); + return true; + } + return false; + } + + public function Delete(\RainLoop\Model\Account $oAccount, string $sScriptName) : bool + { + $oSieveClient = $this->getConnection($oAccount); + if ($oSieveClient) { + $oSieveClient->DeleteScript(\trim($sScriptName)); + return true; + } return false; } diff --git a/snappymail/v/0.0.0/app/localization/webmail/_source.en.yml b/snappymail/v/0.0.0/app/localization/webmail/_source.en.yml index 6db80cdf9..f50a606d8 100644 --- a/snappymail/v/0.0.0/app/localization/webmail/_source.en.yml +++ b/snappymail/v/0.0.0/app/localization/webmail/_source.en.yml @@ -349,6 +349,15 @@ en: VACATION_RECIPIENTS_LABEL: "Recipients (comma separated)" REJECT_MESSAGE_LABEL: "Reject message" ALL_INCOMING_MESSAGES_DESC: "All incoming messages" + POPUPS_SIEVE_SCRIPT: + TITLE_CREATE: "Create Script" + TITLE_EDIT: "Edit Script" + SCRIPT_NAME: "Name" + BUTTON_ADD_FILTER: "Add a Filter" + BUTTON_RAW_SCRIPT: "Use Custom User Script" + BUTTON_SAVE: "Save" + CAPABILITY_LABEL: "Capabilities" + CHANGES_NEED_TO_BE_SAVED_DESC: "These changes need to be saved to the server." POPUPS_SYSTEM_FOLDERS: TITLE_SYSTEM_FOLDERS: "Select system folders" SELECT_CHOOSE_ONE: "Choose one" @@ -431,21 +440,15 @@ en: BUTTON_BACK: "Back" SETTINGS_FILTERS: LEGEND_FILTERS: "Filters" - LEGEND_SIEVE_SCRIPT: "Sieve Script" - BUTTON_SAVE: "Save" - BUTTON_ADD_FILTER: "Add a Filter" BUTTON_DELETE: "Delete" - BUTTON_RAW_SCRIPT: "Use Custom User Script" SUBNAME_NONE: "None" SUBNAME_MOVE_TO: "Move to \"%FOLDER%\"" SUBNAME_FORWARD_TO: "Forward to \"%EMAIL%\"" SUBNAME_REJECT: "Reject" SUBNAME_VACATION_MESSAGE: "Vacation message" SUBNAME_DISCARD: "Discard" - CAPABILITY_LABEL: "Capability" LOADING_PROCESS: "Updating filter list" DELETING_ASK: "Are you sure?" - CHANGES_NEED_TO_BE_SAVED_DESC: "These changes need to be saved to the server." SETTINGS_IDENTITY: LEGEND_IDENTITY: "Identity" LABEL_DISPLAY_NAME: "Name" diff --git a/snappymail/v/0.0.0/app/localization/webmail/de_DE.yml b/snappymail/v/0.0.0/app/localization/webmail/de_DE.yml index 21391407d..304084e3c 100644 --- a/snappymail/v/0.0.0/app/localization/webmail/de_DE.yml +++ b/snappymail/v/0.0.0/app/localization/webmail/de_DE.yml @@ -348,6 +348,15 @@ de_DE: VACATION_RECIPIENTS_LABEL: "Empfänger (durch Komma getrennt)" REJECT_MESSAGE_LABEL: "Ablehnnachricht" ALL_INCOMING_MESSAGES_DESC: "Alle eingehenden Nachrichten" + POPUPS_SIEVE_SCRIPT: + TITLE_CREATE: "Skript erstellen" + TITLE_EDIT: "Skript bearbeiten" + SCRIPT_NAME: "Name" + BUTTON_ADD_FILTER: "Filter hinzufügen" + BUTTON_RAW_SCRIPT: "Benutzerdefiniertes Skript verwenden" + BUTTON_SAVE: "Speichern" + CAPABILITY_LABEL: "Unterstützte Module" + CHANGES_NEED_TO_BE_SAVED_DESC: "Die Änderungen müssen auf dem Server gespeichert werden." POPUPS_SYSTEM_FOLDERS: TITLE_SYSTEM_FOLDERS: "Wählen Sie die Systemordner aus" SELECT_CHOOSE_ONE: "Wählen Sie einen aus" @@ -431,20 +440,15 @@ de_DE: BUTTON_BACK: "Zurück" SETTINGS_FILTERS: LEGEND_FILTERS: "Filter" - BUTTON_SAVE: "Speichern" - BUTTON_ADD_FILTER: "Filter hinzufügen" BUTTON_DELETE: "Löschen" - BUTTON_RAW_SCRIPT: "Benutzerdefiniertes Skript verwenden" SUBNAME_NONE: "Keine" SUBNAME_MOVE_TO: "Verschieben nach \"%FOLDER%\"" SUBNAME_FORWARD_TO: "Weiterleiten nach \"%EMAIL%\"" SUBNAME_REJECT: "Ablehnen" SUBNAME_VACATION_MESSAGE: "Urlaubsbenachrichtigung" SUBNAME_DISCARD: "Verwerfen" - CAPABILITY_LABEL: "Unterstützte Module" LOADING_PROCESS: "Aktualisiere Filterliste" DELETING_ASK: "Sind Sie sicher?" - CHANGES_NEED_TO_BE_SAVED_DESC: "Die Änderungen müssen auf dem Server gespeichert werden." SETTINGS_IDENTITY: LEGEND_IDENTITY: "Identität" LABEL_DISPLAY_NAME: "Name" diff --git a/snappymail/v/0.0.0/app/localization/webmail/en_GB.yml b/snappymail/v/0.0.0/app/localization/webmail/en_GB.yml index ef0267752..f05daa844 100644 --- a/snappymail/v/0.0.0/app/localization/webmail/en_GB.yml +++ b/snappymail/v/0.0.0/app/localization/webmail/en_GB.yml @@ -349,6 +349,15 @@ en_GB: VACATION_RECIPIENTS_LABEL: "Recipients (comma separated)" REJECT_MESSAGE_LABEL: "Reject message" ALL_INCOMING_MESSAGES_DESC: "All incoming messages" + POPUPS_SIEVE_SCRIPT: + TITLE_CREATE: "Create Script" + TITLE_EDIT: "Edit Script" + SCRIPT_NAME: "Name" + BUTTON_ADD_FILTER: "Add a Filter" + BUTTON_RAW_SCRIPT: "Use Custom User Script" + BUTTON_SAVE: "Save" + CAPABILITY_LABEL: "Capabilities" + CHANGES_NEED_TO_BE_SAVED_DESC: "These changes need to be saved to the server." POPUPS_SYSTEM_FOLDERS: TITLE_SYSTEM_FOLDERS: "Select system folders" SELECT_CHOOSE_ONE: "Choose one" @@ -431,20 +440,15 @@ en_GB: BUTTON_BACK: "Back" SETTINGS_FILTERS: LEGEND_FILTERS: "Filters" - BUTTON_SAVE: "Save" - BUTTON_ADD_FILTER: "Add a Filter" BUTTON_DELETE: "Delete" - BUTTON_RAW_SCRIPT: "Use Custom User Script" SUBNAME_NONE: "None" SUBNAME_MOVE_TO: "Move to \"%FOLDER%\"" SUBNAME_FORWARD_TO: "Forward to \"%EMAIL%\"" SUBNAME_REJECT: "Reject" SUBNAME_VACATION_MESSAGE: "Vacation message" SUBNAME_DISCARD: "Discard" - CAPABILITY_LABEL: "Capability" LOADING_PROCESS: "Updating filter list" DELETING_ASK: "Are you sure?" - CHANGES_NEED_TO_BE_SAVED_DESC: "These changes need to be saved to the server." SETTINGS_IDENTITY: LEGEND_IDENTITY: "Identity" LABEL_DISPLAY_NAME: "Name" diff --git a/snappymail/v/0.0.0/app/localization/webmail/en_US.yml b/snappymail/v/0.0.0/app/localization/webmail/en_US.yml index a803f0802..bc2d2ded5 100644 --- a/snappymail/v/0.0.0/app/localization/webmail/en_US.yml +++ b/snappymail/v/0.0.0/app/localization/webmail/en_US.yml @@ -349,6 +349,15 @@ en_US: VACATION_RECIPIENTS_LABEL: "Recipients (comma separated)" REJECT_MESSAGE_LABEL: "Reject message" ALL_INCOMING_MESSAGES_DESC: "All incoming messages" + POPUPS_SIEVE_SCRIPT: + TITLE_CREATE: "Create Script" + TITLE_EDIT: "Edit Script" + SCRIPT_NAME: "Name" + BUTTON_ADD_FILTER: "Add a Filter" + BUTTON_RAW_SCRIPT: "Use Custom User Script" + BUTTON_SAVE: "Save" + CAPABILITY_LABEL: "Capabilities" + CHANGES_NEED_TO_BE_SAVED_DESC: "These changes need to be saved to the server." POPUPS_SYSTEM_FOLDERS: TITLE_SYSTEM_FOLDERS: "Select system folders" SELECT_CHOOSE_ONE: "Choose one" @@ -431,20 +440,15 @@ en_US: BUTTON_BACK: "Back" SETTINGS_FILTERS: LEGEND_FILTERS: "Filters" - BUTTON_SAVE: "Save" - BUTTON_ADD_FILTER: "Add a Filter" BUTTON_DELETE: "Delete" - BUTTON_RAW_SCRIPT: "Use Custom User Script" SUBNAME_NONE: "None" SUBNAME_MOVE_TO: "Move to \"%FOLDER%\"" SUBNAME_FORWARD_TO: "Forward to \"%EMAIL%\"" SUBNAME_REJECT: "Reject" SUBNAME_VACATION_MESSAGE: "Vacation message" SUBNAME_DISCARD: "Discard" - CAPABILITY_LABEL: "Capability" LOADING_PROCESS: "Updating filter list" DELETING_ASK: "Are you sure?" - CHANGES_NEED_TO_BE_SAVED_DESC: "These changes need to be saved to the server." SETTINGS_IDENTITY: LEGEND_IDENTITY: "Identity" LABEL_DISPLAY_NAME: "Name" diff --git a/snappymail/v/0.0.0/app/localization/webmail/es_ES.yml b/snappymail/v/0.0.0/app/localization/webmail/es_ES.yml index 59a9749f3..0fdb4b2ed 100644 --- a/snappymail/v/0.0.0/app/localization/webmail/es_ES.yml +++ b/snappymail/v/0.0.0/app/localization/webmail/es_ES.yml @@ -348,6 +348,15 @@ es_ES: VACATION_RECIPIENTS_LABEL: "Destinatarios (separados por coma)" REJECT_MESSAGE_LABEL: "Rechazar mensaje" ALL_INCOMING_MESSAGES_DESC: "Todos los mensajes entrantes" + POPUPS_SIEVE_SCRIPT: + TITLE_CREATE: "Create Script" + TITLE_EDIT: "Edit Script" + SCRIPT_NAME: "Nombre" + BUTTON_ADD_FILTER: "Añadir un Filtro" + BUTTON_RAW_SCRIPT: "Usar Script Personalizado" + BUTTON_SAVE: "Guardar" + CAPABILITY_LABEL: "Capability" + CHANGES_NEED_TO_BE_SAVED_DESC: "Estos cambios deben ser cambiados en el servidor." POPUPS_SYSTEM_FOLDERS: TITLE_SYSTEM_FOLDERS: "Seleccione las carpetas del sistema" SELECT_CHOOSE_ONE: "Elija una" @@ -432,20 +441,15 @@ es_ES: BUTTON_BACK: "Atrás" SETTINGS_FILTERS: LEGEND_FILTERS: "Filtros" - BUTTON_SAVE: "Guardar" - BUTTON_ADD_FILTER: "Añadir un Filtro" BUTTON_DELETE: "Borrar" - BUTTON_RAW_SCRIPT: "Usar Script Personalizado" SUBNAME_NONE: "Ninguno" SUBNAME_MOVE_TO: "Mover a \"%FOLDER%\"" SUBNAME_FORWARD_TO: "Reenviar a \"%EMAIL%\"" SUBNAME_REJECT: "Rechazar" SUBNAME_VACATION_MESSAGE: "Mensaje de vacaciones" SUBNAME_DISCARD: "Descartar" - CAPABILITY_LABEL: "Capability" LOADING_PROCESS: "Actualizando lista de filtros" DELETING_ASK: "¿Está seguro?" - CHANGES_NEED_TO_BE_SAVED_DESC: "Estos cambios deben ser cambiados en el servidor." SETTINGS_IDENTITY: LEGEND_IDENTITY: "Identidad" LABEL_DISPLAY_NAME: "Nombre" diff --git a/snappymail/v/0.0.0/app/localization/webmail/fr_FR.yml b/snappymail/v/0.0.0/app/localization/webmail/fr_FR.yml index 97251b349..b07f4da3c 100644 --- a/snappymail/v/0.0.0/app/localization/webmail/fr_FR.yml +++ b/snappymail/v/0.0.0/app/localization/webmail/fr_FR.yml @@ -349,6 +349,15 @@ fr_FR: VACATION_RECIPIENTS_LABEL: "Destinataires (séparés par des virgules)" REJECT_MESSAGE_LABEL: "Message rejeté" ALL_INCOMING_MESSAGES_DESC: "Tous les messages entrants" + POPUPS_SIEVE_SCRIPT: + TITLE_CREATE: "Create Script" + TITLE_EDIT: "Edit Script" + SCRIPT_NAME: "Nom" + BUTTON_ADD_FILTER: "Ajouter un filtre" + BUTTON_RAW_SCRIPT: "Utiliser le script d'un utilisateur" + BUTTON_SAVE: "Enregistrer" + CAPABILITY_LABEL: "Capacité" + CHANGES_NEED_TO_BE_SAVED_DESC: "Ces changements doivent être enregistrés sur le serveur." POPUPS_SYSTEM_FOLDERS: TITLE_SYSTEM_FOLDERS: "Sélectionner les dossiers systèmes" SELECT_CHOOSE_ONE: "Faites un choix" @@ -432,20 +441,15 @@ fr_FR: BUTTON_BACK: "Retour" SETTINGS_FILTERS: LEGEND_FILTERS: "Filtres" - BUTTON_SAVE: "Enregistrer" - BUTTON_ADD_FILTER: "Ajouter un filtre" BUTTON_DELETE: "Effacer" - BUTTON_RAW_SCRIPT: "Utiliser le script d'un utilisateur" SUBNAME_NONE: "Aucun" SUBNAME_MOVE_TO: "Déplacer vers \"%FOLDER%\"" SUBNAME_FORWARD_TO: "Faire suivre à \"%EMAIL%\"" SUBNAME_REJECT: "Rejeter" SUBNAME_VACATION_MESSAGE: "Message d'absence" SUBNAME_DISCARD: "Exclure" - CAPABILITY_LABEL: "Capacité" LOADING_PROCESS: "Mise à jour de la liste de filtres" DELETING_ASK: "Êtes-vous sûr ?" - CHANGES_NEED_TO_BE_SAVED_DESC: "Ces changements doivent être enregistrés sur le serveur." SETTINGS_IDENTITY: LEGEND_IDENTITY: "Identité" LABEL_DISPLAY_NAME: "Nom" diff --git a/snappymail/v/0.0.0/app/localization/webmail/nl_NL.yml b/snappymail/v/0.0.0/app/localization/webmail/nl_NL.yml index dc9bca049..6f432e604 100644 --- a/snappymail/v/0.0.0/app/localization/webmail/nl_NL.yml +++ b/snappymail/v/0.0.0/app/localization/webmail/nl_NL.yml @@ -348,6 +348,15 @@ nl_NL: VACATION_RECIPIENTS_LABEL: "Ontvangers (comma gescheiden)" REJECT_MESSAGE_LABEL: "Afwijsbericht" ALL_INCOMING_MESSAGES_DESC: "Alle inkomende berichten" + POPUPS_SIEVE_SCRIPT: + TITLE_CREATE: "Script toevoegen" + TITLE_EDIT: "Script aanpassen" + SCRIPT_NAME: "Naam" + BUTTON_ADD_FILTER: "Filter toevoegen" + BUTTON_RAW_SCRIPT: "Maak een custom script" + BUTTON_SAVE: "Opslaan" + CAPABILITY_LABEL: "Mogelijkheden" + CHANGES_NEED_TO_BE_SAVED_DESC: "Wijzigingen moeten nog opgeslagen worden op de server." POPUPS_SYSTEM_FOLDERS: TITLE_SYSTEM_FOLDERS: "Selecteer systeem mappen" SELECT_CHOOSE_ONE: "Kies één" @@ -430,20 +439,15 @@ nl_NL: BUTTON_BACK: "Terug" SETTINGS_FILTERS: LEGEND_FILTERS: "Filters" - BUTTON_SAVE: "Opslaan" - BUTTON_ADD_FILTER: "Filter toevoegen" BUTTON_DELETE: "Verwijder" - BUTTON_RAW_SCRIPT: "Maak een custom script" SUBNAME_NONE: "Geen" SUBNAME_MOVE_TO: "Verplaats naar map \"%FOLDER%\"" SUBNAME_FORWARD_TO: "Doorsturen naar \"%EMAIL%\"" SUBNAME_REJECT: "Afwijzen" SUBNAME_VACATION_MESSAGE: "Afwezigheidsbericht" SUBNAME_DISCARD: "Gooi weg" - CAPABILITY_LABEL: "Mogelijkheden" LOADING_PROCESS: "Bezig met updaten van de filter lijst" DELETING_ASK: "Weet u het zeker?" - CHANGES_NEED_TO_BE_SAVED_DESC: "Wijzigingen moeten nog opgeslagen worden op de server." SETTINGS_IDENTITY: LEGEND_IDENTITY: "Identiteit" LABEL_DISPLAY_NAME: "Naam" diff --git a/snappymail/v/0.0.0/app/localization/webmail/zh_CN.yml b/snappymail/v/0.0.0/app/localization/webmail/zh_CN.yml index 0b092750e..073ab5092 100644 --- a/snappymail/v/0.0.0/app/localization/webmail/zh_CN.yml +++ b/snappymail/v/0.0.0/app/localization/webmail/zh_CN.yml @@ -348,6 +348,15 @@ zh_CN: VACATION_RECIPIENTS_LABEL: "收件人 (半角逗号“,”分隔)" REJECT_MESSAGE_LABEL: "拒收邮件" ALL_INCOMING_MESSAGES_DESC: "所有来信" + POPUPS_SIEVE_SCRIPT: + TITLE_CREATE: "Create Script" + TITLE_EDIT: "Edit Script" + SCRIPT_NAME: "名称" + BUTTON_ADD_FILTER: "添加筛选条件" + BUTTON_RAW_SCRIPT: "使用自定义脚本" + BUTTON_SAVE: "保存" + CAPABILITY_LABEL: "Capability" + CHANGES_NEED_TO_BE_SAVED_DESC: "这些更改需要保存。" POPUPS_SYSTEM_FOLDERS: TITLE_SYSTEM_FOLDERS: "选择系统文件夹" SELECT_CHOOSE_ONE: "选择一个" @@ -429,20 +438,15 @@ zh_CN: BUTTON_BACK: "返回" SETTINGS_FILTERS: LEGEND_FILTERS: "筛选器" - BUTTON_SAVE: "保存" - BUTTON_ADD_FILTER: "添加筛选条件" BUTTON_DELETE: "删除" - BUTTON_RAW_SCRIPT: "使用自定义脚本" SUBNAME_NONE: "无" SUBNAME_MOVE_TO: "移动到 \"%FOLDER%\"" SUBNAME_FORWARD_TO: "转发到 \"%EMAIL%\"" SUBNAME_REJECT: "拒绝" SUBNAME_VACATION_MESSAGE: "假期自动回复" SUBNAME_DISCARD: "取消" - CAPABILITY_LABEL: "Capability" LOADING_PROCESS: "正在更新筛选规则列表" DELETING_ASK: "确定删除?" - CHANGES_NEED_TO_BE_SAVED_DESC: "这些更改需要保存。" SETTINGS_IDENTITY: LEGEND_IDENTITY: "身份" LABEL_DISPLAY_NAME: "名称" diff --git a/snappymail/v/0.0.0/app/templates/Views/User/PopupsSieveScript.html b/snappymail/v/0.0.0/app/templates/Views/User/PopupsSieveScript.html index a6dce24f1..269b27ff0 100644 --- a/snappymail/v/0.0.0/app/templates/Views/User/PopupsSieveScript.html +++ b/snappymail/v/0.0.0/app/templates/Views/User/PopupsSieveScript.html @@ -1,51 +1,65 @@