mirror of
https://github.com/the-djmaze/snappymail.git
synced 2026-07-13 03:27:39 +03:00
Sieve filters (interface)
This commit is contained in:
parent
644f861347
commit
ef3042aff7
15 changed files with 449 additions and 55 deletions
|
|
@ -312,7 +312,7 @@
|
||||||
*/
|
*/
|
||||||
Enums.FiltersAction = {
|
Enums.FiltersAction = {
|
||||||
'None': 'None',
|
'None': 'None',
|
||||||
'Move': 'Move',
|
'MoveTo': 'MoveTo',
|
||||||
'Discard': 'Discard',
|
'Discard': 'Discard',
|
||||||
'Forward': 'Forward'
|
'Forward': 'Forward'
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -29,15 +29,24 @@
|
||||||
this.name.error = ko.observable(false);
|
this.name.error = ko.observable(false);
|
||||||
this.name.focused = ko.observable(false);
|
this.name.focused = ko.observable(false);
|
||||||
|
|
||||||
|
this.raw = ko.observable('');
|
||||||
|
|
||||||
this.conditions = ko.observableArray([]);
|
this.conditions = ko.observableArray([]);
|
||||||
this.conditionsType = ko.observable(Enums.FilterRulesType.All);
|
this.conditionsType = ko.observable(Enums.FilterRulesType.Any);
|
||||||
|
|
||||||
// Actions
|
// Actions
|
||||||
this.actionValue = ko.observable('');
|
this.actionValue = ko.observable('');
|
||||||
this.actionMarkAsRead = ko.observable(false);
|
this.actionMarkAsRead = ko.observable(false);
|
||||||
|
|
||||||
this.actionSkipOthers = ko.observable(false);
|
this.actionSkipOthers = ko.observable(false);
|
||||||
|
|
||||||
this.actionType = ko.observable(Enums.FiltersAction.Move);
|
this.keepForward = ko.observable(true);
|
||||||
|
|
||||||
|
this.actionType = ko.observable(Enums.FiltersAction.MoveTo);
|
||||||
|
|
||||||
|
this.actionType.subscribe(function (sValue) {
|
||||||
|
this.actionValue('');
|
||||||
|
}, this);
|
||||||
|
|
||||||
this.actionTemplate = ko.computed(function () {
|
this.actionTemplate = ko.computed(function () {
|
||||||
|
|
||||||
|
|
@ -45,11 +54,14 @@
|
||||||
switch (this.actionType())
|
switch (this.actionType())
|
||||||
{
|
{
|
||||||
default:
|
default:
|
||||||
case Enums.FiltersAction.Move:
|
case Enums.FiltersAction.MoveTo:
|
||||||
sTemplate = 'SettingsFiltersActionValueAsFolders';
|
sTemplate = 'SettingsFiltersActionValueAsFolders';
|
||||||
break;
|
break;
|
||||||
|
// case Enums.FiltersAction.Forward:
|
||||||
|
// sTemplate = 'SettingsFiltersActionWithValue';
|
||||||
|
// break;
|
||||||
case Enums.FiltersAction.Forward:
|
case Enums.FiltersAction.Forward:
|
||||||
sTemplate = 'SettingsFiltersActionWithValue';
|
sTemplate = 'SettingsFiltersActionForward';
|
||||||
break;
|
break;
|
||||||
case Enums.FiltersAction.None:
|
case Enums.FiltersAction.None:
|
||||||
case Enums.FiltersAction.Discard:
|
case Enums.FiltersAction.Discard:
|
||||||
|
|
@ -86,17 +98,20 @@
|
||||||
{
|
{
|
||||||
return {
|
return {
|
||||||
'ID': this.id,
|
'ID': this.id,
|
||||||
'Enabled': this.enabled(),
|
'Enabled': this.enabled() ? '1' : '0',
|
||||||
'Name': this.name(),
|
'Name': this.name(),
|
||||||
'ConditionsType': this.conditionsType(),
|
'ConditionsType': this.conditionsType(),
|
||||||
'Conditions': _.map(this.conditions(), function (oItem) {
|
'Conditions': _.map(this.conditions(), function (oItem) {
|
||||||
return oItem.toJson();
|
return oItem.toJson();
|
||||||
}),
|
}),
|
||||||
|
|
||||||
'ActionValue': this.actionValue(),
|
'ActionValue': this.actionValue(),
|
||||||
'ActionType': this.actionType(),
|
'ActionType': this.actionType(),
|
||||||
|
|
||||||
|
'Raw': this.raw(),
|
||||||
|
|
||||||
'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'
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
@ -117,7 +132,7 @@
|
||||||
var bResult = false;
|
var bResult = false;
|
||||||
if (oItem && 'Object/Filter' === oItem['@Object'])
|
if (oItem && 'Object/Filter' === oItem['@Object'])
|
||||||
{
|
{
|
||||||
this.ID = Utils.pString(oItem['ID']);
|
this.id = Utils.pString(oItem['ID']);
|
||||||
this.name(Utils.pString(oItem['Name']));
|
this.name(Utils.pString(oItem['Name']));
|
||||||
|
|
||||||
bResult = true;
|
bResult = true;
|
||||||
|
|
@ -130,13 +145,15 @@
|
||||||
{
|
{
|
||||||
var oClone = new FilterModel();
|
var oClone = new FilterModel();
|
||||||
|
|
||||||
oClone.ID = this.ID;
|
oClone.id = this.id;
|
||||||
|
|
||||||
oClone.enabled(this.enabled());
|
oClone.enabled(this.enabled());
|
||||||
|
|
||||||
oClone.name(this.name());
|
oClone.name(this.name());
|
||||||
oClone.name.error(this.name.error());
|
oClone.name.error(this.name.error());
|
||||||
|
|
||||||
|
oClone.raw(this.raw());
|
||||||
|
|
||||||
oClone.conditionsType(this.conditionsType());
|
oClone.conditionsType(this.conditionsType());
|
||||||
|
|
||||||
oClone.actionMarkAsRead(this.actionMarkAsRead());
|
oClone.actionMarkAsRead(this.actionMarkAsRead());
|
||||||
|
|
@ -146,6 +163,8 @@
|
||||||
|
|
||||||
oClone.actionType(this.actionType());
|
oClone.actionType(this.actionType());
|
||||||
|
|
||||||
|
oClone.keepForward(this.keepForward());
|
||||||
|
|
||||||
oClone.conditions(_.map(this.conditions(), function (oCondition) {
|
oClone.conditions(_.map(this.conditions(), function (oCondition) {
|
||||||
return oCondition.cloneSelf();
|
return oCondition.cloneSelf();
|
||||||
}));
|
}));
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,6 @@
|
||||||
|
|
||||||
if (Enums.StorageResultType.Success === sResult && oData && oData.Result)
|
if (Enums.StorageResultType.Success === sResult && oData && oData.Result)
|
||||||
{
|
{
|
||||||
|
|
||||||
self.haveChanges(false);
|
self.haveChanges(false);
|
||||||
self.updateList();
|
self.updateList();
|
||||||
}
|
}
|
||||||
|
|
@ -86,9 +85,9 @@
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
this.filters.loading(true);
|
this.filters.loading(true);
|
||||||
// Remote.filtersGet(function (sResult, oData) {
|
Remote.filtersGet(function (sResult, oData) {
|
||||||
self.filters.loading(false);
|
self.filters.loading(false);
|
||||||
// });
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
FiltersUserSettings.prototype.deleteFilter = function (oFilter)
|
FiltersUserSettings.prototype.deleteFilter = function (oFilter)
|
||||||
|
|
|
||||||
|
|
@ -238,6 +238,14 @@
|
||||||
});
|
});
|
||||||
}, this);
|
}, this);
|
||||||
|
|
||||||
|
this.folderMenuForFilters = ko.computed(function () {
|
||||||
|
return Utils.folderListOptionsBuilder(this.folderListSystem(), this.folderList(), [
|
||||||
|
'INBOX'
|
||||||
|
], null, null, null, null, function (oItem) {
|
||||||
|
return oItem ? oItem.localName() : '';
|
||||||
|
});
|
||||||
|
}, this);
|
||||||
|
|
||||||
// message list
|
// message list
|
||||||
this.staticMessageList = [];
|
this.staticMessageList = [];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -247,8 +247,7 @@
|
||||||
*/
|
*/
|
||||||
RemoteUserStorage.prototype.filtersGet = function (fCallback)
|
RemoteUserStorage.prototype.filtersGet = function (fCallback)
|
||||||
{
|
{
|
||||||
this.defaultRequest(fCallback, 'Filters', {
|
this.defaultRequest(fCallback, 'Filters', {});
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@
|
||||||
this.filter = ko.observable(null);
|
this.filter = ko.observable(null);
|
||||||
|
|
||||||
this.selectedFolderValue = ko.observable(Consts.Values.UnuseOptionValue);
|
this.selectedFolderValue = ko.observable(Consts.Values.UnuseOptionValue);
|
||||||
this.folderSelectList = Data.folderMenuForMove;
|
this.folderSelectList = Data.folderMenuForFilters;
|
||||||
this.defautOptionsAfterRender = Utils.defautOptionsAfterRender;
|
this.defautOptionsAfterRender = Utils.defautOptionsAfterRender;
|
||||||
|
|
||||||
this.saveFilter = Utils.createCommand(this, function () {
|
this.saveFilter = Utils.createCommand(this, function () {
|
||||||
|
|
@ -46,6 +46,15 @@
|
||||||
|
|
||||||
if (this.fTrueCallback)
|
if (this.fTrueCallback)
|
||||||
{
|
{
|
||||||
|
if (Enums.FiltersAction.MoveTo === this.filter().actionType())
|
||||||
|
{
|
||||||
|
this.filter().actionValue(this.selectedFolderValue());
|
||||||
|
}
|
||||||
|
else if (Enums.FiltersAction.Forward !== this.filter().actionType())
|
||||||
|
{
|
||||||
|
this.filter().actionValue('');
|
||||||
|
}
|
||||||
|
|
||||||
this.fTrueCallback(this.filter());
|
this.fTrueCallback(this.filter());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -60,8 +69,8 @@
|
||||||
|
|
||||||
this.actionTypeOptions = [
|
this.actionTypeOptions = [
|
||||||
{'id': Enums.FiltersAction.None, 'name': 'None @i18n'},
|
{'id': Enums.FiltersAction.None, 'name': 'None @i18n'},
|
||||||
{'id': Enums.FiltersAction.Move, 'name': ' Move to @i18n'},
|
{'id': Enums.FiltersAction.MoveTo, 'name': ' Move to @i18n'},
|
||||||
// {'id': Enums.FiltersAction.Forward, 'name': 'Forward to @i18n'},
|
{'id': Enums.FiltersAction.Forward, 'name': 'Forward to @i18n'},
|
||||||
{'id': Enums.FiltersAction.Discard, 'name': 'Discard @i18n'}
|
{'id': Enums.FiltersAction.Discard, 'name': 'Discard @i18n'}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
@ -72,10 +81,10 @@
|
||||||
];
|
];
|
||||||
|
|
||||||
this.typeOptions = [
|
this.typeOptions = [
|
||||||
{'id': Enums.FilterConditionType.EqualTo, 'name': 'Equal To @i18n'},
|
|
||||||
{'id': Enums.FilterConditionType.NotEqualTo, 'name': 'Not Equal To @i18n'},
|
|
||||||
{'id': Enums.FilterConditionType.Contains, 'name': 'Contains @i18n'},
|
{'id': Enums.FilterConditionType.Contains, 'name': 'Contains @i18n'},
|
||||||
{'id': Enums.FilterConditionType.NotContains, 'name': 'Not Contains @i18n'}
|
{'id': Enums.FilterConditionType.NotContains, 'name': 'Not Contains @i18n'},
|
||||||
|
{'id': Enums.FilterConditionType.EqualTo, 'name': 'Equal To @i18n'},
|
||||||
|
{'id': Enums.FilterConditionType.NotEqualTo, 'name': 'Not Equal To @i18n'}
|
||||||
];
|
];
|
||||||
|
|
||||||
kn.constructorEnd(this);
|
kn.constructorEnd(this);
|
||||||
|
|
@ -119,7 +128,7 @@
|
||||||
{
|
{
|
||||||
if (this.isNew() && this.filter())
|
if (this.isNew() && this.filter())
|
||||||
{
|
{
|
||||||
this.filter().name.focused(true);
|
this.filter().name.focused(true);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -250,7 +250,9 @@ class Actions
|
||||||
break;
|
break;
|
||||||
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)
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
case 'address-book':
|
case 'address-book':
|
||||||
// \RainLoop\Providers\AddressBook\AddressBookInterface
|
// \RainLoop\Providers\AddressBook\AddressBookInterface
|
||||||
|
|
@ -2053,10 +2055,9 @@ class Actions
|
||||||
*/
|
*/
|
||||||
public function DoFilters()
|
public function DoFilters()
|
||||||
{
|
{
|
||||||
return $this->TrueResponse(__FUNCTION__);
|
return $this->DefaultResponse(__FUNCTION__, $this->FiltersProvider()->Load());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return array
|
* @return array
|
||||||
*
|
*
|
||||||
|
|
@ -2064,6 +2065,7 @@ class Actions
|
||||||
*/
|
*/
|
||||||
public function DoFiltersSave()
|
public function DoFiltersSave()
|
||||||
{
|
{
|
||||||
|
sleep(1);
|
||||||
$aIncFilters = $this->GetActionParam('Filters', array());
|
$aIncFilters = $this->GetActionParam('Filters', array());
|
||||||
|
|
||||||
$aFilters = array();
|
$aFilters = array();
|
||||||
|
|
@ -2074,14 +2076,13 @@ class Actions
|
||||||
$oFilter = new \RainLoop\Providers\Filters\Classes\Filter();
|
$oFilter = new \RainLoop\Providers\Filters\Classes\Filter();
|
||||||
if ($oFilter->FromJSON($aFilter))
|
if ($oFilter->FromJSON($aFilter))
|
||||||
{
|
{
|
||||||
$this->Logger()->WriteDump($oFilter);
|
|
||||||
$aFilters[] = $oFilter;
|
$aFilters[] = $oFilter;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->TrueResponse(__FUNCTION__);
|
return $this->DefaultResponse(__FUNCTION__, $this->FiltersProvider()->Save($aFilters));
|
||||||
// return $this->DefaultResponse(__FUNCTION__, $this->FiltersProvider()->Save($aFilters));
|
// return $this->TrueResponse(__FUNCTION__);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -269,6 +269,8 @@ Enables caching in the system'),
|
||||||
'imap_folder_list_limit' => array(200),
|
'imap_folder_list_limit' => array(200),
|
||||||
'imap_show_login_alert' => array(true),
|
'imap_show_login_alert' => array(true),
|
||||||
'smtp_show_server_errors' => array(false),
|
'smtp_show_server_errors' => array(false),
|
||||||
|
'sieve_allow_raw_script' => array(false),
|
||||||
|
'sieve_utf8_folder_name' => array(true),
|
||||||
'curl_proxy' => array(''),
|
'curl_proxy' => array(''),
|
||||||
'curl_proxy_auth' => array(''),
|
'curl_proxy_auth' => array(''),
|
||||||
'in_iframe' => array(false),
|
'in_iframe' => array(false),
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,11 @@ class Filter
|
||||||
*/
|
*/
|
||||||
private $sID;
|
private $sID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
private $bEnabled;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
|
|
@ -19,6 +24,11 @@ class Filter
|
||||||
*/
|
*/
|
||||||
private $aConditions;
|
private $aConditions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $sConditionsType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
|
|
@ -39,6 +49,11 @@ class Filter
|
||||||
*/
|
*/
|
||||||
private $bSkipOthers;
|
private $bSkipOthers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
private $bKeepForward;
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
$this->Clear();
|
$this->Clear();
|
||||||
|
|
@ -49,15 +64,18 @@ class Filter
|
||||||
$this->sID = '';
|
$this->sID = '';
|
||||||
$this->sName = '';
|
$this->sName = '';
|
||||||
|
|
||||||
|
$this->bEnabled = true;
|
||||||
|
|
||||||
$this->aConditions = array();
|
$this->aConditions = array();
|
||||||
|
|
||||||
$this->sFilterRulesType = \RainLoop\Providers\Filters\Enumerations\FilterRulesType::ALL;
|
$this->sConditionsType = \RainLoop\Providers\Filters\Enumerations\ConditionsType::ANY;
|
||||||
|
|
||||||
$this->sActionType = \RainLoop\Providers\Filters\Enumerations\ActionType::MOVE_TO;
|
$this->sActionType = \RainLoop\Providers\Filters\Enumerations\ActionType::MOVE_TO;
|
||||||
$this->sActionValue = '';
|
$this->sActionValue = '';
|
||||||
|
|
||||||
$this->bMarkAsRead = false;
|
$this->bMarkAsRead = false;
|
||||||
$this->bSkipOthers = false;
|
$this->bSkipOthers = false;
|
||||||
|
$this->bKeepForward = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -68,6 +86,14 @@ class Filter
|
||||||
return $this->sID;
|
return $this->sID;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function Enabled()
|
||||||
|
{
|
||||||
|
return $this->bEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
|
|
@ -87,9 +113,9 @@ class Filter
|
||||||
/**
|
/**
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function FilterRulesType()
|
public function ConditionsType()
|
||||||
{
|
{
|
||||||
return $this->sFilterRulesType;
|
return $this->sConditionsType;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -124,6 +150,14 @@ class Filter
|
||||||
return $this->bSkipOthers;
|
return $this->bSkipOthers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function KeepForward()
|
||||||
|
{
|
||||||
|
return $this->bKeepForward;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
|
|
@ -137,10 +171,13 @@ class Filter
|
||||||
*/
|
*/
|
||||||
public function unserializeFromJson($sFilterJson)
|
public function unserializeFromJson($sFilterJson)
|
||||||
{
|
{
|
||||||
$sFilterJson = \json_decode(\trim($sFilterJson));
|
$aFilterJson = \json_decode(\trim($sFilterJson), true);
|
||||||
if (!empty($sFilterJson))
|
if (\is_array($aFilterJson))
|
||||||
{
|
{
|
||||||
|
return $this->FromJSON($aFilterJson);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -155,8 +192,10 @@ class Filter
|
||||||
$this->sID = isset($aFilter['ID']) ? $aFilter['ID'] : '';
|
$this->sID = isset($aFilter['ID']) ? $aFilter['ID'] : '';
|
||||||
$this->sName = isset($aFilter['Name']) ? $aFilter['Name'] : '';
|
$this->sName = isset($aFilter['Name']) ? $aFilter['Name'] : '';
|
||||||
|
|
||||||
$this->sFilterRulesType = isset($aFilter['FilterRulesType']) ? $aFilter['FilterRulesType'] :
|
$this->bEnabled = isset($aFilter['Enabled']) ? '1' === (string) $aFilter['Enabled'] : true;
|
||||||
\RainLoop\Providers\Filters\Enumerations\FilterRulesType::ALL;
|
|
||||||
|
$this->sConditionsType = isset($aFilter['ConditionsType']) ? $aFilter['ConditionsType'] :
|
||||||
|
\RainLoop\Providers\Filters\Enumerations\ConditionsType::ANY;
|
||||||
|
|
||||||
$this->sActionType = isset($aFilter['ActionType']) ? $aFilter['ActionType'] :
|
$this->sActionType = isset($aFilter['ActionType']) ? $aFilter['ActionType'] :
|
||||||
\RainLoop\Providers\Filters\Enumerations\ActionType::MOVE_TO;
|
\RainLoop\Providers\Filters\Enumerations\ActionType::MOVE_TO;
|
||||||
|
|
@ -165,7 +204,15 @@ class Filter
|
||||||
|
|
||||||
$this->bMarkAsRead = isset($aFilter['MarkAsRead']) ? $aFilter['MarkAsRead'] : false;
|
$this->bMarkAsRead = isset($aFilter['MarkAsRead']) ? $aFilter['MarkAsRead'] : false;
|
||||||
$this->bSkipOthers = isset($aFilter['SkipOthers']) ? $aFilter['SkipOthers'] : false;
|
$this->bSkipOthers = isset($aFilter['SkipOthers']) ? $aFilter['SkipOthers'] : false;
|
||||||
|
$this->bKeepForward = isset($aFilter['KeepForward']) ? $aFilter['KeepForward'] : true;
|
||||||
|
|
||||||
|
$this->aConditions = \RainLoop\Providers\Filters\Classes\FilterCondition::CollectionFromJSON(
|
||||||
|
isset($aFilter['Conditions']) ? $aFilter['Conditions'] : array());
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -186,9 +233,10 @@ class Filter
|
||||||
|
|
||||||
return array(
|
return array(
|
||||||
'ID' => $this->ID(),
|
'ID' => $this->ID(),
|
||||||
|
'Enabled' => $this->Enabled(),
|
||||||
'Name' => $this->Name(),
|
'Name' => $this->Name(),
|
||||||
'Conditions' => $aConditions,
|
'Conditions' => $aConditions,
|
||||||
'FilterRulesType' => $this->FilterRulesType(),
|
'ConditionsType' => $this->ConditionsType(),
|
||||||
'ActionType' => $this->ActionType(),
|
'ActionType' => $this->ActionType(),
|
||||||
'ActionValue' => $this->ActionValue(),
|
'ActionValue' => $this->ActionValue(),
|
||||||
'MarkAsRead' => $this->MarkAsRead(),
|
'MarkAsRead' => $this->MarkAsRead(),
|
||||||
|
|
|
||||||
|
|
@ -56,22 +56,26 @@ class FilterCondition
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param array $aFilter
|
* @param array $aData
|
||||||
*
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function FromJSON($aFilter)
|
public function FromJSON($aData)
|
||||||
{
|
{
|
||||||
if (\is_array($aFilter))
|
if (\is_array($aData))
|
||||||
{
|
{
|
||||||
$this->sField = isset($aFilter['Field']) ? $aFilter['Field'] :
|
$this->sField = isset($aData['Field']) ? $aData['Field'] :
|
||||||
\RainLoop\Providers\Filters\Enumerations\ConditionField::FROM;
|
\RainLoop\Providers\Filters\Enumerations\ConditionField::FROM;
|
||||||
|
|
||||||
$this->sType = isset($aFilter['Type']) ? $aFilter['Type'] :
|
$this->sType = isset($aData['Type']) ? $aData['Type'] :
|
||||||
\RainLoop\Providers\Filters\Enumerations\ConditionType::EQUAL_TO;
|
\RainLoop\Providers\Filters\Enumerations\ConditionType::EQUAL_TO;
|
||||||
|
|
||||||
$this->sValue = isset($aFilter['Value']) ? $aFilter['Value'] : '';
|
$this->sValue = isset($aData['Value']) ? $aData['Value'] : '';
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -87,4 +91,28 @@ class FilterCondition
|
||||||
'Value' => $this->Value()
|
'Value' => $this->Value()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public static function CollectionFromJSON($aCollection)
|
||||||
|
{
|
||||||
|
$aResult = array();
|
||||||
|
if (\is_array($aCollection) && 0 < \count($aCollection))
|
||||||
|
{
|
||||||
|
foreach ($aCollection as $aItem)
|
||||||
|
{
|
||||||
|
if (\is_array($aItem) && 0 < \count($aItem))
|
||||||
|
{
|
||||||
|
$oItem = new \RainLoop\Providers\Filters\Classes\FilterCondition();
|
||||||
|
if ($oItem->FromJSON($aItem))
|
||||||
|
{
|
||||||
|
$aResult[] = $oItem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $aResult;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,4 +7,5 @@ class ActionType
|
||||||
const NONE = 'None';
|
const NONE = 'None';
|
||||||
const MOVE_TO = 'MoveTo';
|
const MOVE_TO = 'MoveTo';
|
||||||
const DISCARD = 'Discard';
|
const DISCARD = 'Discard';
|
||||||
|
const FORWARD = 'Forward';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
namespace RainLoop\Providers\Filters\Enumerations;
|
namespace RainLoop\Providers\Filters\Enumerations;
|
||||||
|
|
||||||
class FilterRulesType
|
class ConditionsType
|
||||||
{
|
{
|
||||||
const ALL = 'All';
|
const ALL = 'All';
|
||||||
const ANY = 'Any';
|
const ANY = 'Any';
|
||||||
|
|
@ -4,11 +4,21 @@ namespace RainLoop\Providers\Filters;
|
||||||
|
|
||||||
class SieveStorage implements \RainLoop\Providers\Filters\FiltersInterface
|
class SieveStorage implements \RainLoop\Providers\Filters\FiltersInterface
|
||||||
{
|
{
|
||||||
|
const NEW_LINE = "\n";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
private $bUtf8FolderName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param bool $bUtf8FolderName = true
|
||||||
|
*
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function __construct()
|
public function __construct($bUtf8FolderName = true)
|
||||||
{
|
{
|
||||||
|
$this->bUtf8FolderName = !!$bUtf8FolderName;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -16,7 +26,6 @@ class SieveStorage implements \RainLoop\Providers\Filters\FiltersInterface
|
||||||
*/
|
*/
|
||||||
public function Load()
|
public function Load()
|
||||||
{
|
{
|
||||||
// TODO
|
|
||||||
return $this->fileStringToCollection(@\file_get_contents('e:/sieve.txt'));
|
return $this->fileStringToCollection(@\file_get_contents('e:/sieve.txt'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -30,6 +39,215 @@ class SieveStorage implements \RainLoop\Providers\Filters\FiltersInterface
|
||||||
return @\file_put_contents('e:/sieve.txt', $this->collectionToFileString($aFilters));
|
return @\file_put_contents('e:/sieve.txt', $this->collectionToFileString($aFilters));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param \RainLoop\Providers\Filters\Classes\FilterCondition $oCondition
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
private function conditionToSieveScript($oCondition)
|
||||||
|
{
|
||||||
|
$sResult = '';
|
||||||
|
$sTypeWord = '';
|
||||||
|
|
||||||
|
$sValue = \trim($oCondition->Value());
|
||||||
|
if (0 < strlen($sValue))
|
||||||
|
{
|
||||||
|
switch ($oCondition->Type())
|
||||||
|
{
|
||||||
|
case \RainLoop\Providers\Filters\Enumerations\ConditionType::NOT_EQUAL_TO:
|
||||||
|
$sResult .= 'not ';
|
||||||
|
case \RainLoop\Providers\Filters\Enumerations\ConditionType::EQUAL_TO:
|
||||||
|
$sTypeWord = ':is';
|
||||||
|
break;
|
||||||
|
case \RainLoop\Providers\Filters\Enumerations\ConditionType::NOT_CONTAINS:
|
||||||
|
$sResult .= 'not ';
|
||||||
|
case \RainLoop\Providers\Filters\Enumerations\ConditionType::CONTAINS:
|
||||||
|
$sTypeWord = ':contains';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ($oCondition->Field())
|
||||||
|
{
|
||||||
|
case \RainLoop\Providers\Filters\Enumerations\ConditionField::FROM:
|
||||||
|
$sResult .= 'header '.$sTypeWord.' ["From"]';
|
||||||
|
break;
|
||||||
|
case \RainLoop\Providers\Filters\Enumerations\ConditionField::RECIPIENT:
|
||||||
|
$sResult .= 'header '.$sTypeWord.' ["To", "CC"]';
|
||||||
|
break;
|
||||||
|
case \RainLoop\Providers\Filters\Enumerations\ConditionField::SUBJECT:
|
||||||
|
$sResult .= 'header '.$sTypeWord.' ["Subject"]';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (\in_array($oCondition->Field(), array(
|
||||||
|
\RainLoop\Providers\Filters\Enumerations\ConditionField::FROM,
|
||||||
|
\RainLoop\Providers\Filters\Enumerations\ConditionField::RECIPIENT
|
||||||
|
)) && false !== \strpos($sValue, ','))
|
||||||
|
{
|
||||||
|
$self = $this;
|
||||||
|
$aValue = \array_map(function ($sValue) use ($self) {
|
||||||
|
return '"'.$self->quote(\trim($sValue)).'"';
|
||||||
|
}, \explode(',', $sValue));
|
||||||
|
|
||||||
|
$sResult .= ' ['.\trim(\implode(', ', $aValue)).']';
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$sResult .= ' "'.$this->quote($sValue).'"';
|
||||||
|
}
|
||||||
|
|
||||||
|
$sResult = \preg_replace('/[\s]+/u', ' ', $sResult);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$sResult = '/* @Error: empty condition value */ false';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $sResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param \RainLoop\Providers\Filters\Classes\Filter $oFilter
|
||||||
|
* @param array $aCapa
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
private function filterToSieveScript($oFilter, &$aCapa)
|
||||||
|
{
|
||||||
|
$sNL = \RainLoop\Providers\Filters\SieveStorage::NEW_LINE;
|
||||||
|
$sTab = ' ';
|
||||||
|
|
||||||
|
$bAll = false;
|
||||||
|
$aResult = array();
|
||||||
|
|
||||||
|
// Conditions
|
||||||
|
$aConditions = $oFilter->Conditions();
|
||||||
|
if (\is_array($aConditions))
|
||||||
|
{
|
||||||
|
if (1 < \count($aConditions))
|
||||||
|
{
|
||||||
|
if (\RainLoop\Providers\Filters\Enumerations\ConditionsType::ANY ===
|
||||||
|
$oFilter->ConditionsType())
|
||||||
|
{
|
||||||
|
$aResult[] = 'if anyof(';
|
||||||
|
|
||||||
|
$bTrim = false;
|
||||||
|
foreach ($aConditions as $oCond)
|
||||||
|
{
|
||||||
|
$bTrim = true;
|
||||||
|
$sCons = $this->conditionToSieveScript($oCond);
|
||||||
|
if (!empty($sCons))
|
||||||
|
{
|
||||||
|
$aResult[] = $sTab.$sCons.',';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($bTrim)
|
||||||
|
{
|
||||||
|
$aResult[\count($aResult) - 1] = \rtrim($aResult[\count($aResult) - 1], ',');
|
||||||
|
}
|
||||||
|
|
||||||
|
$aResult[] = ')';
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$aResult[] = 'if allof(';
|
||||||
|
foreach ($aConditions as $oCond)
|
||||||
|
{
|
||||||
|
$aResult[] = $sTab.$this->conditionToSieveScript($oCond).',';
|
||||||
|
}
|
||||||
|
|
||||||
|
$aResult[\count($aResult) - 1] = \rtrim($aResult[\count($aResult) - 1], ',');
|
||||||
|
$aResult[] = ')';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (1 === \count($aConditions))
|
||||||
|
{
|
||||||
|
$aResult[] = 'if '.$this->conditionToSieveScript($aConditions[0]).'';
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$bAll = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// actions
|
||||||
|
if (!$bAll)
|
||||||
|
{
|
||||||
|
$aResult[] = '{';
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$sTab = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($oFilter->MarkAsRead())
|
||||||
|
{
|
||||||
|
$aCapa['imap4flags'] = true;
|
||||||
|
$aResult[] = $sTab.'addflag "\\\\Seen";';
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ($oFilter->ActionType())
|
||||||
|
{
|
||||||
|
case \RainLoop\Providers\Filters\Enumerations\ActionType::NONE:
|
||||||
|
$aResult[] = $sTab.'stop;';
|
||||||
|
break;
|
||||||
|
case \RainLoop\Providers\Filters\Enumerations\ActionType::DISCARD:
|
||||||
|
$aResult[] = $sTab.'discard;';
|
||||||
|
$aResult[] = $sTab.'stop;';
|
||||||
|
break;
|
||||||
|
case \RainLoop\Providers\Filters\Enumerations\ActionType::FORWARD:
|
||||||
|
$sValue = $oFilter->ActionValue();
|
||||||
|
if (0 < \strlen($sValue))
|
||||||
|
{
|
||||||
|
if ($oFilter->KeepForward())
|
||||||
|
{
|
||||||
|
$aCapa['copy'] = true;
|
||||||
|
$aResult[] = $sTab.'redirect :copy "'.$this->quote($sValue).'";';
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$aResult[] = $sTab.'redirect "'.$this->quote($sValue).'";';
|
||||||
|
}
|
||||||
|
|
||||||
|
$aResult[] = $sTab.'stop;';
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$aResult[] = $sTab.'# @Error (redirect): empty action value';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case \RainLoop\Providers\Filters\Enumerations\ActionType::MOVE_TO:
|
||||||
|
$sValue = $oFilter->ActionValue();
|
||||||
|
if (0 < \strlen($sValue))
|
||||||
|
{
|
||||||
|
$aCapa['fileinto'] = true;
|
||||||
|
|
||||||
|
$sFolderName = $sValue; // utf7-imap
|
||||||
|
if ($this->bUtf8FolderName) // to utf-8
|
||||||
|
{
|
||||||
|
$sFolderName = \MailSo\Base\Utils::ConvertEncoding($sFolderName,
|
||||||
|
\MailSo\Base\Enumerations\Charset::UTF_7_IMAP,
|
||||||
|
\MailSo\Base\Enumerations\Charset::UTF_8);
|
||||||
|
}
|
||||||
|
|
||||||
|
$aResult[] = $sTab.'fileinto "'.$this->quote($sFolderName).'";';
|
||||||
|
$aResult[] = $sTab.'stop;';
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$aResult[] = $sTab.'# @Error (fileinto): empty action value';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$bAll)
|
||||||
|
{
|
||||||
|
$aResult[] = '}';
|
||||||
|
}
|
||||||
|
|
||||||
|
return \implode($sNL, $aResult);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param array $aFilters
|
* @param array $aFilters
|
||||||
*
|
*
|
||||||
|
|
@ -37,17 +255,38 @@ class SieveStorage implements \RainLoop\Providers\Filters\FiltersInterface
|
||||||
*/
|
*/
|
||||||
private function collectionToFileString($aFilters)
|
private function collectionToFileString($aFilters)
|
||||||
{
|
{
|
||||||
|
$sNL = \RainLoop\Providers\Filters\SieveStorage::NEW_LINE;
|
||||||
|
|
||||||
|
$aCapa = array();
|
||||||
$aParts = array();
|
$aParts = array();
|
||||||
|
|
||||||
|
$aParts[] = '# This is RainLoop Webmail sieve script.';
|
||||||
|
$aParts[] = '# Please don\'t change anything here.';
|
||||||
|
$aParts[] = '# RAINLOOP:SIEVE';
|
||||||
|
$aParts[] = '';
|
||||||
|
|
||||||
foreach ($aFilters as /* @var $oItem \RainLoop\Providers\Filters\Classes\Filter */ $oItem)
|
foreach ($aFilters as /* @var $oItem \RainLoop\Providers\Filters\Classes\Filter */ $oItem)
|
||||||
{
|
{
|
||||||
$sItem = $oItem->serializeToJson();
|
$aData = array();
|
||||||
$sItem = \chunk_split(\base64_encode($sItem), 74, "\n");
|
$aData[] = '/*';
|
||||||
|
$aData[] = 'BEGIN:FILTER:'.$oItem->ID();
|
||||||
|
$aData[] = 'BEGIN:HEADER';
|
||||||
|
$aData[] = \chunk_split(\base64_encode($oItem->serializeToJson()), 74, $sNL).'END:HEADER';
|
||||||
|
$aData[] = '*/';
|
||||||
|
$aData[] = $oItem->Enabled() ? '' : '/* @Filter is disabled ';
|
||||||
|
$aData[] = $this->filterToSieveScript($oItem, $aCapa);
|
||||||
|
$aData[] = $oItem->Enabled() ? '' : '*/';
|
||||||
|
$aData[] = '/* END:FILTER */';
|
||||||
|
$aData[] = '';
|
||||||
|
|
||||||
$aParts[] = $sItem;
|
$aParts[] = \implode($sNL, $aData);
|
||||||
}
|
}
|
||||||
|
|
||||||
return \implode("\n", $aParts);
|
$aCapa = \array_keys($aCapa);
|
||||||
|
$sCapa = 0 < \count($aCapa) ? $sNL.'require '.
|
||||||
|
\str_replace('","', '", "', \json_encode($aCapa)).';'.$sNL : '';
|
||||||
|
|
||||||
|
return $sCapa.$sNL.\implode($sNL, $aParts).$sNL;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -57,11 +296,41 @@ class SieveStorage implements \RainLoop\Providers\Filters\FiltersInterface
|
||||||
*/
|
*/
|
||||||
private function fileStringToCollection($sFileString)
|
private function fileStringToCollection($sFileString)
|
||||||
{
|
{
|
||||||
if (!empty($sFileString))
|
$aResult = array();
|
||||||
|
if (!empty($sFileString) && false !== \strpos($sFileString, 'RAINLOOP:SIEVE'))
|
||||||
{
|
{
|
||||||
|
$aMatch = array();
|
||||||
|
if (\preg_match_all('/BEGIN:FILTER(.+?)BEGIN:HEADER(.+?)END:HEADER/s', $sFileString, $aMatch) &&
|
||||||
|
isset($aMatch[2]) && \is_array($aMatch[2]))
|
||||||
|
{
|
||||||
|
foreach ($aMatch[2] as $sEncodedLine)
|
||||||
|
{
|
||||||
|
if (!empty($sEncodedLine))
|
||||||
|
{
|
||||||
|
$sDecodedLine = \base64_decode(\preg_replace('/[\s]+/', '', $sEncodedLine));
|
||||||
|
if (!empty($sDecodedLine))
|
||||||
|
{
|
||||||
|
$oItem = new \RainLoop\Providers\Filters\Classes\Filter();
|
||||||
|
if ($oItem && $oItem->unserializeFromJson($sDecodedLine))
|
||||||
|
{
|
||||||
|
$aResult[] = $oItem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return array();
|
return $aResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $sValue
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
private function quote($sValue)
|
||||||
|
{
|
||||||
|
return \str_replace('"', '\\"', \trim($sValue));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
|
|
||||||
<div class="control-group" data-bind="css: {'error': name.error}">
|
<div class="control-group" data-bind="css: {'error': name.error}">
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
<input type="text" class="span5" data-bind="value: name, hasFocus: name.focused" placeholder="Name"
|
<input type="text" class="span5" data-bind="value: name, hasFocus: name.focused" placeholder="Name"
|
||||||
autocorrect="off" autocapitalize="off" spellcheck="false" />
|
autocorrect="off" autocapitalize="off" spellcheck="false" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -27,12 +27,12 @@
|
||||||
<div>
|
<div>
|
||||||
<div data-bind="visible: 1 < conditions().length">
|
<div data-bind="visible: 1 < conditions().length">
|
||||||
<select class="span4" data-bind="value: conditionsType">
|
<select class="span4" data-bind="value: conditionsType">
|
||||||
<option value="All">
|
|
||||||
Matching all of the following rules @i18n
|
|
||||||
</option>
|
|
||||||
<option value="Any">
|
<option value="Any">
|
||||||
Matching any of the following rules @i18n
|
Matching any of the following rules @i18n
|
||||||
</option>
|
</option>
|
||||||
|
<option value="All">
|
||||||
|
Matching all of the following rules @i18n
|
||||||
|
</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div data-bind="visible: 0 < conditions().length, foreach: conditions">
|
<div data-bind="visible: 0 < conditions().length, foreach: conditions">
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
<select data-bind="options: $root.actionTypeOptions, value: actionType, optionsText: 'name', optionsValue: 'id'"></select>
|
||||||
|
|
||||||
|
<input type="text" data-bind="value: actionValue" />
|
||||||
|
|
||||||
|
<div data-bind="component: {
|
||||||
|
name: 'Checkbox',
|
||||||
|
params: {
|
||||||
|
label: 'keep in INBOX @i18n',
|
||||||
|
value: keepForward
|
||||||
|
}
|
||||||
|
}"></div>
|
||||||
Loading…
Add table
Add a link
Reference in a new issue