Sieve filters (alpha / beta version coming soon)

This commit is contained in:
RainLoop Team 2015-01-24 02:35:42 +04:00
parent 02ba68868f
commit 94789632f0
33 changed files with 544 additions and 81 deletions

75
build/langs.php Normal file
View file

@ -0,0 +1,75 @@
<?php
define('LANGS_PATH', __DIR__.'/../rainloop/v/0.0.0/langs');
function getLangStructure($sLangFile)
{
$sEngLang = \file_get_contents(LANGS_PATH.'/'.$sLangFile);
return $sEngLang ? \parse_ini_string($sEngLang, true) : null;
}
function mergeLangStructure($aFromLang, $aEngLang, &$iCount = 0)
{
$iCount = 0;
foreach ($aEngLang as $sSectionKey => $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);
}

View file

@ -307,6 +307,7 @@
'MoveTo': 'MoveTo', 'MoveTo': 'MoveTo',
'Discard': 'Discard', 'Discard': 'Discard',
'Vacation': 'Vacation', 'Vacation': 'Vacation',
'Reject': 'Reject',
'Forward': 'Forward' 'Forward': 'Forward'
}; };

View file

@ -45,7 +45,7 @@
this.actionSkipOthers = ko.observable(false); this.actionSkipOthers = ko.observable(false);
this.keepForward = ko.observable(true); this.actionKeep = ko.observable(true);
this.actionType = ko.observable(Enums.FiltersAction.MoveTo); this.actionType = ko.observable(Enums.FiltersAction.MoveTo);
@ -79,6 +79,9 @@
case Enums.FiltersAction.Vacation: case Enums.FiltersAction.Vacation:
sResult = 'Vacation message @i18n'; sResult = 'Vacation message @i18n';
break; break;
case Enums.FiltersAction.Reject:
sResult = 'Reject @i18n';
break;
case Enums.FiltersAction.Discard: case Enums.FiltersAction.Discard:
sResult = 'Discard @i18n'; sResult = 'Discard @i18n';
break; break;
@ -103,6 +106,9 @@
case Enums.FiltersAction.Vacation: case Enums.FiltersAction.Vacation:
sTemplate = 'SettingsFiltersActionVacation'; sTemplate = 'SettingsFiltersActionVacation';
break; break;
case Enums.FiltersAction.Reject:
sTemplate = 'SettingsFiltersActionReject';
break;
case Enums.FiltersAction.None: case Enums.FiltersAction.None:
sTemplate = 'SettingsFiltersActionNone'; sTemplate = 'SettingsFiltersActionNone';
break; break;
@ -161,6 +167,7 @@
if (-1 < Utils.inArray(this.actionType(), [ if (-1 < Utils.inArray(this.actionType(), [
Enums.FiltersAction.MoveTo, Enums.FiltersAction.MoveTo,
Enums.FiltersAction.Forward, Enums.FiltersAction.Forward,
Enums.FiltersAction.Reject,
Enums.FiltersAction.Vacation Enums.FiltersAction.Vacation
])) ]))
{ {
@ -197,8 +204,8 @@
'ActionValueSecond': this.actionValueSecond(), 'ActionValueSecond': this.actionValueSecond(),
'ActionType': this.actionType(), 'ActionType': this.actionType(),
'Keep': this.actionKeep() ? '1' : '0',
'MarkAsRead': this.actionMarkAsRead() ? '1' : '0', 'MarkAsRead': this.actionMarkAsRead() ? '1' : '0',
'KeepForward': this.keepForward() ? '1' : '0',
'SkipOthers': this.actionSkipOthers() ? '1' : '0' 'SkipOthers': this.actionSkipOthers() ? '1' : '0'
}; };
}; };
@ -241,8 +248,8 @@
this.actionValue(Utils.pString(oItem['ActionValue'])); this.actionValue(Utils.pString(oItem['ActionValue']));
this.actionValueSecond(Utils.pString(oItem['ActionValueSecond'])); this.actionValueSecond(Utils.pString(oItem['ActionValueSecond']));
this.actionKeep(!!oItem['Keep']);
this.actionMarkAsRead(!!oItem['MarkAsRead']); this.actionMarkAsRead(!!oItem['MarkAsRead']);
this.keepForward(!!oItem['KeepForward']);
this.actionSkipOthers(!!oItem['SkipOthers']); this.actionSkipOthers(!!oItem['SkipOthers']);
bResult = true; bResult = true;
@ -274,7 +281,7 @@
oClone.actionValueSecond(this.actionValueSecond()); oClone.actionValueSecond(this.actionValueSecond());
oClone.keepForward(this.keepForward()); oClone.actionKeep(this.actionKeep());
oClone.conditions(_.map(this.conditions(), function (oCondition) { oClone.conditions(_.map(this.conditions(), function (oCondition) {
return oCondition.cloneSelf(); return oCondition.cloneSelf();

View file

@ -10,6 +10,8 @@
Enums = require('Common/Enums'), Enums = require('Common/Enums'),
Utils = require('Common/Utils'), Utils = require('Common/Utils'),
Data = require('Storage/User/Data'),
Remote = require('Storage/User/Remote') Remote = require('Storage/User/Remote')
; ;
@ -25,12 +27,19 @@
this.processText = ko.observable(''); this.processText = ko.observable('');
this.visibility = ko.observable(false); this.visibility = ko.observable(false);
this.modules = Data.filterModules;
this.filters = ko.observableArray([]); this.filters = ko.observableArray([]);
this.filters.loading = ko.observable(false).extend({'throttle': 200}); this.filters.loading = ko.observable(false).extend({'throttle': 200});
this.filters.saving = ko.observable(false).extend({'throttle': 200}); this.filters.saving = ko.observable(false).extend({'throttle': 200});
this.filters.subscribe(Utils.windowResizeCallback); 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 () { this.processText = ko.computed(function () {
return this.filters.loading() ? Utils.i18n('SETTINGS_FILTERS/LOADING_PROCESS') : ''; return this.filters.loading() ? Utils.i18n('SETTINGS_FILTERS/LOADING_PROCESS') : '';
}, this); }, this);
@ -58,7 +67,7 @@
self.updateList(); self.updateList();
} }
}, this.filters()); }, this.filters(), this.filterRaw(), this.filterRaw.active());
} }
return true; return true;
@ -70,6 +79,14 @@
this.filters.subscribe(function () { this.filters.subscribe(function () {
this.haveChanges(true); this.haveChanges(true);
}, this); }, this);
this.filterRaw.subscribe(function () {
this.haveChanges(true);
}, this);
this.filterRaw.active.subscribe(function () {
this.haveChanges(true);
}, this);
} }
FiltersUserSettings.prototype.scrollableOptions = function () FiltersUserSettings.prototype.scrollableOptions = function ()
@ -93,18 +110,28 @@
self.filters.loading(false); self.filters.loading(false);
if (Enums.StorageResultType.Success === sResult && oData && 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(); var oNew = new FilterModel();
return (oNew && oNew.parse(aItem)) ? oNew : null; return (oNew && oNew.parse(aItem)) ? oNew : null;
})); }));
self.filters(aResult); 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 else
{ {
self.filters([]); self.filters([]);
self.modules({});
self.filterRaw('');
self.filterRaw.capa({});
} }
self.haveChanges(false); self.haveChanges(false);
@ -130,6 +157,7 @@
require('View/Popup/Filter'), [oNew, function () { require('View/Popup/Filter'), [oNew, function () {
self.filters.push(oNew); self.filters.push(oNew);
self.haveChanges(true); self.haveChanges(true);
self.filterRaw.active(false);
}, false]); }, false]);
}; };

