diff --git a/build/langs.php b/build/langs.php new file mode 100644 index 000000000..5c12dd192 --- /dev/null +++ b/build/langs.php @@ -0,0 +1,75 @@ + $aSectionValue) + { + foreach (\array_keys($aSectionValue) as $sParamKey) + { + if (isset($aFromLang[$sSectionKey][$sParamKey])) + { + $aEngLang[$sSectionKey][$sParamKey] = $aFromLang[$sSectionKey][$sParamKey]; + } + else + { +// echo $sSectionKey.'/'.$sParamKey.','."\n"; + $iCount++; + } + } + } + + return $aEngLang; +} + +function saveLangStructure($sLangFile, $aLang) +{ + $aResultLines = array(); + $aResultLines[] = '; '.$sLangFile; + + foreach ($aLang as $sSectionKey => $aSectionValue) + { + $aResultLines[] = ''; + $aResultLines[] = '['.$sSectionKey.']'; + + foreach ($aSectionValue as $sParamKey => $sParamValue) + { + $aResultLines[] = $sParamKey.' = "'. + \str_replace(array('\\', '"'), array('\\\\', '\\"'), \trim($sParamValue)).'"'; + } + } + + \file_put_contents(LANGS_PATH.'/'.$sLangFile, implode("\n", $aResultLines)); +} + +$sNL = "\n"; +$aEngLang = \getLangStructure('en.ini'); + +$aFiles = \glob(LANGS_PATH.'/*.ini'); +foreach ($aFiles as $sFile) +{ + $iCount = 0; + $sFileName = \basename($sFile); + + $aNextLang = \getLangStructure($sFileName); + $aNewLang = \mergeLangStructure($aNextLang, $aEngLang, $iCount); + + if (\json_encode($aNextLang) === \json_encode($aNewLang)) + { + echo $sFileName.': ok'.$sNL; + } + else + { + echo $sFileName.': changed ('.$iCount.')'.$sNL; + } + +// \saveLangStructure($sFileName, $aNewLang); +} \ No newline at end of file diff --git a/dev/Common/Enums.js b/dev/Common/Enums.js index cbd7e496e..e61cfa545 100644 --- a/dev/Common/Enums.js +++ b/dev/Common/Enums.js @@ -307,6 +307,7 @@ 'MoveTo': 'MoveTo', 'Discard': 'Discard', 'Vacation': 'Vacation', + 'Reject': 'Reject', 'Forward': 'Forward' }; diff --git a/dev/Model/Filter.js b/dev/Model/Filter.js index 58d43272f..cb112d2c9 100644 --- a/dev/Model/Filter.js +++ b/dev/Model/Filter.js @@ -45,7 +45,7 @@ this.actionSkipOthers = ko.observable(false); - this.keepForward = ko.observable(true); + this.actionKeep = ko.observable(true); this.actionType = ko.observable(Enums.FiltersAction.MoveTo); @@ -79,6 +79,9 @@ case Enums.FiltersAction.Vacation: sResult = 'Vacation message @i18n'; break; + case Enums.FiltersAction.Reject: + sResult = 'Reject @i18n'; + break; case Enums.FiltersAction.Discard: sResult = 'Discard @i18n'; break; @@ -103,6 +106,9 @@ case Enums.FiltersAction.Vacation: sTemplate = 'SettingsFiltersActionVacation'; break; + case Enums.FiltersAction.Reject: + sTemplate = 'SettingsFiltersActionReject'; + break; case Enums.FiltersAction.None: sTemplate = 'SettingsFiltersActionNone'; break; @@ -161,6 +167,7 @@ if (-1 < Utils.inArray(this.actionType(), [ Enums.FiltersAction.MoveTo, Enums.FiltersAction.Forward, + Enums.FiltersAction.Reject, Enums.FiltersAction.Vacation ])) { @@ -197,8 +204,8 @@ 'ActionValueSecond': this.actionValueSecond(), 'ActionType': this.actionType(), + 'Keep': this.actionKeep() ? '1' : '0', 'MarkAsRead': this.actionMarkAsRead() ? '1' : '0', - 'KeepForward': this.keepForward() ? '1' : '0', 'SkipOthers': this.actionSkipOthers() ? '1' : '0' }; }; @@ -241,8 +248,8 @@ this.actionValue(Utils.pString(oItem['ActionValue'])); this.actionValueSecond(Utils.pString(oItem['ActionValueSecond'])); + this.actionKeep(!!oItem['Keep']); this.actionMarkAsRead(!!oItem['MarkAsRead']); - this.keepForward(!!oItem['KeepForward']); this.actionSkipOthers(!!oItem['SkipOthers']); bResult = true; @@ -274,7 +281,7 @@ oClone.actionValueSecond(this.actionValueSecond()); - oClone.keepForward(this.keepForward()); + oClone.actionKeep(this.actionKeep()); oClone.conditions(_.map(this.conditions(), function (oCondition) { return oCondition.cloneSelf(); diff --git a/dev/Settings/User/Filters.js b/dev/Settings/User/Filters.js index fa9e84cf8..e79cdbef2 100644 --- a/dev/Settings/User/Filters.js +++ b/dev/Settings/User/Filters.js @@ -10,6 +10,8 @@ Enums = require('Common/Enums'), Utils = require('Common/Utils'), + Data = require('Storage/User/Data'), + Remote = require('Storage/User/Remote') ; @@ -25,12 +27,19 @@ this.processText = ko.observable(''); this.visibility = ko.observable(false); + this.modules = Data.filterModules; + this.filters = ko.observableArray([]); this.filters.loading = ko.observable(false).extend({'throttle': 200}); this.filters.saving = ko.observable(false).extend({'throttle': 200}); this.filters.subscribe(Utils.windowResizeCallback); + this.filterRaw = ko.observable(''); + this.filterRaw.capa = ko.observable(''); + this.filterRaw.active = ko.observable(false); + this.filterRaw.allow = ko.observable(false); + this.processText = ko.computed(function () { return this.filters.loading() ? Utils.i18n('SETTINGS_FILTERS/LOADING_PROCESS') : ''; }, this); @@ -58,7 +67,7 @@ self.updateList(); } - }, this.filters()); + }, this.filters(), this.filterRaw(), this.filterRaw.active()); } return true; @@ -70,6 +79,14 @@ this.filters.subscribe(function () { this.haveChanges(true); }, this); + + this.filterRaw.subscribe(function () { + this.haveChanges(true); + }, this); + + this.filterRaw.active.subscribe(function () { + this.haveChanges(true); + }, this); } FiltersUserSettings.prototype.scrollableOptions = function () @@ -93,18 +110,28 @@ self.filters.loading(false); if (Enums.StorageResultType.Success === sResult && oData && - oData.Result && Utils.isArray(oData.Result)) + oData.Result && Utils.isArray(oData.Result.Filters)) { - var aResult = _.compact(_.map(oData.Result, function (aItem) { + var aResult = _.compact(_.map(oData.Result.Filters, function (aItem) { var oNew = new FilterModel(); return (oNew && oNew.parse(aItem)) ? oNew : null; })); self.filters(aResult); + + self.modules(oData.Result.Modules ? oData.Result.Modules : {}); + + self.filterRaw(oData.Result.Raw || ''); + self.filterRaw.capa(Utils.isArray(oData.Result.Capa) ? oData.Result.Capa.join(' ') : ''); + self.filterRaw.active(!!oData.Result.RawIsActive); + self.filterRaw.allow(!!oData.Result.RawIsAllow); } else { self.filters([]); + self.modules({}); + self.filterRaw(''); + self.filterRaw.capa({}); } self.haveChanges(false); @@ -130,6 +157,7 @@ require('View/Popup/Filter'), [oNew, function () { self.filters.push(oNew); self.haveChanges(true); + self.filterRaw.active(false); }, false]); }; diff --git a/dev/Storage/Admin/Remote.js b/dev/Storage/Admin/Remote.js index e768924d2..7002b6ece 100644 --- a/dev/Storage/Admin/Remote.js +++ b/dev/Storage/Admin/Remote.js @@ -212,7 +212,7 @@ RemoteAdminStorage.prototype.createOrUpdateDomain = function (fCallback, bCreate, sName, sIncHost, iIncPort, sIncSecure, bIncShortLogin, - bUseSieve, sSieveHost, iSievePort, sSieveSecure, + bUseSieve, sSieveAllowRaw, sSieveHost, iSievePort, sSieveSecure, sOutHost, iOutPort, sOutSecure, bOutShortLogin, bOutAuth, bOutPhpMail, sWhiteList) { @@ -226,6 +226,7 @@ 'IncShortLogin': bIncShortLogin ? '1' : '0', 'UseSieve': bUseSieve ? '1' : '0', + 'SieveAllowRaw': sSieveAllowRaw ? '1' : '0', 'SieveHost': sSieveHost, 'SievePort': iSievePort, 'SieveSecure': sSieveSecure, diff --git a/dev/Storage/User/Data.js b/dev/Storage/User/Data.js index 5ddb17b5c..62106220f 100644 --- a/dev/Storage/User/Data.js +++ b/dev/Storage/User/Data.js @@ -445,6 +445,7 @@ }, this); // other + this.filterModules = ko.observable({}); this.composeInEdit = ko.observable(false); this.capaOpenPGP = ko.observable(false); this.openpgpkeys = ko.observableArray([]); diff --git a/dev/Storage/User/Remote.js b/dev/Storage/User/Remote.js index 8d83241a5..833a26652 100644 --- a/dev/Storage/User/Remote.js +++ b/dev/Storage/User/Remote.js @@ -233,9 +233,12 @@ /** * @param {?Function} fCallback */ - RemoteUserStorage.prototype.filtersSave = function (fCallback, aFilters) + RemoteUserStorage.prototype.filtersSave = function (fCallback, + aFilters, sRaw, bRawIsActive) { this.defaultRequest(fCallback, 'FiltersSave', { + 'Raw': sRaw, + 'RawIsActive': bRawIsActive ? '1' : '0', 'Filters': _.map(aFilters, function (oItem) { return oItem.toJson(); }) diff --git a/dev/View/Popup/Domain.js b/dev/View/Popup/Domain.js index 732316111..cbd993bb8 100644 --- a/dev/View/Popup/Domain.js +++ b/dev/View/Popup/Domain.js @@ -70,6 +70,7 @@ this.imapSecure = ko.observable(Enums.ServerSecure.None); this.imapShortLogin = ko.observable(false); this.useSieve = ko.observable(false); + this.sieveAllowRaw = ko.observable(false); this.sieveServer = ko.observable(''); this.sievePort = ko.observable('' + Consts.Values.SieveDefaulPort); this.sieveSecure = ko.observable(Enums.ServerSecure.None); @@ -126,6 +127,7 @@ this.imapShortLogin(), this.useSieve(), + this.sieveAllowRaw(), this.sieveServer(), Utils.pInt(this.sievePort()), this.sieveSecure(), @@ -353,6 +355,7 @@ this.imapSecure(Utils.trim(oDomain.IncSecure)); this.imapShortLogin(!!oDomain.IncShortLogin); this.useSieve(!!oDomain.UseSieve); + this.sieveAllowRaw(!!oDomain.SieveAllowRaw); this.sieveServer(Utils.trim(oDomain.SieveHost)); this.sievePort('' + Utils.pInt(oDomain.SievePort)); this.sieveSecure(Utils.trim(oDomain.SieveSecure)); @@ -396,6 +399,7 @@ this.imapShortLogin(false); this.useSieve(false); + this.sieveAllowRaw(false); this.sieveServer(''); this.sievePort('' + Consts.Values.SieveDefaulPort); this.sieveSecure(Enums.ServerSecure.None); diff --git a/dev/View/Popup/Filter.js b/dev/View/Popup/Filter.js index 10c7bb634..5c079c476 100644 --- a/dev/View/Popup/Filter.js +++ b/dev/View/Popup/Filter.js @@ -29,6 +29,9 @@ this.fTrueCallback = null; this.filter = ko.observable(null); + this.modules = Data.filterModules; + this.allowMarkAsRead = ko.observable(false); + this.defautOptionsAfterRender = Utils.defautOptionsAfterRender; this.folderSelectList = Data.folderMenuForFilters; this.selectedFolderValue = ko.observable(''); @@ -68,13 +71,55 @@ return true; }); - this.actionTypeOptions = [ -// {'id': Enums.FiltersAction.None, 'name': 'None @i18n'}, - {'id': Enums.FiltersAction.MoveTo, 'name': ' Move to @i18n'}, - {'id': Enums.FiltersAction.Forward, 'name': 'Forward to @i18n'}, - {'id': Enums.FiltersAction.Vacation, 'name': 'Vacation message @i18n'}, - {'id': Enums.FiltersAction.Discard, 'name': 'Discard @i18n'} - ]; + this.actionTypeOptions = []; + this.fieldOptions = []; + this.typeOptions = []; + + this.populateOptions(); + + this.modules.subscribe(this.populateOptions, this) + + kn.constructorEnd(this); + } + + kn.extendAsViewModel(['View/Popup/Filter', 'PopupsFilterViewModel'], FilterPopupView); + _.extend(FilterPopupView.prototype, AbstractView.prototype); + + FilterPopupView.prototype.populateOptions = function () + { + this.actionTypeOptions = [] +// this.actionTypeOptions.push({'id': Enums.FiltersAction.None, 'name': 'None @i18n'}); + + var oModules = this.modules(); + if (oModules) + { + if (oModules.markasread) + { + this.allowMarkAsRead(true); + } + + if (oModules.moveto) + { + this.actionTypeOptions.push({'id': Enums.FiltersAction.MoveTo, 'name': 'Move to @i18n'}); + } + + if (oModules.redirect) + { + this.actionTypeOptions.push({'id': Enums.FiltersAction.Forward, 'name': 'Forward to @i18n'}); + } + + if (oModules.reject) + { + this.actionTypeOptions.push({'id': Enums.FiltersAction.Reject, 'name': 'Reject @i18n'}); + } + + if (oModules.vacation) + { + this.actionTypeOptions.push({'id': Enums.FiltersAction.Vacation, 'name': 'Vacation message @i18n'}); + } + } + + this.actionTypeOptions.push({'id': Enums.FiltersAction.Discard, 'name': 'Discard @i18n'}); this.fieldOptions = [ {'id': Enums.FilterConditionField.From, 'name': 'From @i18n'}, @@ -88,12 +133,8 @@ {'id': Enums.FilterConditionType.EqualTo, 'name': 'Equal To @i18n'}, {'id': Enums.FilterConditionType.NotEqualTo, 'name': 'Not Equal To @i18n'} ]; + }; - kn.constructorEnd(this); - } - - kn.extendAsViewModel(['View/Popup/Filter', 'PopupsFilterViewModel'], FilterPopupView); - _.extend(FilterPopupView.prototype, AbstractView.prototype); FilterPopupView.prototype.removeCondition = function (oConditionToDelete) { diff --git a/rainloop/v/0.0.0/app/libraries/MailSo/Sieve/ManageSieveClient.php b/rainloop/v/0.0.0/app/libraries/MailSo/Sieve/ManageSieveClient.php index 987e8ff33..ee6288e4c 100644 --- a/rainloop/v/0.0.0/app/libraries/MailSo/Sieve/ManageSieveClient.php +++ b/rainloop/v/0.0.0/app/libraries/MailSo/Sieve/ManageSieveClient.php @@ -70,7 +70,15 @@ class ManageSieveClient extends \MailSo\Net\NetClient */ public function IsModuleSupported($sModule) { - return $this->IsSupported('SIEVE') && \in_array(\strtoupper($sModule), $this->aModules); + return $this->IsSupported('SIEVE') && \in_array(\strtolower(\trim($sModule)), $this->aModules); + } + + /** + * @return array + */ + public function Modules() + { + return $this->aModules; } /** @@ -487,7 +495,7 @@ class ManageSieveClient extends \MailSo\Net\NetClient $this->aAuth = \explode(' ', \strtoupper($aTokens[1])); break; case 'SIEVE': - $this->aModules = \explode(' ', \strtoupper($aTokens[1])); + $this->aModules = \explode(' ', \strtolower($aTokens[1])); break; } } @@ -513,7 +521,6 @@ class ManageSieveClient extends \MailSo\Net\NetClient $this->IsConnected(true); - $sRequest = \trim($sRequest); $this->sendRaw($sRequest); } @@ -617,7 +624,7 @@ class ManageSieveClient extends \MailSo\Net\NetClient */ protected function getLogName() { - return 'MANAGE-SIEVE'; + return 'SIEVE'; } /** diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Actions.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Actions.php index 61abf59a4..7761e2fd4 100644 --- a/rainloop/v/0.0.0/app/libraries/RainLoop/Actions.php +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Actions.php @@ -251,8 +251,10 @@ class Actions case 'filters': // \RainLoop\Providers\Filters\FiltersInterface $oResult = new \RainLoop\Providers\Filters\SieveStorage( - !!$this->Config()->Get('labs', 'sieve_utf8_folder_name', true) + $this->Plugins(), $this->Config() ); + + $oResult->SetLogger($this->Logger()); break; case 'address-book': // \RainLoop\Providers\AddressBook\AddressBookInterface @@ -2053,7 +2055,9 @@ class Actions */ public function DoFilters() { - return $this->DefaultResponse(__FUNCTION__, $this->FiltersProvider()->Load()); + $oAccount = $this->getAccountFromToken(); + return $this->DefaultResponse(__FUNCTION__, + $this->FiltersProvider()->Load($oAccount, $oAccount->DomainSieveAllowRaw())); } /** @@ -2063,8 +2067,13 @@ class Actions */ public function DoFiltersSave() { + $oAccount = $this->getAccountFromToken(); + $aIncFilters = $this->GetActionParam('Filters', array()); + $sRaw = $this->GetActionParam('Raw', ''); + $bRawIsActive = '1' === (string) $this->GetActionParam('RawIsActive', '0'); + $aFilters = array(); foreach ($aIncFilters as $aFilter) { @@ -2078,8 +2087,8 @@ class Actions } } - return $this->DefaultResponse(__FUNCTION__, $this->FiltersProvider()->Save($aFilters)); -// return $this->TrueResponse(__FUNCTION__); + return $this->DefaultResponse(__FUNCTION__, $this->FiltersProvider()->Save($oAccount, + $aFilters, $sRaw, $bRawIsActive)); } /** diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Config/Application.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Config/Application.php index b60e36a3e..2a03ba565 100644 --- a/rainloop/v/0.0.0/app/libraries/RainLoop/Config/Application.php +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Config/Application.php @@ -116,7 +116,7 @@ class Application extends \RainLoop\Config\AbstractConfig 'admin_password' => array('12345'), 'allow_admin_panel' => array(true, 'Access settings'), 'allow_two_factor_auth' => array(false), - 'allow_universal_login' => array(true), + 'allow_universal_login' => array(false), 'admin_panel_host' => array(''), 'core_install_access_domain' => array('') ), diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Model/Account.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Model/Account.php index 27e94c515..9a2fe2269 100644 --- a/rainloop/v/0.0.0/app/libraries/RainLoop/Model/Account.php +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Model/Account.php @@ -305,6 +305,38 @@ class Account extends \RainLoop\Account // for backward compatibility return $this->Domain()->OutAuth(); } + /** + * @return string + */ + public function DomainSieveHost() + { + return $this->Domain()->SieveHost(); + } + + /** + * @return int + */ + public function DomainSievePort() + { + return $this->Domain()->SievePort(); + } + + /** + * @return int + */ + public function DomainSieveSecure() + { + return $this->Domain()->SieveSecure(); + } + + /** + * @return bool + */ + public function DomainSieveAllowRaw() + { + return $this->Domain()->SieveAllowRaw(); + } + /** * @return string */ @@ -443,4 +475,52 @@ class Account extends \RainLoop\Account // for backward compatibility return $bLogin; } + + /** + * @param \RainLoop\Plugins\Manager $oPlugins + * @param \MailSo\Sieve\ManageSieveClient $oSieveClient + * @param \RainLoop\Application $oConfig + */ + public function SieveConnectAndLoginHelper($oPlugins, $oSieveClient, $oConfig) + { + $bLogin = false; + + $aSieveCredentials = array( + 'UseConnect' => true, + 'UseAuth' => true, + 'Host' => $this->DomainSieveHost(), + 'Port' => $this->DomainSievePort(), + 'Secure' => $this->DomainSieveSecure(), + 'Login' => $this->IncLogin(), + 'Password' => $this->Password(), + 'VerifySsl' => !!$oConfig->Get('ssl', 'verify_certificate', false), + 'AllowSelfSigned' => !!$oConfig->Get('ssl', 'allow_self_signed', true) + ); + + $oPlugins->RunHook('filter.sieve-credentials', array($this, &$aSieveCredentials)); + + $oPlugins->RunHook('event.sieve-pre-connect', array($this, $aSieveCredentials['UseConnect'], $aSieveCredentials)); + + if ($aSieveCredentials['UseConnect'] && $oSieveClient) + { + $oSieveClient->Connect($aSieveCredentials['Host'], $aSieveCredentials['Port'], + $aSieveCredentials['Secure'], $aSieveCredentials['VerifySsl'], $aSieveCredentials['AllowSelfSigned'] + ); + } + + $oPlugins->RunHook('event.sieve-post-connect', array($this, $aSieveCredentials['UseConnect'], $aSieveCredentials)); + + $oPlugins->RunHook('event.sieve-pre-login', array($this, $aSieveCredentials['UseAuth'], $aSieveCredentials)); + + if ($aSieveCredentials['UseAuth']) + { + $oSieveClient->Login($aSieveCredentials['Login'], $aSieveCredentials['Password']); + + $bLogin = true; + } + + $oPlugins->RunHook('event.sieve-post-login', array($this, $aSieveCredentials['UseAuth'], $bLogin, $aSieveCredentials)); + + return $bLogin; + } } diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Model/Domain.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Model/Domain.php index c2873ea80..476f3d77a 100644 --- a/rainloop/v/0.0.0/app/libraries/RainLoop/Model/Domain.php +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Model/Domain.php @@ -83,6 +83,11 @@ class Domain */ private $iSieveSecure; + /** + * @var bool + */ + private $bSieveAllowRaw; + /** * @var string */ @@ -130,6 +135,8 @@ class Domain $this->iSievePort = $iSievePort; $this->iSieveSecure = $iSieveSecure; + $this->bSieveAllowRaw = false; + $this->sWhiteList = \trim($sWhiteList); } @@ -184,6 +191,7 @@ class Domain !empty($aDomain['imap_secure']) ? $aDomain['imap_secure'] : ''); $bUseSieve = isset($aDomain['sieve_use']) ? (bool) $aDomain['sieve_use'] : false; + $bSieveAllowRaw = isset($aDomain['sieve_allow_raw']) ? (bool) $aDomain['sieve_allow_raw'] : false; $sSieveHost = empty($aDomain['sieve_host']) ? '' : (string) $aDomain['sieve_host']; $iSievePort = empty($aDomain['sieve_port']) ? 2000 : (int) $aDomain['sieve_port']; @@ -207,6 +215,8 @@ class Domain $bUseSieve, $sSieveHost, $iSievePort, $iSieveSecure, $sOutHost, $iOutPort, $iOutSecure, $bOutShortLogin, $bOutAuth, $bOutUsePhpMail, $sWhiteList); + + $oDomain->SetSieveAllowRaw($bSieveAllowRaw); } return $oDomain; @@ -257,6 +267,7 @@ class Domain 'imap_secure = "'.self::ConstConnectionSecurityTypeToStr($this->iIncSecure).'"', 'imap_short_login = '.($this->bIncShortLogin ? 'On' : 'Off'), 'sieve_use = '.($this->bUseSieve ? 'On' : 'Off'), + 'sieve_allow_raw = '.($this->bSieveAllowRaw ? 'On' : 'Off'), 'sieve_host = "'.$this->encodeIniString($this->sSieveHost).'"', 'sieve_port = '.$this->iSievePort, 'sieve_secure = "'.self::ConstConnectionSecurityTypeToStr($this->iSieveSecure).'"', @@ -427,6 +438,22 @@ class Domain return $this->iSieveSecure; } + /** + * @return bool + */ + public function SieveAllowRaw() + { + return $this->bSieveAllowRaw; + } + + /** + * @param bool $bSieveAllowRaw + */ + public function SetSieveAllowRaw($bSieveAllowRaw) + { + $this->bSieveAllowRaw = !!$bSieveAllowRaw; + } + /** * @return string */ @@ -524,6 +551,7 @@ class Domain 'SieveHost' => $bAjax ? \MailSo\Base\Utils::IdnToUtf8($this->SieveHost()) : $this->SieveHost(), 'SievePort' => $this->SievePort(), 'SieveSecure' => $this->SieveSecure(), + 'SieveAllowRaw' => $this->SieveAllowRaw(), 'OutHost' => $bAjax ? \MailSo\Base\Utils::IdnToUtf8($this->OutHost()) : $this->OutHost(), 'OutPort' => $this->OutPort(), 'OutSecure' => $this->OutSecure(), diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Domain.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Domain.php index 5df222f4e..f7ce65253 100644 --- a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Domain.php +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Domain.php @@ -130,6 +130,7 @@ class Domain extends \RainLoop\Providers\AbstractProvider $iIncSecure = (int) $oActions->GetActionParam('IncSecure', \MailSo\Net\Enumerations\ConnectionSecurityType::NONE); $bIncShortLogin = '1' === (string) $oActions->GetActionParam('IncShortLogin', '0'); $bUseSieve = '1' === (string) $oActions->GetActionParam('UseSieve', '0'); + $bSieveAllowRaw = '1' === (string) $oActions->GetActionParam('SieveAllowRaw', '0'); $sSieveHost = (string) $oActions->GetActionParam('SieveHost', ''); $iSievePort = (int) $oActions->GetActionParam('SievePort', 2000); $iSieveSecure = (int) $oActions->GetActionParam('SieveSecure', \MailSo\Net\Enumerations\ConnectionSecurityType::NONE); @@ -173,6 +174,11 @@ class Domain extends \RainLoop\Providers\AbstractProvider $sWhiteList); } } + + if ($oDomain) + { + $oDomain->SetSieveAllowRaw($bSieveAllowRaw); + } } return $oDomain; diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Filters.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Filters.php index cf0398763..589058c95 100644 --- a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Filters.php +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Filters.php @@ -18,21 +18,28 @@ class Filters extends \RainLoop\Providers\AbstractProvider } /** + * @param \RainLoop\Account $oAccount + * @param bool $bAllowRaw = false + * * @return array */ - public function Load() + public function Load($oAccount, $bAllowRaw = false) { - return $this->IsActive() ? $this->oDriver->Load() : array(); + return $this->IsActive() ? $this->oDriver->Load($oAccount, $bAllowRaw) : array(); } /** + * @param \RainLoop\Account $oAccount * @param array $aFilters + * @param string $sRaw = '' + * @param bool $bRawIsActive = false * * @return bool */ - public function Save($aFilters) + public function Save($oAccount, $aFilters, $sRaw = '', $bRawIsActive = false) { - return $this->IsActive() ? $this->oDriver->Save($aFilters) : false; + return $this->IsActive() ? $this->oDriver->Save($oAccount, + $aFilters, $sRaw, $bRawIsActive) : false; } /** diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Filters/Classes/Filter.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Filters/Classes/Filter.php index e21bf73fd..cee63e5a7 100644 --- a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Filters/Classes/Filter.php +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Filters/Classes/Filter.php @@ -57,7 +57,7 @@ class Filter /** * @var bool */ - private $bKeepForward; + private $bKeep; public function __construct() { @@ -81,7 +81,7 @@ class Filter $this->bMarkAsRead = false; $this->bSkipOthers = false; - $this->bKeepForward = true; + $this->bKeep = true; } /** @@ -167,9 +167,9 @@ class Filter /** * @return bool */ - public function KeepForward() + public function Keep() { - return $this->bKeepForward; + return $this->bKeep; } /** @@ -217,9 +217,9 @@ class Filter $this->sActionValue = isset($aFilter['ActionValue']) ? $aFilter['ActionValue'] : ''; $this->sActionValueSecond = isset($aFilter['ActionValueSecond']) ? $aFilter['ActionValueSecond'] : ''; + $this->bKeep = isset($aFilter['Keep']) ? '1' === (string) $aFilter['Keep'] : true; $this->bMarkAsRead = isset($aFilter['MarkAsRead']) ? '1' === (string) $aFilter['MarkAsRead'] : false; $this->bSkipOthers = isset($aFilter['SkipOthers']) ? '1' === (string) $aFilter['SkipOthers'] : false; - $this->bKeepForward = isset($aFilter['KeepForward']) ? '1' === (string) $aFilter['KeepForward'] : true; $this->aConditions = \RainLoop\Providers\Filters\Classes\FilterCondition::CollectionFromJSON( isset($aFilter['Conditions']) ? $aFilter['Conditions'] : array()); @@ -255,6 +255,7 @@ class Filter 'ActionType' => $this->ActionType(), 'ActionValue' => $this->ActionValue(), 'ActionValueSecond' => $this->ActionValueSecond(), + 'Keep' => $this->Keep(), 'MarkAsRead' => $this->MarkAsRead(), 'SkipOthers' => $this->SkipOthers() ); diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Filters/Enumerations/ActionType.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Filters/Enumerations/ActionType.php index 9d7b7bf9a..c5b87d9b3 100644 --- a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Filters/Enumerations/ActionType.php +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Filters/Enumerations/ActionType.php @@ -8,5 +8,6 @@ class ActionType const MOVE_TO = 'MoveTo'; const DISCARD = 'Discard'; const VACATION = 'Vacation'; + const REJECT = 'Reject'; const FORWARD = 'Forward'; } diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Filters/FiltersInterface.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Filters/FiltersInterface.php index cab95f454..2c0a5120d 100644 --- a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Filters/FiltersInterface.php +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Filters/FiltersInterface.php @@ -5,14 +5,18 @@ namespace RainLoop\Providers\Filters; interface FiltersInterface { /** + * @param \RainLoop\Account $oAccount + * @param bool $bAllowRaw = false + * * @return array */ - public function Load(); + public function Load($oAccount, $bAllowRaw = false); /** + * @param \RainLoop\Account $oAccount * @param array $aFilters * * @return bool */ - public function Save($aFilters); + public function Save($oAccount, $aFilters); } diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Filters/SieveStorage.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Filters/SieveStorage.php index a75a807fc..59803ee6c 100644 --- a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Filters/SieveStorage.php +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Filters/SieveStorage.php @@ -4,7 +4,25 @@ namespace RainLoop\Providers\Filters; class SieveStorage implements \RainLoop\Providers\Filters\FiltersInterface { - const NEW_LINE = "\n"; + const NEW_LINE = "\r\n"; + + const SIEVE_FILE_NAME = 'rainloop.user'; + const SIEVE_FILE_NAME_RAW = 'rainloop.raw'; + + /** + * @var \MailSo\Log\Logger + */ + private $oLogger; + + /** + * @var \RainLoop\Plugins\Manager + */ + private $oPlugins; + + /** + * @var \RainLoop\Application + */ + private $oConfig; /** * @var bool @@ -12,31 +30,130 @@ class SieveStorage implements \RainLoop\Providers\Filters\FiltersInterface private $bUtf8FolderName; /** - * @param bool $bUtf8FolderName = true - * * @return void */ - public function __construct($bUtf8FolderName = true) + public function __construct($oPlugins, $oConfig) { - $this->bUtf8FolderName = !!$bUtf8FolderName; + $this->oLogger = null; + + $this->oPlugins = $oPlugins; + $this->oConfig = $oConfig; + + $this->bUtf8FolderName = !!$this->oConfig->Get('labs', 'sieve_utf8_folder_name', true); } /** + * @param \RainLoop\Model\Account $oAccount + * @param bool $bAllowRaw = false + * * @return array */ - public function Load() + public function Load($oAccount, $bAllowRaw = false) { - return $this->fileStringToCollection(@\file_get_contents('e:/sieve.txt')); + $sRaw = ''; + + $bBasicIsActive = false; + $bRawIsActive = false; + + $aModules = array(); + $aFilters = array(); + + $oSieveClient = \MailSo\Sieve\ManageSieveClient::NewInstance()->SetLogger($this->oLogger); + + if ($oAccount->SieveConnectAndLoginHelper($this->oPlugins, $oSieveClient, $this->oConfig)) + { + $aModules = $oSieveClient->Modules(); + $aList = $oSieveClient->ListScripts(); + + if (\is_array($aList) && 0 < \count($aList)) + { + if (isset($aList[self::SIEVE_FILE_NAME])) + { + $bBasicIsActive = !!$aList[self::SIEVE_FILE_NAME]; + $sS = $oSieveClient->GetScript(self::SIEVE_FILE_NAME); + if ($sS) + { + $aFilters = $this->fileStringToCollection($sS); + } + } + + if ($bAllowRaw && isset($aList[self::SIEVE_FILE_NAME_RAW])) + { + $bRawIsActive = !!$aList[self::SIEVE_FILE_NAME_RAW]; + $sRaw = \trim($oSieveClient->GetScript(self::SIEVE_FILE_NAME_RAW)); + } + } + + $oSieveClient->LogoutAndDisconnect(); + } + + return array( + 'RawIsAllow' => $bAllowRaw, + 'RawIsActive' => $bRawIsActive, + 'Raw' => $bAllowRaw ? $sRaw : '', + 'Filters' => !$bBasicIsActive && !$bRawIsActive ? array() : $aFilters, + 'Capa' => $bAllowRaw ? $aModules : array(), + 'Modules' => array( + 'redirect' => \in_array('fileinto', $aModules), + 'moveto' => \in_array('fileinto', $aModules), + 'reject' => \in_array('reject', $aModules), + 'vacation' => \in_array('vacation', $aModules), + 'markasread' => \in_array('imap4flags', $aModules) && false + ) + ); } /** + * @param \RainLoop\Model\Account $oAccount * @param array $aFilters + * @param string $sRaw = '' + * @param bool $bRawIsActive = false * * @return bool */ - public function Save($aFilters) + public function Save($oAccount, $aFilters, $sRaw = '', $bRawIsActive = false) { - return @\file_put_contents('e:/sieve.txt', $this->collectionToFileString($aFilters)); + $oSieveClient = \MailSo\Sieve\ManageSieveClient::NewInstance()->SetLogger($this->oLogger); + + if ($oAccount->SieveConnectAndLoginHelper($this->oPlugins, $oSieveClient, $this->oConfig)) + { + $aList = $oSieveClient->ListScripts(); + + $sUserFilter = $this->collectionToFileString($aFilters); + + if (!empty($sUserFilter)) + { + $oSieveClient->PutScript(self::SIEVE_FILE_NAME, $sUserFilter); + if (!$bRawIsActive) + { + $oSieveClient->SetActiveScript(self::SIEVE_FILE_NAME); + } + } + else if (isset($aList[self::SIEVE_FILE_NAME])) + { + $oSieveClient->DeleteScript(self::SIEVE_FILE_NAME); + } + + $sRaw = \trim($sRaw); + if (!empty($sRaw)) + { + $oSieveClient->PutScript(self::SIEVE_FILE_NAME_RAW, $sRaw); + if ($bRawIsActive) + { + $oSieveClient->SetActiveScript(self::SIEVE_FILE_NAME_RAW); + } + } + else if (isset($aList[self::SIEVE_FILE_NAME_RAW])) + { + $oSieveClient->DeleteScript(self::SIEVE_FILE_NAME_RAW); + } + + $oSieveClient->LogoutAndDisconnect(); + + return true; + } + + return false; } /** @@ -221,20 +338,31 @@ class SieveStorage implements \RainLoop\Providers\Filters\FiltersInterface $aResult[] = $sTab.'# @Error (vacation): empty action value'; } break; + case \RainLoop\Providers\Filters\Enumerations\ActionType::REJECT: + $sValue = \trim($oFilter->ActionValue()); + if (0 < \strlen($sValue)) + { + $aCapa['reject'] = true; + + $aResult[] = $sTab.'reject "'.$this->quote($sValue).'";'; + $aResult[] = $sTab.'stop;'; + } + else + { + $aResult[] = $sTab.'# @Error (reject): empty action value'; + } + break; case \RainLoop\Providers\Filters\Enumerations\ActionType::FORWARD: $sValue = $oFilter->ActionValue(); if (0 < \strlen($sValue)) { - if ($oFilter->KeepForward()) + if ($oFilter->Keep()) { - $aCapa['copy'] = true; - $aResult[] = $sTab.'redirect :copy "'.$this->quote($sValue).'";'; - } - else - { - $aResult[] = $sTab.'redirect "'.$this->quote($sValue).'";'; + $aCapa['fileinto'] = true; + $aResult[] = $sTab.'fileinto "INBOX";'; } + $aResult[] = $sTab.'redirect "'.$this->quote($sValue).'";'; $aResult[] = $sTab.'stop;'; } else @@ -246,8 +374,6 @@ class SieveStorage implements \RainLoop\Providers\Filters\FiltersInterface $sValue = $oFilter->ActionValue(); if (0 < \strlen($sValue)) { - $aCapa['fileinto'] = true; - $sFolderName = $sValue; // utf7-imap if ($this->bUtf8FolderName) // to utf-8 { @@ -256,6 +382,7 @@ class SieveStorage implements \RainLoop\Providers\Filters\FiltersInterface \MailSo\Base\Enumerations\Charset::UTF_8); } + $aCapa['fileinto'] = true; $aResult[] = $sTab.'fileinto "'.$this->quote($sFolderName).'";'; $aResult[] = $sTab.'stop;'; } @@ -359,4 +486,12 @@ class SieveStorage implements \RainLoop\Providers\Filters\FiltersInterface { return \str_replace(array('\\', '"'), array('\\\\', '\\"'), \trim($sValue)); } + + /** + * @param \MailSo\Log\Logger $oLogger + */ + public function SetLogger($oLogger) + { + $this->oLogger = $oLogger instanceof \MailSo\Log\Logger ? $oLogger : null; + } } diff --git a/rainloop/v/0.0.0/app/templates/Views/Admin/PopupsDomain.html b/rainloop/v/0.0.0/app/templates/Views/Admin/PopupsDomain.html index 55eb77abd..08f44a3ec 100644 --- a/rainloop/v/0.0.0/app/templates/Views/Admin/PopupsDomain.html +++ b/rainloop/v/0.0.0/app/templates/Views/Admin/PopupsDomain.html @@ -84,13 +84,25 @@