View file

@ -212,7 +212,7 @@
RemoteAdminStorage.prototype.createOrUpdateDomain = function (fCallback, RemoteAdminStorage.prototype.createOrUpdateDomain = function (fCallback,
bCreate, sName, bCreate, sName,
sIncHost, iIncPort, sIncSecure, bIncShortLogin, sIncHost, iIncPort, sIncSecure, bIncShortLogin,
bUseSieve, sSieveHost, iSievePort, sSieveSecure, bUseSieve, sSieveAllowRaw, sSieveHost, iSievePort, sSieveSecure,
sOutHost, iOutPort, sOutSecure, bOutShortLogin, bOutAuth, bOutPhpMail, sOutHost, iOutPort, sOutSecure, bOutShortLogin, bOutAuth, bOutPhpMail,
sWhiteList) sWhiteList)
{ {
@ -226,6 +226,7 @@
'IncShortLogin': bIncShortLogin ? '1' : '0', 'IncShortLogin': bIncShortLogin ? '1' : '0',
'UseSieve': bUseSieve ? '1' : '0', 'UseSieve': bUseSieve ? '1' : '0',
'SieveAllowRaw': sSieveAllowRaw ? '1' : '0',
'SieveHost': sSieveHost, 'SieveHost': sSieveHost,
'SievePort': iSievePort, 'SievePort': iSievePort,
'SieveSecure': sSieveSecure, 'SieveSecure': sSieveSecure,

View file

@ -445,6 +445,7 @@
}, this); }, this);
// other // other
this.filterModules = ko.observable({});
this.composeInEdit = ko.observable(false); this.composeInEdit = ko.observable(false);
this.capaOpenPGP = ko.observable(false); this.capaOpenPGP = ko.observable(false);
this.openpgpkeys = ko.observableArray([]); this.openpgpkeys = ko.observableArray([]);

View file

@ -233,9 +233,12 @@
/** /**
* @param {?Function} fCallback * @param {?Function} fCallback
*/ */
RemoteUserStorage.prototype.filtersSave = function (fCallback, aFilters) RemoteUserStorage.prototype.filtersSave = function (fCallback,
aFilters, sRaw, bRawIsActive)
{ {
this.defaultRequest(fCallback, 'FiltersSave', { this.defaultRequest(fCallback, 'FiltersSave', {
'Raw': sRaw,
'RawIsActive': bRawIsActive ? '1' : '0',
'Filters': _.map(aFilters, function (oItem) { 'Filters': _.map(aFilters, function (oItem) {
return oItem.toJson(); return oItem.toJson();
}) })

View file

@ -70,6 +70,7 @@
this.imapSecure = ko.observable(Enums.ServerSecure.None); this.imapSecure = ko.observable(Enums.ServerSecure.None);
this.imapShortLogin = ko.observable(false); this.imapShortLogin = ko.observable(false);
this.useSieve = ko.observable(false); this.useSieve = ko.observable(false);
this.sieveAllowRaw = ko.observable(false);
this.sieveServer = ko.observable(''); this.sieveServer = ko.observable('');
this.sievePort = ko.observable('' + Consts.Values.SieveDefaulPort); this.sievePort = ko.observable('' + Consts.Values.SieveDefaulPort);
this.sieveSecure = ko.observable(Enums.ServerSecure.None); this.sieveSecure = ko.observable(Enums.ServerSecure.None);
@ -126,6 +127,7 @@
this.imapShortLogin(), this.imapShortLogin(),
this.useSieve(), this.useSieve(),
this.sieveAllowRaw(),
this.sieveServer(), this.sieveServer(),
Utils.pInt(this.sievePort()), Utils.pInt(this.sievePort()),
this.sieveSecure(), this.sieveSecure(),
@ -353,6 +355,7 @@
this.imapSecure(Utils.trim(oDomain.IncSecure)); this.imapSecure(Utils.trim(oDomain.IncSecure));
this.imapShortLogin(!!oDomain.IncShortLogin); this.imapShortLogin(!!oDomain.IncShortLogin);
this.useSieve(!!oDomain.UseSieve); this.useSieve(!!oDomain.UseSieve);
this.sieveAllowRaw(!!oDomain.SieveAllowRaw);
this.sieveServer(Utils.trim(oDomain.SieveHost)); this.sieveServer(Utils.trim(oDomain.SieveHost));
this.sievePort('' + Utils.pInt(oDomain.SievePort)); this.sievePort('' + Utils.pInt(oDomain.SievePort));
this.sieveSecure(Utils.trim(oDomain.SieveSecure)); this.sieveSecure(Utils.trim(oDomain.SieveSecure));
@ -396,6 +399,7 @@
this.imapShortLogin(false); this.imapShortLogin(false);
this.useSieve(false); this.useSieve(false);
this.sieveAllowRaw(false);
this.sieveServer(''); this.sieveServer('');
this.sievePort('' + Consts.Values.SieveDefaulPort); this.sievePort('' + Consts.Values.SieveDefaulPort);
this.sieveSecure(Enums.ServerSecure.None); this.sieveSecure(Enums.ServerSecure.None);

View file

@ -29,6 +29,9 @@
this.fTrueCallback = null; this.fTrueCallback = null;
this.filter = ko.observable(null); this.filter = ko.observable(null);
this.modules = Data.filterModules;
this.allowMarkAsRead = ko.observable(false);
this.defautOptionsAfterRender = Utils.defautOptionsAfterRender; this.defautOptionsAfterRender = Utils.defautOptionsAfterRender;
this.folderSelectList = Data.folderMenuForFilters; this.folderSelectList = Data.folderMenuForFilters;
this.selectedFolderValue = ko.observable(''); this.selectedFolderValue = ko.observable('');
@ -68,13 +71,55 @@
return true; return true;
}); });
this.actionTypeOptions = [ this.actionTypeOptions = [];
// {'id': Enums.FiltersAction.None, 'name': 'None @i18n'}, this.fieldOptions = [];
{'id': Enums.FiltersAction.MoveTo, 'name': ' Move to @i18n'}, this.typeOptions = [];
{'id': Enums.FiltersAction.Forward, 'name': 'Forward to @i18n'},
{'id': Enums.FiltersAction.Vacation, 'name': 'Vacation message @i18n'}, this.populateOptions();
{'id': Enums.FiltersAction.Discard, 'name': 'Discard @i18n'}
]; 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 = [ this.fieldOptions = [
{'id': Enums.FilterConditionField.From, 'name': 'From @i18n'}, {'id': Enums.FilterConditionField.From, 'name': 'From @i18n'},
@ -88,12 +133,8 @@
{'id': Enums.FilterConditionType.EqualTo, 'name': 'Equal To @i18n'}, {'id': Enums.FilterConditionType.EqualTo, 'name': 'Equal To @i18n'},
{'id': Enums.FilterConditionType.NotEqualTo, 'name': 'Not 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) FilterPopupView.prototype.removeCondition = function (oConditionToDelete)
{ {

View file

@ -70,7 +70,15 @@ class ManageSieveClient extends \MailSo\Net\NetClient
*/ */
public function IsModuleSupported($sModule) 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])); $this->aAuth = \explode(' ', \strtoupper($aTokens[1]));
break; break;
case 'SIEVE': case 'SIEVE':
$this->aModules = \explode(' ', \strtoupper($aTokens[1])); $this->aModules = \explode(' ', \strtolower($aTokens[1]));
break; break;
} }
} }
@ -513,7 +521,6 @@ class ManageSieveClient extends \MailSo\Net\NetClient
$this->IsConnected(true); $this->IsConnected(true);
$sRequest = \trim($sRequest);
$this->sendRaw($sRequest); $this->sendRaw($sRequest);
} }
@ -617,7 +624,7 @@ class ManageSieveClient extends \MailSo\Net\NetClient
*/ */
protected function getLogName() protected function getLogName()
{ {
return 'MANAGE-SIEVE'; return 'SIEVE';
} }
/** /**

View file

@ -251,8 +251,10 @@ class Actions
case 'filters': case 'filters':
// \RainLoop\Providers\Filters\FiltersInterface // \RainLoop\Providers\Filters\FiltersInterface
$oResult = new \RainLoop\Providers\Filters\SieveStorage( $oResult = new \RainLoop\Providers\Filters\SieveStorage(
!!$this->Config()->Get('labs', 'sieve_utf8_folder_name', true) $this->Plugins(), $this->Config()
); );
$oResult->SetLogger($this->Logger());
break; break;
case 'address-book': case 'address-book':
// \RainLoop\Providers\AddressBook\AddressBookInterface // \RainLoop\Providers\AddressBook\AddressBookInterface
@ -2053,7 +2055,9 @@ class Actions
*/ */
public function DoFilters() 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() public function DoFiltersSave()
{ {
$oAccount = $this->getAccountFromToken();
$aIncFilters = $this->GetActionParam('Filters', array()); $aIncFilters = $this->GetActionParam('Filters', array());
$sRaw = $this->GetActionParam('Raw', '');
$bRawIsActive = '1' === (string) $this->GetActionParam('RawIsActive', '0');
$aFilters = array(); $aFilters = array();
foreach ($aIncFilters as $aFilter) foreach ($aIncFilters as $aFilter)
{ {
@ -2078,8 +2087,8 @@ class Actions
} }
} }
return $this->DefaultResponse(__FUNCTION__, $this->FiltersProvider()->Save($aFilters)); return $this->DefaultResponse(__FUNCTION__, $this->FiltersProvider()->Save($oAccount,
// return $this->TrueResponse(__FUNCTION__); $aFilters, $sRaw, $bRawIsActive));
} }
/** /**

View file

@ -116,7 +116,7 @@ class Application extends \RainLoop\Config\AbstractConfig
'admin_password' => array('12345'), 'admin_password' => array('12345'),
'allow_admin_panel' => array(true, 'Access settings'), 'allow_admin_panel' => array(true, 'Access settings'),
'allow_two_factor_auth' => array(false), 'allow_two_factor_auth' => array(false),
'allow_universal_login' => array(true), 'allow_universal_login' => array(false),
'admin_panel_host' => array(''), 'admin_panel_host' => array(''),
'core_install_access_domain' => array('') 'core_install_access_domain' => array('')
), ),

View file

@ -305,6 +305,38 @@ class Account extends \RainLoop\Account // for backward compatibility
return $this->Domain()->OutAuth(); 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 * @return string
*/ */
@ -443,4 +475,52 @@ class Account extends \RainLoop\Account // for backward compatibility
return $bLogin; 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;
}
} }

View file

@ -83,6 +83,11 @@ class Domain
*/ */
private $iSieveSecure; private $iSieveSecure;
/**
* @var bool
*/
private $bSieveAllowRaw;
/** /**
* @var string * @var string
*/ */
@ -130,6 +135,8 @@ class Domain
$this->iSievePort = $iSievePort; $this->iSievePort = $iSievePort;
$this->iSieveSecure = $iSieveSecure; $this->iSieveSecure = $iSieveSecure;
$this->bSieveAllowRaw = false;
$this->sWhiteList = \trim($sWhiteList); $this->sWhiteList = \trim($sWhiteList);
} }
@ -184,6 +191,7 @@ class Domain
!empty($aDomain['imap_secure']) ? $aDomain['imap_secure'] : ''); !empty($aDomain['imap_secure']) ? $aDomain['imap_secure'] : '');
$bUseSieve = isset($aDomain['sieve_use']) ? (bool) $aDomain['sieve_use'] : false; $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']; $sSieveHost = empty($aDomain['sieve_host']) ? '' : (string) $aDomain['sieve_host'];
$iSievePort = empty($aDomain['sieve_port']) ? 2000 : (int) $aDomain['sieve_port']; $iSievePort = empty($aDomain['sieve_port']) ? 2000 : (int) $aDomain['sieve_port'];
@ -207,6 +215,8 @@ class Domain
$bUseSieve, $sSieveHost, $iSievePort, $iSieveSecure, $bUseSieve, $sSieveHost, $iSievePort, $iSieveSecure,
$sOutHost, $iOutPort, $iOutSecure, $bOutShortLogin, $bOutAuth, $bOutUsePhpMail, $sOutHost, $iOutPort, $iOutSecure, $bOutShortLogin, $bOutAuth, $bOutUsePhpMail,
$sWhiteList); $sWhiteList);
$oDomain->SetSieveAllowRaw($bSieveAllowRaw);
} }
return $oDomain; return $oDomain;
@ -257,6 +267,7 @@ class Domain
'imap_secure = "'.self::ConstConnectionSecurityTypeToStr($this->iIncSecure).'"', 'imap_secure = "'.self::ConstConnectionSecurityTypeToStr($this->iIncSecure).'"',
'imap_short_login = '.($this->bIncShortLogin ? 'On' : 'Off'), 'imap_short_login = '.($this->bIncShortLogin ? 'On' : 'Off'),
'sieve_use = '.($this->bUseSieve ? 'On' : 'Off'), 'sieve_use = '.($this->bUseSieve ? 'On' : 'Off'),
'sieve_allow_raw = '.($this->bSieveAllowRaw ? 'On' : 'Off'),
'sieve_host = "'.$this->encodeIniString($this->sSieveHost).'"', 'sieve_host = "'.$this->encodeIniString($this->sSieveHost).'"',
'sieve_port = '.$this->iSievePort, 'sieve_port = '.$this->iSievePort,
'sieve_secure = "'.self::ConstConnectionSecurityTypeToStr($this->iSieveSecure).'"', 'sieve_secure = "'.self::ConstConnectionSecurityTypeToStr($this->iSieveSecure).'"',
@ -427,6 +438,22 @@ class Domain
return $this->iSieveSecure; return $this->iSieveSecure;
} }
/**
* @return bool
*/
public function SieveAllowRaw()
{
return $this->bSieveAllowRaw;
}
/**
* @param bool $bSieveAllowRaw
*/
public function SetSieveAllowRaw($bSieveAllowRaw)
{
$this->bSieveAllowRaw = !!$bSieveAllowRaw;
}
/** /**
* @return string * @return string
*/ */
@ -524,6 +551,7 @@ class Domain
'SieveHost' => $bAjax ? \MailSo\Base\Utils::IdnToUtf8($this->SieveHost()) : $this->SieveHost(), 'SieveHost' => $bAjax ? \MailSo\Base\Utils::IdnToUtf8($this->SieveHost()) : $this->SieveHost(),
'SievePort' => $this->SievePort(), 'SievePort' => $this->SievePort(),
'SieveSecure' => $this->SieveSecure(), 'SieveSecure' => $this->SieveSecure(),
'SieveAllowRaw' => $this->SieveAllowRaw(),
'OutHost' => $bAjax ? \MailSo\Base\Utils::IdnToUtf8($this->OutHost()) : $this->OutHost(), 'OutHost' => $bAjax ? \MailSo\Base\Utils::IdnToUtf8($this->OutHost()) : $this->OutHost(),
'OutPort' => $this->OutPort(), 'OutPort' => $this->OutPort(),
'OutSecure' => $this->OutSecure(), 'OutSecure' => $this->OutSecure(),

View file

@ -130,6 +130,7 @@ class Domain extends \RainLoop\Providers\AbstractProvider
$iIncSecure = (int) $oActions->GetActionParam('IncSecure', \MailSo\Net\Enumerations\ConnectionSecurityType::NONE); $iIncSecure = (int) $oActions->GetActionParam('IncSecure', \MailSo\Net\Enumerations\ConnectionSecurityType::NONE);
$bIncShortLogin = '1' === (string) $oActions->GetActionParam('IncShortLogin', '0'); $bIncShortLogin = '1' === (string) $oActions->GetActionParam('IncShortLogin', '0');
$bUseSieve = '1' === (string) $oActions->GetActionParam('UseSieve', '0'); $bUseSieve = '1' === (string) $oActions->GetActionParam('UseSieve', '0');
$bSieveAllowRaw = '1' === (string) $oActions->GetActionParam('SieveAllowRaw', '0');
$sSieveHost = (string) $oActions->GetActionParam('SieveHost', ''); $sSieveHost = (string) $oActions->GetActionParam('SieveHost', '');
$iSievePort = (int) $oActions->GetActionParam('SievePort', 2000); $iSievePort = (int) $oActions->GetActionParam('SievePort', 2000);
$iSieveSecure = (int) $oActions->GetActionParam('SieveSecure', \MailSo\Net\Enumerations\ConnectionSecurityType::NONE); $iSieveSecure = (int) $oActions->GetActionParam('SieveSecure', \MailSo\Net\Enumerations\ConnectionSecurityType::NONE);
@ -173,6 +174,11 @@ class Domain extends \RainLoop\Providers\AbstractProvider
$sWhiteList); $sWhiteList);
} }
} }
if ($oDomain)
{
$oDomain->SetSieveAllowRaw($bSieveAllowRaw);
}
} }
return $oDomain; return $oDomain;

View file

@ -18,21 +18,28 @@ class Filters extends \RainLoop\Providers\AbstractProvider
} }
/** /**
* @param \RainLoop\Account $oAccount
* @param bool $bAllowRaw = false
*
* @return array * @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 array $aFilters
* @param string $sRaw = ''
* @param bool $bRawIsActive = false
* *
* @return bool * @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;
} }
/** /**

View file

@ -57,7 +57,7 @@ class Filter
/** /**
* @var bool * @var bool
*/ */
private $bKeepForward; private $bKeep;
public function __construct() public function __construct()
{ {
@ -81,7 +81,7 @@ class Filter
$this->bMarkAsRead = false; $this->bMarkAsRead = false;
$this->bSkipOthers = false; $this->bSkipOthers = false;
$this->bKeepForward = true; $this->bKeep = true;
} }
/** /**
@ -167,9 +167,9 @@ class Filter
/** /**
* @return bool * @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->sActionValue = isset($aFilter['ActionValue']) ? $aFilter['ActionValue'] : '';
$this->sActionValueSecond = isset($aFilter['ActionValueSecond']) ? $aFilter['ActionValueSecond'] : ''; $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->bMarkAsRead = isset($aFilter['MarkAsRead']) ? '1' === (string) $aFilter['MarkAsRead'] : false;
$this->bSkipOthers = isset($aFilter['SkipOthers']) ? '1' === (string) $aFilter['SkipOthers'] : 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( $this->aConditions = \RainLoop\Providers\Filters\Classes\FilterCondition::CollectionFromJSON(
isset($aFilter['Conditions']) ? $aFilter['Conditions'] : array()); isset($aFilter['Conditions']) ? $aFilter['Conditions'] : array());
@ -255,6 +255,7 @@ class Filter
'ActionType' => $this->ActionType(), 'ActionType' => $this->ActionType(),
'ActionValue' => $this->ActionValue(), 'ActionValue' => $this->ActionValue(),
'ActionValueSecond' => $this->ActionValueSecond(), 'ActionValueSecond' => $this->ActionValueSecond(),
'Keep' => $this->Keep(),
'MarkAsRead' => $this->MarkAsRead(), 'MarkAsRead' => $this->MarkAsRead(),
'SkipOthers' => $this->SkipOthers() 'SkipOthers' => $this->SkipOthers()
); );

View file

@ -8,5 +8,6 @@ class ActionType
const MOVE_TO = 'MoveTo'; const MOVE_TO = 'MoveTo';
const DISCARD = 'Discard'; const DISCARD = 'Discard';
const VACATION = 'Vacation'; const VACATION = 'Vacation';
const REJECT = 'Reject';
const FORWARD = 'Forward'; const FORWARD = 'Forward';
} }

View file

@ -5,14 +5,18 @@ namespace RainLoop\Providers\Filters;
interface FiltersInterface interface FiltersInterface
{ {
/** /**
* @param \RainLoop\Account $oAccount
* @param bool $bAllowRaw = false
*
* @return array * @return array
*/ */
public function Load(); public function Load($oAccount, $bAllowRaw = false);
/** /**
* @param \RainLoop\Account $oAccount
* @param array $aFilters * @param array $aFilters
* *
* @return bool * @return bool
*/ */
public function Save($aFilters); public function Save($oAccount, $aFilters);
} }

View file

@ -4,7 +4,25 @@ namespace RainLoop\Providers\Filters;
class SieveStorage implements \RainLoop\Providers\Filters\FiltersInterface 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 * @var bool
@ -12,31 +30,130 @@ class SieveStorage implements \RainLoop\Providers\Filters\FiltersInterface
private $bUtf8FolderName; private $bUtf8FolderName;
/** /**
* @param bool $bUtf8FolderName = true
*
* @return void * @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 * @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 array $aFilters
* @param string $sRaw = ''
* @param bool $bRawIsActive = false
* *
* @return bool * @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'; $aResult[] = $sTab.'# @Error (vacation): empty action value';
} }
break; 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: case \RainLoop\Providers\Filters\Enumerations\ActionType::FORWARD:
$sValue = $oFilter->ActionValue(); $sValue = $oFilter->ActionValue();
if (0 < \strlen($sValue)) if (0 < \strlen($sValue))
{ {
if ($oFilter->KeepForward()) if ($oFilter->Keep())
{ {
$aCapa['copy'] = true; $aCapa['fileinto'] = true;
$aResult[] = $sTab.'redirect :copy "'.$this->quote($sValue).'";'; $aResult[] = $sTab.'fileinto "INBOX";';
}
else
{
$aResult[] = $sTab.'redirect "'.$this->quote($sValue).'";';
} }
$aResult[] = $sTab.'redirect "'.$this->quote($sValue).'";';
$aResult[] = $sTab.'stop;'; $aResult[] = $sTab.'stop;';
} }
else else
@ -246,8 +374,6 @@ class SieveStorage implements \RainLoop\Providers\Filters\FiltersInterface
$sValue = $oFilter->ActionValue(); $sValue = $oFilter->ActionValue();
if (0 < \strlen($sValue)) if (0 < \strlen($sValue))
{ {
$aCapa['fileinto'] = true;
$sFolderName = $sValue; // utf7-imap $sFolderName = $sValue; // utf7-imap
if ($this->bUtf8FolderName) // to utf-8 if ($this->bUtf8FolderName) // to utf-8
{ {
@ -256,6 +382,7 @@ class SieveStorage implements \RainLoop\Providers\Filters\FiltersInterface
\MailSo\Base\Enumerations\Charset::UTF_8); \MailSo\Base\Enumerations\Charset::UTF_8);
} }
$aCapa['fileinto'] = true;
$aResult[] = $sTab.'fileinto "'.$this->quote($sFolderName).'";'; $aResult[] = $sTab.'fileinto "'.$this->quote($sFolderName).'";';
$aResult[] = $sTab.'stop;'; $aResult[] = $sTab.'stop;';
} }
@ -359,4 +486,12 @@ class SieveStorage implements \RainLoop\Providers\Filters\FiltersInterface
{ {
return \str_replace(array('\\', '"'), array('\\\\', '\\"'), \trim($sValue)); 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;
}
} }

View file

@ -84,13 +84,25 @@
<div data-bind="component: { <div data-bind="component: {
name: 'Checkbox', name: 'Checkbox',
params: { params: {
label: 'use Sieve for this domain', label: 'Use Sieve for this domain',
value: useSieve value: useSieve
} }
}"></div> }"></div>
</div> </div>
</div> </div>
<div data-bind="visible: useSieve"> <div data-bind="visible: useSieve">
<br />
<div class="row">
<div class="span5">
<div data-bind="component: {
name: 'Checkbox',
params: {
label: 'Allow custom raw script',
value: sieveAllowRaw
}
}"></div>
</div>
</div>
<br /> <br />
<div class="row"> <div class="row">
<div class="span3"> <div class="span3">

View file

@ -11,7 +11,12 @@
&nbsp;&nbsp; &nbsp;&nbsp;
<span class="i18n" data-i18n-text="SETTINGS_FILTERS/BUTTON_ADD_FILTER"></span> <span class="i18n" data-i18n-text="SETTINGS_FILTERS/BUTTON_ADD_FILTER"></span>
</a> </a>
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;
<a class="btn" data-bind="visible: filterRaw.allow, click: function () { filterRaw.active(!filterRaw.active()) },
css: {'active': filterRaw.active }">
<i class="icon-file-code"></i>
</a>
&nbsp;&nbsp;
<a class="btn" data-bind="command: saveChanges"> <a class="btn" data-bind="command: saveChanges">
<i data-bind="css: {'icon-floppy': !filters.saving(), 'icon-spinner animated': filters.saving()}"></i> <i data-bind="css: {'icon-floppy': !filters.saving(), 'icon-spinner animated': filters.saving()}"></i>
&nbsp;&nbsp; &nbsp;&nbsp;
@ -36,7 +41,11 @@
&nbsp;&nbsp; &nbsp;&nbsp;
<span data-bind="text: processText"></span> <span data-bind="text: processText"></span>
</div> </div>
<table class="table table-hover list-table g-ui-user-select-none" data-bind="i18nUpdate: filters"> <div data-bind="visible: filterRaw.allow() && filterRaw.active()">
<textarea class="span8" style="height: 300px" data-bind="value: filterRaw, valueUpdate: 'afterkeydown'"></textarea>
</div>
<table class="table table-hover list-table g-ui-user-select-none"
data-bind="visible: !filterRaw.active() || !filterRaw.active(), i18nUpdate: filters">
<colgroup> <colgroup>
<col style="width: 30px" /> <col style="width: 30px" />
<col /> <col />

View file

@ -9,10 +9,10 @@
name: 'Checkbox', name: 'Checkbox',
params: { params: {
label: 'keep in INBOX @i18n', label: 'keep in INBOX @i18n',
value: keepForward value: actionKeep
} }
}"></div> }"></div>
<div data-bind="component: { <div data-bind="visible: $root.allowMarkAsRead, component: {
name: 'Checkbox', name: 'Checkbox',
params: { params: {
label: 'Mark as read @i18n', label: 'Mark as read @i18n',

View file

@ -6,7 +6,7 @@
</div> </div>
<div class="control-group"> <div class="control-group">
<div class="controls"> <div class="controls">
<div data-bind="component: { <div data-bind="visible: $root.allowMarkAsRead, component: {
name: 'Checkbox', name: 'Checkbox',
params: { params: {
label: 'Mark as read @i18n', label: 'Mark as read @i18n',

View file

@ -0,0 +1,5 @@
<div class="control-group" data-bind="css: {'error': actionValue.error}" style="margin-bottom: 0">
<div class="controls">
<textarea class="span5" data-bind="value: actionValue" style="height: 100px;" placeholder="Reject message @i18n"></textarea>
</div>
</div>

View file

@ -5,7 +5,8 @@
&nbsp; &nbsp;
<input class="span3" type="text" data-bind="value: value" /> <input class="span3" type="text" data-bind="value: value" />
&nbsp; &nbsp;
<span class="delete-action button-delete pull-right" data-bind="click: function (oCondition) { $root.removeCondition(oCondition); }"> <span class="delete-action button-delete pull-right" style="margin-top: 5px;"
data-bind="click: function (oCondition) { $root.removeCondition(oCondition); }">
<i class="icon-trash"></i> <i class="icon-trash"></i>
</span> </span>
</div> </div>

View file

@ -173,7 +173,6 @@ LABEL_EMAIL = "Email"
LABEL_PHONE = "Phone" LABEL_PHONE = "Phone"
LABEL_WEB = "Web" LABEL_WEB = "Web"
LABEL_BIRTHDAY = "Birthday" LABEL_BIRTHDAY = "Birthday"
LABEL_TAGS = "Tags"
LINK_ADD_EMAIL = "Add an email address" LINK_ADD_EMAIL = "Add an email address"
LINK_ADD_PHONE = "Add a phone" LINK_ADD_PHONE = "Add a phone"
LINK_BIRTHDAY = "Birthday" LINK_BIRTHDAY = "Birthday"
@ -376,10 +375,8 @@ TITLE_EDIT_FILTER = "Update filter?"
LEGEND_FILTERS = "Filters" LEGEND_FILTERS = "Filters"
BUTTON_SAVE = "Save" BUTTON_SAVE = "Save"
BUTTON_DONE = "Done" BUTTON_DONE = "Done"
BUTTON_ADD_FILTER = "Add Filter" BUTTON_ADD_FILTER = "Add a Filter"
BUTTON_SAVE_FILTER = "Save Filter" BUTTON_ADD_CONDITION = "Add a Condition"
BUTTON_DELETE_FILTER = "Delete Filter"
BUTTON_ADD_CONDITION = "Add Condition"
BUTTON_DELETE = "Delete" BUTTON_DELETE = "Delete"
LOADING_PROCESS = "Updating filter list" LOADING_PROCESS = "Updating filter list"
DELETING_ASK = "Are you sure?" DELETING_ASK = "Are you sure?"

View file

@ -548,7 +548,7 @@ LABEL_SAVE_MESSAGE = "Guardar aviso"
LABEL_SEND_MESSAGE = "Enviar mensaje" LABEL_SEND_MESSAGE = "Enviar mensaje"
LABEL_CLOSE_COMPOSE = "Cerrar componer" LABEL_CLOSE_COMPOSE = "Cerrar componer"
[ PGP_NOTIFICATIONS ] [PGP_NOTIFICATIONS]
NO_PUBLIC_KEYS_FOUND = "No se encontraron claves públicas" NO_PUBLIC_KEYS_FOUND = "No se encontraron claves públicas"
NO_PUBLIC_KEYS_FOUND_FOR = "No hay claves públicas encontrados para el email %MAIL%" NO_PUBLIC_KEYS_FOUND_FOR = "No hay claves públicas encontrados para el email %MAIL%"
NO_PRIVATE_KEY_FOUND = "No se encontró la clave privada " NO_PRIVATE_KEY_FOUND = "No se encontró la clave privada "

View file

@ -619,8 +619,8 @@ NO_SCRIPT_TITLE = "È richiesto JavaScript per questa applicazione."
NO_SCRIPT_DESC = "Il supporto a JavaScript è disabilitato. NO_SCRIPT_DESC = "Il supporto a JavaScript è disabilitato.
Abilitare il supporto a JavaScript e riprovare." Abilitare il supporto a JavaScript e riprovare."
NO_SCRIPT_TITLE = "Sono richiesti i Cookie per questa applicazione." NO_COOKIE_TITLE = "Sono richiesti i Cookie per questa applicazione."
NO_SCRIPT_DESC = "Il supporto ai Cookie è disabilitato. NO_COOKIE_DESC = "Il supporto ai Cookie è disabilitato.
Abilitare il supporto ai Cookie e riprovare." Abilitare il supporto ai Cookie e riprovare."
BAD_BROWSER_TITLE = "Il tuo browser è vecchio" BAD_BROWSER_TITLE = "Il tuo browser è vecchio"

View file

@ -585,7 +585,7 @@ CANT_MOVE_MESSAGE = "Kan ikke flytte meldingen"
CANT_SAVE_MESSAGE = "Kan ikke lagre melding" CANT_SAVE_MESSAGE = "Kan ikke lagre melding"
CANT_SEND_MESSAGE = "Kan ikke sende melding" CANT_SEND_MESSAGE = "Kan ikke sende melding"
INVALID_RECIPIENTS = "Ugyldige mottakere" INVALID_RECIPIENTS = "Ugyldige mottakere"
Cant_create_folder = "Kan ikke opprette mappe" CANT_CREATE_FOLDER = "Kan ikke opprette mappe"
CANT_RENAME_FOLDER = "Kan ikke endre navn på mappen" CANT_RENAME_FOLDER = "Kan ikke endre navn på mappen"
CANT_DELETE_FOLDER = "Kan ikke slette mappen" CANT_DELETE_FOLDER = "Kan ikke slette mappen"
CANT_DELETE_NON_EMPTY_FOLDER = "Kan ikke slette katalog med innhold" CANT_DELETE_NON_EMPTY_FOLDER = "Kan ikke slette katalog med innhold"

View file

@ -599,8 +599,8 @@ CANT_DELETE_PACKAGE = "Nu pot șterge pachetul. Încercați din nou"
INVALID_PLUGIN_PACKAGE = "Pachetul este invalid" INVALID_PLUGIN_PACKAGE = "Pachetul este invalid"
UNSUPPORTED_PLUGIN_PACKAGE = "Plugin-ul necesită sprijinul complet al serverului" UNSUPPORTED_PLUGIN_PACKAGE = "Plugin-ul necesită sprijinul complet al serverului"
LICENSING_SERVER_IS_UNAVAILABLE = "Server de abonamente este temporar indisponibil." LICENSING_SERVER_IS_UNAVAILABLE = "Server de abonamente este temporar indisponibil."
LICENSING_EXPIRED = "Licența a expirat." LICENSING_DOMAIN_EXPIRED = "Subscription for this domain has expired."
LICENSING_BANNED = "Licență restricționată." LICENSING_DOMAIN_BANNED = "Subscription for this domain is banned."
DEMO_SEND_MESSAGE_ERROR = "Cont demo trimite e-mail la adresele de e-mail externe este interzisă!" DEMO_SEND_MESSAGE_ERROR = "Cont demo trimite e-mail la adresele de e-mail externe este interzisă!"
ACCOUNT_ALREADY_EXISTS = "Contul deja există" ACCOUNT_ALREADY_EXISTS = "Contul deja există"
MAIL_SERVER_ERROR = "Nu am reușit să accesez serverul de e-mail" MAIL_SERVER_ERROR = "Nu am reușit să accesez serverul de e-mail"

View file

@ -601,8 +601,8 @@ CANT_DELETE_PACKAGE = "Ошибка удаления пакета"
INVALID_PLUGIN_PACKAGE = "Ошибка пакета плагина" INVALID_PLUGIN_PACKAGE = "Ошибка пакета плагина"
UNSUPPORTED_PLUGIN_PACKAGE = "Для работы плагина необходима полная поддержка сервера" UNSUPPORTED_PLUGIN_PACKAGE = "Для работы плагина необходима полная поддержка сервера"
LICENSING_SERVER_IS_UNAVAILABLE = "Сервер подписок временно не доступен." LICENSING_SERVER_IS_UNAVAILABLE = "Сервер подписок временно не доступен."
LICENSING_EXPIRED = "Подписка на данный домен устарела." LICENSING_DOMAIN_EXPIRED = "Подписка на данный домен устарела."
LICENSING_BANNED = "Подписка на данный домен заблокирована." LICENSING_DOMAIN_BANNED = "Подписка на данный домен заблокирована."
DEMO_SEND_MESSAGE_ERROR = "Демо аккаунту отправка писем на внешние почтовые адреса запрещена!" DEMO_SEND_MESSAGE_ERROR = "Демо аккаунту отправка писем на внешние почтовые адреса запрещена!"
ACCOUNT_ALREADY_EXISTS = "Аккаунт уже добавлен" ACCOUNT_ALREADY_EXISTS = "Аккаунт уже добавлен"
MAIL_SERVER_ERROR = "Ошибка доступа к почтовому серверу" MAIL_SERVER_ERROR = "Ошибка доступа к почтовому серверу"

View file

@ -601,8 +601,8 @@ CANT_DELETE_PACKAGE = "Помилка видалення пакету"
INVALID_PLUGIN_PACKAGE = "Помилка пакету плагіну" INVALID_PLUGIN_PACKAGE = "Помилка пакету плагіну"
UNSUPPORTED_PLUGIN_PACKAGE = "Для роботи плагину необхідна повна підтримка серверу" UNSUPPORTED_PLUGIN_PACKAGE = "Для роботи плагину необхідна повна підтримка серверу"
LICENSING_SERVER_IS_UNAVAILABLE = "Сервер підписок тимчасово не доступний." LICENSING_SERVER_IS_UNAVAILABLE = "Сервер підписок тимчасово не доступний."
LICENSING_EXPIRED = "Підписка на цей домен застаріла." LICENSING_DOMAIN_EXPIRED = "Підписка на цей домен застаріла."
LICENSING_BANNED = "Підписка на цей домен заблокована." LICENSING_DOMAIN_BANNED = "Підписка на цей домен заблокована."
DEMO_SEND_MESSAGE_ERROR = "Демо акаунту надсилання листів на зовнішні поштові адреси заборонена!" DEMO_SEND_MESSAGE_ERROR = "Демо акаунту надсилання листів на зовнішні поштові адреси заборонена!"
ACCOUNT_ALREADY_EXISTS = "акаунт вже додано" ACCOUNT_ALREADY_EXISTS = "акаунт вже додано"
MAIL_SERVER_ERROR = "Помилка доступу до поштового серверу" MAIL_SERVER_ERROR = "Помилка доступу до поштового серверу"