mirror of
https://github.com/the-djmaze/snappymail.git
synced 2025-10-07 20:25:48 +08:00
Split collectionToFileString() from SieveStorage to Sieve
Converted SieveStorage fileStringToCollection() to JavaScript sieveScriptToFilters() Drop the old filtersSave()
This commit is contained in:
parent
d9118fbf90
commit
dd9f277ccf
7 changed files with 509 additions and 554 deletions
|
@ -4,7 +4,7 @@ import { AbstractModel } from 'Knoin/AbstractModel';
|
||||||
import { FilterModel } from 'Model/Filter';
|
import { FilterModel } from 'Model/Filter';
|
||||||
|
|
||||||
// collectionToFileString
|
// collectionToFileString
|
||||||
function filtersToSieveScript(aFilters)
|
function filtersToSieveScript(filters)
|
||||||
{
|
{
|
||||||
let eol = '\r\n',
|
let eol = '\r\n',
|
||||||
split = /.{0,74}/g,
|
split = /.{0,74}/g,
|
||||||
|
@ -16,8 +16,8 @@ function filtersToSieveScript(aFilters)
|
||||||
''
|
''
|
||||||
];
|
];
|
||||||
|
|
||||||
const quote = sValue => '"' + sValue.trim().replace(/(\\|")/, '\\\\$1') + '"';
|
const quote = string => '"' + string.trim().replace(/(\\|")/, '\\\\$1') + '"';
|
||||||
const StripSpaces = sValue => sValue.replace(/\s+/, ' ').trim();
|
const StripSpaces = string => string.replace(/\s+/, ' ').trim();
|
||||||
|
|
||||||
// conditionToSieveScript
|
// conditionToSieveScript
|
||||||
const conditionToString = (condition, require) =>
|
const conditionToString = (condition, require) =>
|
||||||
|
@ -83,16 +83,11 @@ function filtersToSieveScript(aFilters)
|
||||||
return '/* @Error: unknown field value ' + field + ' */ false';
|
return '/* @Error: unknown field value ' + field + ' */ false';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (('From' === field || 'Recipient' === field) && value.includes(','))
|
if (('From' === field || 'Recipient' === field) && value.includes(',')) {
|
||||||
{
|
|
||||||
result += ' [' + value.split(',').map(value => quote(value)).join(', ').trim() + ']';
|
result += ' [' + value.split(',').map(value => quote(value)).join(', ').trim() + ']';
|
||||||
}
|
} else if ('Size' === field) {
|
||||||
else if ('Size' === field)
|
|
||||||
{
|
|
||||||
result += ' ' + value;
|
result += ' ' + value;
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
result += ' ' + quote(value);
|
result += ' ' + quote(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,12 +102,12 @@ function filtersToSieveScript(aFilters)
|
||||||
{
|
{
|
||||||
let sTab = ' ',
|
let sTab = ' ',
|
||||||
block = true,
|
block = true,
|
||||||
result = [];
|
result = [],
|
||||||
|
conditions = filter.conditions();
|
||||||
|
|
||||||
const errorAction = type => result.push(sTab + '# @Error (' + type + '): empty action value');
|
const errorAction = type => result.push(sTab + '# @Error (' + type + '): empty action value');
|
||||||
|
|
||||||
// Conditions
|
// Conditions
|
||||||
let conditions = filter.conditions();
|
|
||||||
if (1 < conditions.length) {
|
if (1 < conditions.length) {
|
||||||
result.push('Any' === filter.conditionsType()
|
result.push('Any' === filter.conditionsType()
|
||||||
? 'if anyof('
|
? 'if anyof('
|
||||||
|
@ -127,28 +122,24 @@ function filtersToSieveScript(aFilters)
|
||||||
}
|
}
|
||||||
|
|
||||||
// actions
|
// actions
|
||||||
if (block) {
|
block ? result.push('{') : (sTab = '');
|
||||||
result.push('{');
|
|
||||||
} else {
|
|
||||||
sTab = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (filter.actionMarkAsRead() && ['None','MoveTo','Forward'].includes(filter.actionType())) {
|
if (filter.actionMarkAsRead() && ['None','MoveTo','Forward'].includes(filter.actionType())) {
|
||||||
require.imap4flags = 1;
|
require.imap4flags = 1;
|
||||||
result.push(sTab + 'addflag "\\\\Seen";');
|
result.push(sTab + 'addflag "\\\\Seen";');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let value = filter.actionValue().trim();
|
||||||
|
value = value.length ? quote(value) : 0;
|
||||||
switch (filter.actionType())
|
switch (filter.actionType())
|
||||||
{
|
{
|
||||||
// case FiltersAction.None:
|
|
||||||
case 'None':
|
case 'None':
|
||||||
break;
|
break;
|
||||||
case 'Discard':
|
case 'Discard':
|
||||||
result.push(sTab + 'discard;');
|
result.push(sTab + 'discard;');
|
||||||
break;
|
break;
|
||||||
case 'Vacation': {
|
case 'Vacation':
|
||||||
let value = filter.actionValue().trim();
|
if (value) {
|
||||||
if (value.length) {
|
|
||||||
require.vacation = 1;
|
require.vacation = 1;
|
||||||
|
|
||||||
let days = 1,
|
let days = 1,
|
||||||
|
@ -175,41 +166,38 @@ function filtersToSieveScript(aFilters)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
result.push(sTab + 'vacation :days ' + days + ' ' + addresses + subject + quote(value) + ';');
|
result.push(sTab + 'vacation :days ' + days + ' ' + addresses + subject + value + ';');
|
||||||
} else {
|
} else {
|
||||||
errorAction('vacation');
|
errorAction('vacation');
|
||||||
}
|
}
|
||||||
break; }
|
break;
|
||||||
case 'Reject': {
|
case 'Reject': {
|
||||||
let value = filter.actionValue().trim();
|
if (value) {
|
||||||
if (value.length) {
|
|
||||||
require.reject = 1;
|
require.reject = 1;
|
||||||
result.push(sTab + 'reject ' + quote(value) + ';');
|
result.push(sTab + 'reject ' + value + ';');
|
||||||
} else {
|
} else {
|
||||||
errorAction('reject');
|
errorAction('reject');
|
||||||
}
|
}
|
||||||
break; }
|
break; }
|
||||||
case 'Forward': {
|
case 'Forward':
|
||||||
let value = filter.actionValue();
|
if (value) {
|
||||||
if (value.length) {
|
|
||||||
if (filter.actionKeep()) {
|
if (filter.actionKeep()) {
|
||||||
require.fileinto = 1;
|
require.fileinto = 1;
|
||||||
result.push(sTab + 'fileinto "INBOX";');
|
result.push(sTab + 'fileinto "INBOX";');
|
||||||
}
|
}
|
||||||
result.push(sTab + 'redirect ' + quote(value) + ';');
|
result.push(sTab + 'redirect ' + value + ';');
|
||||||
} else {
|
} else {
|
||||||
errorAction('redirect');
|
errorAction('redirect');
|
||||||
}
|
}
|
||||||
break; }
|
break;
|
||||||
case 'MoveTo': {
|
case 'MoveTo':
|
||||||
let value = filter.actionValue();
|
if (value) {
|
||||||
if (value.length) {
|
|
||||||
require.fileinto = 1;
|
require.fileinto = 1;
|
||||||
result.push(sTab + 'fileinto ' + quote(value) + ';');
|
result.push(sTab + 'fileinto ' + value + ';');
|
||||||
} else {
|
} else {
|
||||||
errorAction('fileinto');
|
errorAction('fileinto');
|
||||||
}
|
}
|
||||||
break; }
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
filter.actionNoStop() || result.push(sTab + 'stop;');
|
filter.actionNoStop() || result.push(sTab + 'stop;');
|
||||||
|
@ -219,7 +207,7 @@ function filtersToSieveScript(aFilters)
|
||||||
return result.join(eol);
|
return result.join(eol);
|
||||||
};
|
};
|
||||||
|
|
||||||
aFilters.forEach(filter => {
|
filters.forEach(filter => {
|
||||||
parts.push([
|
parts.push([
|
||||||
'/*',
|
'/*',
|
||||||
'BEGIN:FILTER:' + filter.id,
|
'BEGIN:FILTER:' + filter.id,
|
||||||
|
@ -238,6 +226,27 @@ function filtersToSieveScript(aFilters)
|
||||||
return (require.length ? 'require ' + JSON.stringify(require) + ';' + eol : '') + eol + parts.join(eol);
|
return (require.length ? 'require ' + JSON.stringify(require) + ';' + eol : '') + eol + parts.join(eol);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fileStringToCollection
|
||||||
|
function sieveScriptToFilters(script)
|
||||||
|
{
|
||||||
|
let regex = /BEGIN:HEADER([\s\S]+?)END:HEADER/gm,
|
||||||
|
filters = [],
|
||||||
|
json,
|
||||||
|
filter;
|
||||||
|
if (script.length && script.includes('RAINLOOP:SIEVE')) {
|
||||||
|
while ((json = regex.exec(script))) {
|
||||||
|
json = decodeURIComponent(escape(atob(json[1].replace(/\s+/g, ''))));
|
||||||
|
if (json && json.length && (json = JSON.parse(json))) {
|
||||||
|
json['@Object'] = 'Object/Filter';
|
||||||
|
json.Conditions.forEach(condition => condition['@Object'] = 'Object/FilterCondition');
|
||||||
|
filter = FilterModel.reviveFromJson(json);
|
||||||
|
filter && filters.push(filter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return filters;
|
||||||
|
}
|
||||||
|
|
||||||
class SieveScriptModel extends AbstractModel
|
class SieveScriptModel extends AbstractModel
|
||||||
{
|
{
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -266,13 +275,14 @@ class SieveScriptModel extends AbstractModel
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
setFilters() {
|
|
||||||
/*let tree = */window.Sieve.parseScript(this.body);
|
|
||||||
// this.filters = ko.observableArray(tree);
|
|
||||||
}
|
|
||||||
|
|
||||||
filtersToRaw() {
|
filtersToRaw() {
|
||||||
return filtersToSieveScript(this.filters);
|
return filtersToSieveScript(this.filters);
|
||||||
|
// this.body(filtersToSieveScript(this.filters));
|
||||||
|
}
|
||||||
|
|
||||||
|
rawToFilters() {
|
||||||
|
return sieveScriptToFilters(this.body());
|
||||||
|
// this.filters(sieveScriptToFilters(this.body()));
|
||||||
}
|
}
|
||||||
|
|
||||||
verify() {
|
verify() {
|
||||||
|
@ -305,9 +315,11 @@ class SieveScriptModel extends AbstractModel
|
||||||
static reviveFromJson(json) {
|
static reviveFromJson(json) {
|
||||||
const script = super.reviveFromJson(json);
|
const script = super.reviveFromJson(json);
|
||||||
if (script) {
|
if (script) {
|
||||||
if (script.allowFilters() && Array.isNotEmpty(json.filters)) {
|
if (script.allowFilters()) {
|
||||||
script.filters(
|
script.filters(
|
||||||
json.filters.map(aData => FilterModel.reviveFromJson(aData)).filter(v => v)
|
Array.isNotEmpty(json.filters)
|
||||||
|
? json.filters.map(aData => FilterModel.reviveFromJson(aData)).filter(v => v)
|
||||||
|
: sieveScriptToFilters(script.body())
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
script.filters([]);
|
script.filters([]);
|
||||||
|
|
|
@ -222,20 +222,6 @@ class RemoteUserFetch extends AbstractFetchRemote {
|
||||||
this.defaultRequest(fCallback, 'AccountsAndIdentities');
|
this.defaultRequest(fCallback, 'AccountsAndIdentities');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {?Function} fCallback
|
|
||||||
* @param {Array} filters
|
|
||||||
* @param {string} raw
|
|
||||||
* @param {boolean} isRawIsActive
|
|
||||||
*/
|
|
||||||
filtersSave(fCallback, filters, raw, isRawIsActive) {
|
|
||||||
this.defaultRequest(fCallback, 'FiltersSave', {
|
|
||||||
Raw: raw,
|
|
||||||
RawIsActive: isRawIsActive ? 1 : 0,
|
|
||||||
Filters: filters.map(item => item.toJson())
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {?Function} fCallback
|
* @param {?Function} fCallback
|
||||||
* @param {SieveScriptModel} script
|
* @param {SieveScriptModel} script
|
||||||
|
|
|
@ -13,8 +13,6 @@ import { showScreenPopup } from 'Knoin/Knoin';
|
||||||
|
|
||||||
class FiltersUserSettings {
|
class FiltersUserSettings {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.sieve = SieveStore;
|
|
||||||
|
|
||||||
this.scripts = SieveStore.scripts;
|
this.scripts = SieveStore.scripts;
|
||||||
this.loading = ko.observable(false).extend({ throttle: 200 });
|
this.loading = ko.observable(false).extend({ throttle: 200 });
|
||||||
|
|
||||||
|
@ -23,27 +21,24 @@ class FiltersUserSettings {
|
||||||
serverErrorDesc: ''
|
serverErrorDesc: ''
|
||||||
});
|
});
|
||||||
|
|
||||||
this.serverError.subscribe((value) => {
|
|
||||||
if (!value) {
|
|
||||||
this.serverErrorDesc('');
|
|
||||||
}
|
|
||||||
}, this);
|
|
||||||
|
|
||||||
this.scriptForDeletion = ko.observable(null).deleteAccessHelper();
|
this.scriptForDeletion = ko.observable(null).deleteAccessHelper();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setError(text) {
|
||||||
|
this.serverError(true);
|
||||||
|
this.serverErrorDesc(text);
|
||||||
|
}
|
||||||
|
|
||||||
updateList() {
|
updateList() {
|
||||||
if (!this.loading()) {
|
if (!this.loading()) {
|
||||||
this.loading(true);
|
this.loading(true);
|
||||||
|
this.serverError(false);
|
||||||
|
|
||||||
Remote.filtersGet((result, data) => {
|
Remote.filtersGet((result, data) => {
|
||||||
this.loading(false);
|
this.loading(false);
|
||||||
this.serverError(false);
|
|
||||||
this.scripts([]);
|
this.scripts([]);
|
||||||
|
|
||||||
if (StorageResultType.Success === result && data && data.Result) {
|
if (StorageResultType.Success === result && data && data.Result) {
|
||||||
this.serverError(false);
|
|
||||||
|
|
||||||
SieveStore.capa(data.Result.Capa);
|
SieveStore.capa(data.Result.Capa);
|
||||||
/*
|
/*
|
||||||
this.scripts(
|
this.scripts(
|
||||||
|
@ -55,10 +50,8 @@ class FiltersUserSettings {
|
||||||
value && this.scripts.push(value)
|
value && this.scripts.push(value)
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.scripts([]);
|
|
||||||
SieveStore.capa([]);
|
SieveStore.capa([]);
|
||||||
this.serverError(true);
|
this.setError(
|
||||||
this.serverErrorDesc(
|
|
||||||
data && data.ErrorCode ? getNotification(data.ErrorCode) : getNotification(Notification.CantGetFilters)
|
data && data.ErrorCode ? getNotification(data.ErrorCode) : getNotification(Notification.CantGetFilters)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -75,34 +68,32 @@ class FiltersUserSettings {
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteScript(script) {
|
deleteScript(script) {
|
||||||
if (!script.active()) {
|
this.serverError(false);
|
||||||
Remote.filtersScriptDelete(
|
Remote.filtersScriptDelete(
|
||||||
(result, data) => {
|
(result, data) => {
|
||||||
if (StorageResultType.Success === result && data && data.Result) {
|
if (StorageResultType.Success === result && data && data.Result) {
|
||||||
this.scripts.remove(script);
|
this.scripts.remove(script);
|
||||||
delegateRunOnDestroy(script);
|
delegateRunOnDestroy(script);
|
||||||
} else {
|
} else {
|
||||||
this.serverError(true);
|
this.setError((data && data.ErrorCode)
|
||||||
this.serverErrorDesc((data && data.ErrorCode)
|
? (data.ErrorMessageAdditional || getNotification(data.ErrorCode))
|
||||||
? (data.ErrorMessageAdditional || getNotification(data.ErrorCode))
|
: getNotification(Notification.CantActivateFiltersScript)
|
||||||
: getNotification(Notification.CantActivateFiltersScript)
|
);
|
||||||
);
|
}
|
||||||
}
|
},
|
||||||
},
|
script.name()
|
||||||
script.name()
|
);
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleScript(script) {
|
toggleScript(script) {
|
||||||
let name = script.active() ? '' : script.name();
|
let name = script.active() ? '' : script.name();
|
||||||
|
this.serverError(false);
|
||||||
Remote.filtersScriptActivate(
|
Remote.filtersScriptActivate(
|
||||||
(result, data) => {
|
(result, data) => {
|
||||||
if (StorageResultType.Success === result && data && data.Result) {
|
if (StorageResultType.Success === result && data && data.Result) {
|
||||||
this.scripts().forEach(script => script.active(script.name() === name));
|
this.scripts().forEach(script => script.active(script.name() === name));
|
||||||
} else {
|
} else {
|
||||||
this.serverError(true);
|
this.setError((data && data.ErrorCode)
|
||||||
this.serverErrorDesc((data && data.ErrorCode)
|
|
||||||
? (data.ErrorMessageAdditional || getNotification(data.ErrorCode))
|
? (data.ErrorMessageAdditional || getNotification(data.ErrorCode))
|
||||||
: getNotification(Notification.CantActivateFiltersScript)
|
: getNotification(Notification.CantActivateFiltersScript)
|
||||||
);
|
);
|
||||||
|
|
|
@ -41,41 +41,6 @@ trait Filters
|
||||||
/**
|
/**
|
||||||
* @throws \MailSo\Base\Exceptions\Exception
|
* @throws \MailSo\Base\Exceptions\Exception
|
||||||
*/
|
*/
|
||||||
public function DoFiltersSave() : array
|
|
||||||
{
|
|
||||||
$oAccount = $this->getAccountFromToken();
|
|
||||||
|
|
||||||
if (!$this->GetCapa(false, false, Capa::FILTERS, $oAccount))
|
|
||||||
{
|
|
||||||
return $this->FalseResponse(__FUNCTION__);
|
|
||||||
}
|
|
||||||
|
|
||||||
$aIncFilters = $this->GetActionParam('Filters', array());
|
|
||||||
|
|
||||||
$sRaw = $this->GetActionParam('Raw', '');
|
|
||||||
$bRawIsActive = '1' === (string) $this->GetActionParam('RawIsActive', '0');
|
|
||||||
|
|
||||||
$aFilters = array();
|
|
||||||
foreach ($aIncFilters as $aFilter)
|
|
||||||
{
|
|
||||||
if (\is_array($aFilter))
|
|
||||||
{
|
|
||||||
$oFilter = new \RainLoop\Providers\Filters\Classes\Filter();
|
|
||||||
if ($oFilter->FromJSON($aFilter))
|
|
||||||
{
|
|
||||||
$aFilters[] = $oFilter;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->Plugins()
|
|
||||||
->RunHook('filter.filters-save', array($oAccount, &$aFilters, &$sRaw, &$bRawIsActive))
|
|
||||||
;
|
|
||||||
|
|
||||||
return $this->DefaultResponse(__FUNCTION__, $this->FiltersProvider()->Save($oAccount,
|
|
||||||
$aFilters, $sRaw, $bRawIsActive));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function DoFiltersScriptSave() : array
|
public function DoFiltersScriptSave() : array
|
||||||
{
|
{
|
||||||
$oAccount = $this->getAccountFromToken();
|
$oAccount = $this->getAccountFromToken();
|
||||||
|
@ -108,6 +73,9 @@ trait Filters
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws \MailSo\Base\Exceptions\Exception
|
||||||
|
*/
|
||||||
public function DoFiltersScriptActivate() : array
|
public function DoFiltersScriptActivate() : array
|
||||||
{
|
{
|
||||||
$oAccount = $this->getAccountFromToken();
|
$oAccount = $this->getAccountFromToken();
|
||||||
|
@ -121,6 +89,9 @@ trait Filters
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws \MailSo\Base\Exceptions\Exception
|
||||||
|
*/
|
||||||
public function DoFiltersScriptDelete() : array
|
public function DoFiltersScriptDelete() : array
|
||||||
{
|
{
|
||||||
$oAccount = $this->getAccountFromToken();
|
$oAccount = $this->getAccountFromToken();
|
||||||
|
|
|
@ -179,25 +179,25 @@ class Filter implements \JsonSerializable
|
||||||
|
|
||||||
public function FromJSON(array $aFilter) : bool
|
public function FromJSON(array $aFilter) : bool
|
||||||
{
|
{
|
||||||
$this->sID = isset($aFilter['ID']) ? $aFilter['ID'] : '';
|
$this->sID = $aFilter['ID'] ?? '';
|
||||||
$this->sName = isset($aFilter['Name']) ? $aFilter['Name'] : '';
|
$this->sName = $aFilter['Name'] ?? '';
|
||||||
|
|
||||||
$this->bEnabled = isset($aFilter['Enabled']) ? '1' === (string) $aFilter['Enabled'] : true;
|
$this->bEnabled = !isset($aFilter['Enabled']) || !empty($aFilter['Enabled']);
|
||||||
|
|
||||||
$this->sConditionsType = isset($aFilter['ConditionsType']) ? $aFilter['ConditionsType'] :
|
$this->sConditionsType = $aFilter['ConditionsType']
|
||||||
\RainLoop\Providers\Filters\Enumerations\ConditionsType::ANY;
|
?? \RainLoop\Providers\Filters\Enumerations\ConditionsType::ANY;
|
||||||
|
|
||||||
$this->sActionType = isset($aFilter['ActionType']) ? $aFilter['ActionType'] :
|
$this->sActionType = $aFilter['ActionType']
|
||||||
\RainLoop\Providers\Filters\Enumerations\ActionType::MOVE_TO;
|
?? \RainLoop\Providers\Filters\Enumerations\ActionType::MOVE_TO;
|
||||||
|
|
||||||
$this->sActionValue = isset($aFilter['ActionValue']) ? $aFilter['ActionValue'] : '';
|
$this->sActionValue = $aFilter['ActionValue'] ?? '';
|
||||||
$this->sActionValueSecond = isset($aFilter['ActionValueSecond']) ? $aFilter['ActionValueSecond'] : '';
|
$this->sActionValueSecond = $aFilter['ActionValueSecond'] ?? '';
|
||||||
$this->sActionValueThird = isset($aFilter['ActionValueThird']) ? $aFilter['ActionValueThird'] : '';
|
$this->sActionValueThird = $aFilter['ActionValueThird'] ?? '';
|
||||||
$this->sActionValueFourth = isset($aFilter['ActionValueFourth']) ? $aFilter['ActionValueFourth'] : '';
|
$this->sActionValueFourth = $aFilter['ActionValueFourth'] ?? '';
|
||||||
|
|
||||||
$this->bKeep = isset($aFilter['Keep']) ? '1' === (string) $aFilter['Keep'] : true;
|
$this->bKeep = !isset($aFilter['Keep']) || !empty($aFilter['Keep']);
|
||||||
$this->bStop = isset($aFilter['Stop']) ? '1' === (string) $aFilter['Stop'] : true;
|
$this->bStop = !isset($aFilter['Stop']) || !empty($aFilter['Stop']);
|
||||||
$this->bMarkAsRead = isset($aFilter['MarkAsRead']) ? '1' === (string) $aFilter['MarkAsRead'] : false;
|
$this->bMarkAsRead = !isset($aFilter['MarkAsRead']) || !empty($aFilter['MarkAsRead']);
|
||||||
|
|
||||||
$this->aConditions = empty($aFilter['Conditions']) ? array() : FilterCondition::CollectionFromJSON($aFilter['Conditions']);
|
$this->aConditions = empty($aFilter['Conditions']) ? array() : FilterCondition::CollectionFromJSON($aFilter['Conditions']);
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,393 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace RainLoop\Providers\Filters;
|
||||||
|
|
||||||
|
class Sieve
|
||||||
|
{
|
||||||
|
const NEW_LINE = "\r\n";
|
||||||
|
|
||||||
|
public static $bUtf8FolderName = true;
|
||||||
|
|
||||||
|
public static function collectionToFileString(array $aFilters) : string
|
||||||
|
{
|
||||||
|
$sNL = static::NEW_LINE;
|
||||||
|
|
||||||
|
$aCapa = array();
|
||||||
|
$aParts = [
|
||||||
|
'# This is SnappyMail sieve script.',
|
||||||
|
'# Please don\'t change anything here.',
|
||||||
|
'# RAINLOOP:SIEVE',
|
||||||
|
''
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($aFilters as /* @var $oItem \RainLoop\Providers\Filters\Classes\Filter */ $oItem)
|
||||||
|
{
|
||||||
|
$aParts[] = \implode($sNL, [
|
||||||
|
'/*',
|
||||||
|
'BEGIN:FILTER:'.$oItem->ID(),
|
||||||
|
'BEGIN:HEADER',
|
||||||
|
\chunk_split(\base64_encode(\json_encode($oItem)), 74, $sNL).'END:HEADER',
|
||||||
|
'*/',
|
||||||
|
$oItem->Enabled() ? '' : '/* @Filter is disabled ',
|
||||||
|
static::filterToSieveScript($oItem, $aCapa),
|
||||||
|
$oItem->Enabled() ? '' : '*/',
|
||||||
|
'/* END:FILTER */',
|
||||||
|
''
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$aCapa = \array_keys($aCapa);
|
||||||
|
$sCapa = \count($aCapa)
|
||||||
|
? $sNL . 'require ' . \str_replace('","', '", "', \json_encode($aCapa)).';' . $sNL
|
||||||
|
: '';
|
||||||
|
|
||||||
|
return $sCapa . $sNL . \implode($sNL, $aParts);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function fileStringToCollection(string $sFileString) : array
|
||||||
|
{
|
||||||
|
$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+/s', '', $sEncodedLine));
|
||||||
|
if (!empty($sDecodedLine))
|
||||||
|
{
|
||||||
|
$oItem = new Classes\Filter();
|
||||||
|
if ($oItem && $oItem->unserializeFromJson($sDecodedLine))
|
||||||
|
{
|
||||||
|
$aResult[] = $oItem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $aResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function conditionToSieveScript(Classes\FilterCondition $oCondition, array &$aCapa) : string
|
||||||
|
{
|
||||||
|
$sResult = '';
|
||||||
|
$sTypeWord = '';
|
||||||
|
$bTrue = true;
|
||||||
|
|
||||||
|
$sValue = \trim($oCondition->Value());
|
||||||
|
$sValueSecond = \trim($oCondition->ValueSecond());
|
||||||
|
|
||||||
|
if (0 < \strlen($sValue) ||
|
||||||
|
(0 < \strlen($sValue) && 0 < \strlen($sValueSecond) &&
|
||||||
|
Enumerations\ConditionField::HEADER === $oCondition->Field()))
|
||||||
|
{
|
||||||
|
switch ($oCondition->Type())
|
||||||
|
{
|
||||||
|
case Enumerations\ConditionType::TEXT:
|
||||||
|
case Enumerations\ConditionType::RAW:
|
||||||
|
case Enumerations\ConditionType::OVER:
|
||||||
|
case Enumerations\ConditionType::UNDER:
|
||||||
|
$sTypeWord = ':' . \strtolower($oCondition->Type());
|
||||||
|
break;
|
||||||
|
case Enumerations\ConditionType::NOT_EQUAL_TO:
|
||||||
|
$sResult .= 'not ';
|
||||||
|
case Enumerations\ConditionType::EQUAL_TO:
|
||||||
|
$sTypeWord = ':is';
|
||||||
|
break;
|
||||||
|
case Enumerations\ConditionType::NOT_CONTAINS:
|
||||||
|
$sResult .= 'not ';
|
||||||
|
case Enumerations\ConditionType::CONTAINS:
|
||||||
|
$sTypeWord = ':contains';
|
||||||
|
break;
|
||||||
|
case Enumerations\ConditionType::REGEX:
|
||||||
|
$sTypeWord = ':regex';
|
||||||
|
$aCapa['regex'] = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
$bTrue = false;
|
||||||
|
$sResult = '/* @Error: unknown type value */ false';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ($oCondition->Field())
|
||||||
|
{
|
||||||
|
case Enumerations\ConditionField::FROM:
|
||||||
|
$sResult .= 'header '.$sTypeWord.' ["From"]';
|
||||||
|
break;
|
||||||
|
case Enumerations\ConditionField::RECIPIENT:
|
||||||
|
$sResult .= 'header '.$sTypeWord.' ["To", "CC"]';
|
||||||
|
break;
|
||||||
|
case Enumerations\ConditionField::SUBJECT:
|
||||||
|
$sResult .= 'header '.$sTypeWord.' ["Subject"]';
|
||||||
|
break;
|
||||||
|
case Enumerations\ConditionField::HEADER:
|
||||||
|
$sResult .= 'header '.$sTypeWord.' ["'.static::quote($sValueSecond).'"]';
|
||||||
|
break;
|
||||||
|
case Enumerations\ConditionField::BODY:
|
||||||
|
// :text :raw :content
|
||||||
|
$sResult .= 'body '.$sTypeWord.' :contains';
|
||||||
|
$aCapa['body'] = true;
|
||||||
|
break;
|
||||||
|
case Enumerations\ConditionField::SIZE:
|
||||||
|
$sResult .= 'size '.$sTypeWord;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
$bTrue = false;
|
||||||
|
$sResult = '/* @Error: unknown field value */ false';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($bTrue)
|
||||||
|
{
|
||||||
|
if (\in_array($oCondition->Field(), array(
|
||||||
|
Enumerations\ConditionField::FROM,
|
||||||
|
Enumerations\ConditionField::RECIPIENT
|
||||||
|
)) && false !== \strpos($sValue, ','))
|
||||||
|
{
|
||||||
|
$aValue = \array_map(function ($sValue) use ($self) {
|
||||||
|
return '"'.static::quote(\trim($sValue)).'"';
|
||||||
|
}, \explode(',', $sValue));
|
||||||
|
|
||||||
|
$sResult .= ' ['.\trim(\implode(', ', $aValue)).']';
|
||||||
|
}
|
||||||
|
else if (Enumerations\ConditionField::SIZE === $oCondition->Field())
|
||||||
|
{
|
||||||
|
$sResult .= ' '.static::quote($sValue);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$sResult .= ' "'.static::quote($sValue).'"';
|
||||||
|
}
|
||||||
|
|
||||||
|
$sResult = \MailSo\Base\Utils::StripSpaces($sResult);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$sResult = '/* @Error: empty condition value */ false';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $sResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function filterToSieveScript(Classes\Filter $oFilter, array &$aCapa) : string
|
||||||
|
{
|
||||||
|
$sNL = static::NEW_LINE;
|
||||||
|
$sTab = ' ';
|
||||||
|
|
||||||
|
$bBlock = true;
|
||||||
|
$aResult = array();
|
||||||
|
|
||||||
|
// Conditions
|
||||||
|
$aConditions = $oFilter->Conditions();
|
||||||
|
if (1 < \count($aConditions))
|
||||||
|
{
|
||||||
|
if (Enumerations\ConditionsType::ANY ===
|
||||||
|
$oFilter->ConditionsType())
|
||||||
|
{
|
||||||
|
$aResult[] = 'if anyof(';
|
||||||
|
|
||||||
|
$bTrim = false;
|
||||||
|
foreach ($aConditions as $oCond)
|
||||||
|
{
|
||||||
|
$bTrim = true;
|
||||||
|
$sCons = static::conditionToSieveScript($oCond, $aCapa);
|
||||||
|
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 . static::conditionToSieveScript($oCond, $aCapa) . ',';
|
||||||
|
}
|
||||||
|
|
||||||
|
$aResult[\count($aResult) - 1] = \rtrim($aResult[\count($aResult) - 1], ',');
|
||||||
|
$aResult[] = ')';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (1 === \count($aConditions))
|
||||||
|
{
|
||||||
|
$aResult[] = 'if ' . static::conditionToSieveScript($aConditions[0], $aCapa);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$bBlock = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// actions
|
||||||
|
if ($bBlock)
|
||||||
|
{
|
||||||
|
$aResult[] = '{';
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$sTab = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($oFilter->MarkAsRead() && \in_array($oFilter->ActionType(), array(
|
||||||
|
Enumerations\ActionType::NONE,
|
||||||
|
Enumerations\ActionType::MOVE_TO,
|
||||||
|
Enumerations\ActionType::FORWARD
|
||||||
|
)))
|
||||||
|
{
|
||||||
|
$aCapa['imap4flags'] = true;
|
||||||
|
$aResult[] = $sTab.'addflag "\\\\Seen";';
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ($oFilter->ActionType())
|
||||||
|
{
|
||||||
|
case Enumerations\ActionType::NONE:
|
||||||
|
if ($oFilter->Stop())
|
||||||
|
{
|
||||||
|
$aResult[] = $sTab.'stop;';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Enumerations\ActionType::DISCARD:
|
||||||
|
$aResult[] = $sTab.'discard;';
|
||||||
|
if ($oFilter->Stop())
|
||||||
|
{
|
||||||
|
$aResult[] = $sTab.'stop;';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Enumerations\ActionType::VACATION:
|
||||||
|
$sValue = \trim($oFilter->ActionValue());
|
||||||
|
$sValueSecond = \trim($oFilter->ActionValueSecond());
|
||||||
|
$sValueThird = \trim($oFilter->ActionValueThird());
|
||||||
|
$sValueFourth = \trim($oFilter->ActionValueFourth());
|
||||||
|
if (0 < \strlen($sValue))
|
||||||
|
{
|
||||||
|
$aCapa['vacation'] = true;
|
||||||
|
|
||||||
|
$iDays = 1;
|
||||||
|
$sSubject = '';
|
||||||
|
if (0 < \strlen($sValueSecond))
|
||||||
|
{
|
||||||
|
$sSubject = ':subject "'.
|
||||||
|
static::quote(\MailSo\Base\Utils::StripSpaces($sValueSecond)).'" ';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (0 < \strlen($sValueThird) && \is_numeric($sValueThird) && 1 < (int) $sValueThird)
|
||||||
|
{
|
||||||
|
$iDays = (int) $sValueThird;
|
||||||
|
}
|
||||||
|
|
||||||
|
$sAddresses = '';
|
||||||
|
if (0 < \strlen($sValueFourth))
|
||||||
|
{
|
||||||
|
$aAddresses = \explode(',', $sValueFourth);
|
||||||
|
$aAddresses = \array_filter(\array_map(function ($sEmail) use ($self) {
|
||||||
|
$sEmail = \trim($sEmail);
|
||||||
|
return 0 < \strlen($sEmail) ? '"'.static::quote($sEmail).'"' : '';
|
||||||
|
}, $aAddresses), 'strlen');
|
||||||
|
|
||||||
|
if (0 < \count($aAddresses))
|
||||||
|
{
|
||||||
|
$sAddresses = ':addresses ['.\implode(', ', $aAddresses).'] ';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$aResult[] = $sTab.'vacation :days '.$iDays.' '.$sAddresses.$sSubject.'"'.static::quote($sValue).'";';
|
||||||
|
if ($oFilter->Stop())
|
||||||
|
{
|
||||||
|
$aResult[] = $sTab.'stop;';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$aResult[] = $sTab.'# @Error (vacation): empty action value';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Enumerations\ActionType::REJECT:
|
||||||
|
$sValue = \trim($oFilter->ActionValue());
|
||||||
|
if (0 < \strlen($sValue))
|
||||||
|
{
|
||||||
|
$aCapa['reject'] = true;
|
||||||
|
|
||||||
|
$aResult[] = $sTab.'reject "'.static::quote($sValue).'";';
|
||||||
|
if ($oFilter->Stop())
|
||||||
|
{
|
||||||
|
$aResult[] = $sTab.'stop;';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$aResult[] = $sTab.'# @Error (reject): empty action value';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Enumerations\ActionType::FORWARD:
|
||||||
|
$sValue = $oFilter->ActionValue();
|
||||||
|
if (0 < \strlen($sValue))
|
||||||
|
{
|
||||||
|
if ($oFilter->Keep())
|
||||||
|
{
|
||||||
|
$aCapa['fileinto'] = true;
|
||||||
|
$aResult[] = $sTab.'fileinto "INBOX";';
|
||||||
|
}
|
||||||
|
|
||||||
|
$aResult[] = $sTab.'redirect "'.static::quote($sValue).'";';
|
||||||
|
if ($oFilter->Stop())
|
||||||
|
{
|
||||||
|
$aResult[] = $sTab.'stop;';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$aResult[] = $sTab.'# @Error (redirect): empty action value';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Enumerations\ActionType::MOVE_TO:
|
||||||
|
$sValue = $oFilter->ActionValue();
|
||||||
|
if (0 < \strlen($sValue))
|
||||||
|
{
|
||||||
|
$sFolderName = $sValue; // utf7-imap
|
||||||
|
if (static::$bUtf8FolderName) // to utf-8
|
||||||
|
{
|
||||||
|
$sFolderName = \MailSo\Base\Utils::ConvertEncoding($sFolderName,
|
||||||
|
\MailSo\Base\Enumerations\Charset::UTF_7_IMAP,
|
||||||
|
\MailSo\Base\Enumerations\Charset::UTF_8);
|
||||||
|
}
|
||||||
|
|
||||||
|
$aCapa['fileinto'] = true;
|
||||||
|
$aResult[] = $sTab.'fileinto "'.static::quote($sFolderName).'";';
|
||||||
|
if ($oFilter->Stop())
|
||||||
|
{
|
||||||
|
$aResult[] = $sTab.'stop;';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$aResult[] = $sTab.'# @Error (fileinto): empty action value';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($bBlock)
|
||||||
|
{
|
||||||
|
$aResult[] = '}';
|
||||||
|
}
|
||||||
|
|
||||||
|
return \implode($sNL, $aResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function quote(string $sValue) : string
|
||||||
|
{
|
||||||
|
return \str_replace(array('\\', '"'), array('\\\\', '\\"'), \trim($sValue));
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,8 +4,6 @@ namespace RainLoop\Providers\Filters;
|
||||||
|
|
||||||
class SieveStorage implements FiltersInterface
|
class SieveStorage implements FiltersInterface
|
||||||
{
|
{
|
||||||
const NEW_LINE = "\r\n";
|
|
||||||
|
|
||||||
const SIEVE_FILE_NAME = 'rainloop.user';
|
const SIEVE_FILE_NAME = 'rainloop.user';
|
||||||
const SIEVE_FILE_NAME_RAW = 'rainloop.user.raw';
|
const SIEVE_FILE_NAME_RAW = 'rainloop.user.raw';
|
||||||
|
|
||||||
|
@ -24,19 +22,12 @@ class SieveStorage implements FiltersInterface
|
||||||
*/
|
*/
|
||||||
private $oConfig;
|
private $oConfig;
|
||||||
|
|
||||||
/**
|
|
||||||
* @var bool
|
|
||||||
*/
|
|
||||||
private $bUtf8FolderName;
|
|
||||||
|
|
||||||
public function __construct($oPlugins, $oConfig)
|
public function __construct($oPlugins, $oConfig)
|
||||||
{
|
{
|
||||||
$this->oLogger = null;
|
$this->oLogger = null;
|
||||||
|
|
||||||
$this->oPlugins = $oPlugins;
|
$this->oPlugins = $oPlugins;
|
||||||
$this->oConfig = $oConfig;
|
$this->oConfig = $oConfig;
|
||||||
|
|
||||||
$this->bUtf8FolderName = !!$this->oConfig->Get('labs', 'sieve_utf8_folder_name', true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getConnection(\RainLoop\Model\Account $oAccount) : ?\MailSo\Sieve\ManageSieveClient
|
protected function getConnection(\RainLoop\Model\Account $oAccount) : ?\MailSo\Sieve\ManageSieveClient
|
||||||
|
@ -62,26 +53,12 @@ class SieveStorage implements FiltersInterface
|
||||||
$aList = $oSieveClient->ListScripts();
|
$aList = $oSieveClient->ListScripts();
|
||||||
|
|
||||||
foreach ($aList as $name => $active) {
|
foreach ($aList as $name => $active) {
|
||||||
if ($name != self::SIEVE_FILE_NAME) {
|
if ($bAllowRaw || $name == self::SIEVE_FILE_NAME) {
|
||||||
if ($bAllowRaw) {
|
|
||||||
$aScripts[$name] = array(
|
|
||||||
'@Object' => 'Object/SieveScript',
|
|
||||||
'name' => $name,
|
|
||||||
'active' => $active,
|
|
||||||
'body' => $oSieveClient->GetScript($name) // \trim() ?
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$sS = $oSieveClient->GetScript(self::SIEVE_FILE_NAME);
|
|
||||||
if ($sS) {
|
|
||||||
$aFilters = $this->fileStringToCollection($sS);
|
|
||||||
}
|
|
||||||
$aScripts[$name] = array(
|
$aScripts[$name] = array(
|
||||||
'@Object' => 'Object/SieveScript',
|
'@Object' => 'Object/SieveScript',
|
||||||
'name' => $name,
|
'name' => $name,
|
||||||
'active' => $active,
|
'active' => $active,
|
||||||
'body' => $oSieveClient->GetScript($name), // \trim() ?
|
'body' => $oSieveClient->GetScript($name)
|
||||||
'filters' => $aFilters
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -119,7 +96,8 @@ class SieveStorage implements FiltersInterface
|
||||||
public function Save(\RainLoop\Model\Account $oAccount, string $sScriptName, array $aFilters, string $sRaw = '') : bool
|
public function Save(\RainLoop\Model\Account $oAccount, string $sScriptName, array $aFilters, string $sRaw = '') : bool
|
||||||
{
|
{
|
||||||
if (self::SIEVE_FILE_NAME === $sScriptName) {
|
if (self::SIEVE_FILE_NAME === $sScriptName) {
|
||||||
$sRaw = $this->collectionToFileString($aFilters);
|
Sieve::$bUtf8FolderName = !!$this->oConfig->Get('labs', 'sieve_utf8_folder_name', true);
|
||||||
|
$sRaw = Sieve::collectionToFileString($aFilters);
|
||||||
}
|
}
|
||||||
$oSieveClient = $this->getConnection($oAccount);
|
$oSieveClient = $this->getConnection($oAccount);
|
||||||
if ($oSieveClient) {
|
if ($oSieveClient) {
|
||||||
|
@ -149,7 +127,17 @@ class SieveStorage implements FiltersInterface
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
public function Check(\RainLoop\Model\Account $oAccount, string $sScript) : bool
|
||||||
|
{
|
||||||
|
$oSieveClient = $this->getConnection($oAccount);
|
||||||
|
if ($oSieveClient) {
|
||||||
|
$oSieveClient->CheckScript($sScript);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
*/
|
||||||
public function Delete(\RainLoop\Model\Account $oAccount, string $sScriptName) : bool
|
public function Delete(\RainLoop\Model\Account $oAccount, string $sScriptName) : bool
|
||||||
{
|
{
|
||||||
$oSieveClient = $this->getConnection($oAccount);
|
$oSieveClient = $this->getConnection($oAccount);
|
||||||
|
@ -160,392 +148,6 @@ class SieveStorage implements FiltersInterface
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function conditionToSieveScript(Classes\FilterCondition $oCondition, array &$aCapa) : string
|
|
||||||
{
|
|
||||||
$sResult = '';
|
|
||||||
$sTypeWord = '';
|
|
||||||
$bTrue = true;
|
|
||||||
|
|
||||||
$sValue = \trim($oCondition->Value());
|
|
||||||
$sValueSecond = \trim($oCondition->ValueSecond());
|
|
||||||
|
|
||||||
if (0 < \strlen($sValue) ||
|
|
||||||
(0 < \strlen($sValue) && 0 < \strlen($sValueSecond) &&
|
|
||||||
Enumerations\ConditionField::HEADER === $oCondition->Field()))
|
|
||||||
{
|
|
||||||
switch ($oCondition->Type())
|
|
||||||
{
|
|
||||||
case Enumerations\ConditionType::TEXT:
|
|
||||||
case Enumerations\ConditionType::RAW:
|
|
||||||
case Enumerations\ConditionType::OVER:
|
|
||||||
case Enumerations\ConditionType::UNDER:
|
|
||||||
$sTypeWord = ':' . \strtolower($oCondition->Type());
|
|
||||||
break;
|
|
||||||
case Enumerations\ConditionType::NOT_EQUAL_TO:
|
|
||||||
$sResult .= 'not ';
|
|
||||||
case Enumerations\ConditionType::EQUAL_TO:
|
|
||||||
$sTypeWord = ':is';
|
|
||||||
break;
|
|
||||||
case Enumerations\ConditionType::NOT_CONTAINS:
|
|
||||||
$sResult .= 'not ';
|
|
||||||
case Enumerations\ConditionType::CONTAINS:
|
|
||||||
$sTypeWord = ':contains';
|
|
||||||
break;
|
|
||||||
case Enumerations\ConditionType::REGEX:
|
|
||||||
$sTypeWord = ':regex';
|
|
||||||
$aCapa['regex'] = true;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
$bTrue = false;
|
|
||||||
$sResult = '/* @Error: unknown type value */ false';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch ($oCondition->Field())
|
|
||||||
{
|
|
||||||
case Enumerations\ConditionField::FROM:
|
|
||||||
$sResult .= 'header '.$sTypeWord.' ["From"]';
|
|
||||||
break;
|
|
||||||
case Enumerations\ConditionField::RECIPIENT:
|
|
||||||
$sResult .= 'header '.$sTypeWord.' ["To", "CC"]';
|
|
||||||
break;
|
|
||||||
case Enumerations\ConditionField::SUBJECT:
|
|
||||||
$sResult .= 'header '.$sTypeWord.' ["Subject"]';
|
|
||||||
break;
|
|
||||||
case Enumerations\ConditionField::HEADER:
|
|
||||||
$sResult .= 'header '.$sTypeWord.' ["'.$this->quote($sValueSecond).'"]';
|
|
||||||
break;
|
|
||||||
case Enumerations\ConditionField::BODY:
|
|
||||||
// :text :raw :content
|
|
||||||
$sResult .= 'body '.$sTypeWord.' :contains';
|
|
||||||
$aCapa['body'] = true;
|
|
||||||
break;
|
|
||||||
case Enumerations\ConditionField::SIZE:
|
|
||||||
$sResult .= 'size '.$sTypeWord;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
$bTrue = false;
|
|
||||||
$sResult = '/* @Error: unknown field value */ false';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($bTrue)
|
|
||||||
{
|
|
||||||
if (\in_array($oCondition->Field(), array(
|
|
||||||
Enumerations\ConditionField::FROM,
|
|
||||||
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 if (Enumerations\ConditionField::SIZE === $oCondition->Field())
|
|
||||||
{
|
|
||||||
$sResult .= ' '.$this->quote($sValue);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
$sResult .= ' "'.$this->quote($sValue).'"';
|
|
||||||
}
|
|
||||||
|
|
||||||
$sResult = \MailSo\Base\Utils::StripSpaces($sResult);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
$sResult = '/* @Error: empty condition value */ false';
|
|
||||||
}
|
|
||||||
|
|
||||||
return $sResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function filterToSieveScript(Classes\Filter $oFilter, array &$aCapa) : string
|
|
||||||
{
|
|
||||||
$sNL = static::NEW_LINE;
|
|
||||||
$sTab = ' ';
|
|
||||||
|
|
||||||
$bAll = false;
|
|
||||||
$aResult = array();
|
|
||||||
|
|
||||||
// Conditions
|
|
||||||
$aConditions = $oFilter->Conditions();
|
|
||||||
if (1 < \count($aConditions))
|
|
||||||
{
|
|
||||||
if (Enumerations\ConditionsType::ANY ===
|
|
||||||
$oFilter->ConditionsType())
|
|
||||||
{
|
|
||||||
$aResult[] = 'if anyof(';
|
|
||||||
|
|
||||||
$bTrim = false;
|
|
||||||
foreach ($aConditions as $oCond)
|
|
||||||
{
|
|
||||||
$bTrim = true;
|
|
||||||
$sCons = $this->conditionToSieveScript($oCond, $aCapa);
|
|
||||||
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, $aCapa).',';
|
|
||||||
}
|
|
||||||
|
|
||||||
$aResult[\count($aResult) - 1] = \rtrim($aResult[\count($aResult) - 1], ',');
|
|
||||||
$aResult[] = ')';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (1 === \count($aConditions))
|
|
||||||
{
|
|
||||||
$aResult[] = 'if '.$this->conditionToSieveScript($aConditions[0], $aCapa).'';
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
$bAll = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// actions
|
|
||||||
if (!$bAll)
|
|
||||||
{
|
|
||||||
$aResult[] = '{';
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
$sTab = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($oFilter->MarkAsRead() && \in_array($oFilter->ActionType(), array(
|
|
||||||
Enumerations\ActionType::NONE,
|
|
||||||
Enumerations\ActionType::MOVE_TO,
|
|
||||||
Enumerations\ActionType::FORWARD
|
|
||||||
)))
|
|
||||||
{
|
|
||||||
$aCapa['imap4flags'] = true;
|
|
||||||
$aResult[] = $sTab.'addflag "\\\\Seen";';
|
|
||||||
}
|
|
||||||
|
|
||||||
switch ($oFilter->ActionType())
|
|
||||||
{
|
|
||||||
case Enumerations\ActionType::NONE:
|
|
||||||
if ($oFilter->Stop())
|
|
||||||
{
|
|
||||||
$aResult[] = $sTab.'stop;';
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case Enumerations\ActionType::DISCARD:
|
|
||||||
$aResult[] = $sTab.'discard;';
|
|
||||||
if ($oFilter->Stop())
|
|
||||||
{
|
|
||||||
$aResult[] = $sTab.'stop;';
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case Enumerations\ActionType::VACATION:
|
|
||||||
$sValue = \trim($oFilter->ActionValue());
|
|
||||||
$sValueSecond = \trim($oFilter->ActionValueSecond());
|
|
||||||
$sValueThird = \trim($oFilter->ActionValueThird());
|
|
||||||
$sValueFourth = \trim($oFilter->ActionValueFourth());
|
|
||||||
if (0 < \strlen($sValue))
|
|
||||||
{
|
|
||||||
$aCapa['vacation'] = true;
|
|
||||||
|
|
||||||
$iDays = 1;
|
|
||||||
$sSubject = '';
|
|
||||||
if (0 < \strlen($sValueSecond))
|
|
||||||
{
|
|
||||||
$sSubject = ':subject "'.
|
|
||||||
$this->quote(\MailSo\Base\Utils::StripSpaces($sValueSecond)).'" ';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (0 < \strlen($sValueThird) && \is_numeric($sValueThird) && 1 < (int) $sValueThird)
|
|
||||||
{
|
|
||||||
$iDays = (int) $sValueThird;
|
|
||||||
}
|
|
||||||
|
|
||||||
$sAddresses = '';
|
|
||||||
if (0 < \strlen($sValueFourth))
|
|
||||||
{
|
|
||||||
$self = $this;
|
|
||||||
|
|
||||||
$aAddresses = \explode(',', $sValueFourth);
|
|
||||||
$aAddresses = \array_filter(\array_map(function ($sEmail) use ($self) {
|
|
||||||
$sEmail = \trim($sEmail);
|
|
||||||
return 0 < \strlen($sEmail) ? '"'.$self->quote($sEmail).'"' : '';
|
|
||||||
}, $aAddresses), 'strlen');
|
|
||||||
|
|
||||||
if (0 < \count($aAddresses))
|
|
||||||
{
|
|
||||||
$sAddresses = ':addresses ['.\implode(', ', $aAddresses).'] ';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$aResult[] = $sTab.'vacation :days '.$iDays.' '.$sAddresses.$sSubject.'"'.$this->quote($sValue).'";';
|
|
||||||
if ($oFilter->Stop())
|
|
||||||
{
|
|
||||||
$aResult[] = $sTab.'stop;';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
$aResult[] = $sTab.'# @Error (vacation): empty action value';
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case Enumerations\ActionType::REJECT:
|
|
||||||
$sValue = \trim($oFilter->ActionValue());
|
|
||||||
if (0 < \strlen($sValue))
|
|
||||||
{
|
|
||||||
$aCapa['reject'] = true;
|
|
||||||
|
|
||||||
$aResult[] = $sTab.'reject "'.$this->quote($sValue).'";';
|
|
||||||
if ($oFilter->Stop())
|
|
||||||
{
|
|
||||||
$aResult[] = $sTab.'stop;';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
$aResult[] = $sTab.'# @Error (reject): empty action value';
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case Enumerations\ActionType::FORWARD:
|
|
||||||
$sValue = $oFilter->ActionValue();
|
|
||||||
if (0 < \strlen($sValue))
|
|
||||||
{
|
|
||||||
if ($oFilter->Keep())
|
|
||||||
{
|
|
||||||
$aCapa['fileinto'] = true;
|
|
||||||
$aResult[] = $sTab.'fileinto "INBOX";';
|
|
||||||
}
|
|
||||||
|
|
||||||
$aResult[] = $sTab.'redirect "'.$this->quote($sValue).'";';
|
|
||||||
if ($oFilter->Stop())
|
|
||||||
{
|
|
||||||
$aResult[] = $sTab.'stop;';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
$aResult[] = $sTab.'# @Error (redirect): empty action value';
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case Enumerations\ActionType::MOVE_TO:
|
|
||||||
$sValue = $oFilter->ActionValue();
|
|
||||||
if (0 < \strlen($sValue))
|
|
||||||
{
|
|
||||||
$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);
|
|
||||||
}
|
|
||||||
|
|
||||||
$aCapa['fileinto'] = true;
|
|
||||||
$aResult[] = $sTab.'fileinto "'.$this->quote($sFolderName).'";';
|
|
||||||
if ($oFilter->Stop())
|
|
||||||
{
|
|
||||||
$aResult[] = $sTab.'stop;';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
$aResult[] = $sTab.'# @Error (fileinto): empty action value';
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$bAll)
|
|
||||||
{
|
|
||||||
$aResult[] = '}';
|
|
||||||
}
|
|
||||||
|
|
||||||
return \implode($sNL, $aResult);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function collectionToFileString(array $aFilters) : string
|
|
||||||
{
|
|
||||||
$sNL = static::NEW_LINE;
|
|
||||||
|
|
||||||
$aCapa = array();
|
|
||||||
$aParts = array();
|
|
||||||
|
|
||||||
$aParts[] = '# This is SnappyMail sieve script.';
|
|
||||||
$aParts[] = '# Please don\'t change anything here.';
|
|
||||||
$aParts[] = '# RAINLOOP:SIEVE';
|
|
||||||
$aParts[] = '';
|
|
||||||
|
|
||||||
foreach ($aFilters as /* @var $oItem \RainLoop\Providers\Filters\Classes\Filter */ $oItem)
|
|
||||||
{
|
|
||||||
$aData = array();
|
|
||||||
$aData[] = '/*';
|
|
||||||
$aData[] = 'BEGIN:FILTER:'.$oItem->ID();
|
|
||||||
$aData[] = 'BEGIN:HEADER';
|
|
||||||
$aData[] = \chunk_split(\base64_encode(\json_encode($oItem)), 74, $sNL).'END:HEADER';
|
|
||||||
$aData[] = '*/';
|
|
||||||
$aData[] = $oItem->Enabled() ? '' : '/* @Filter is disabled ';
|
|
||||||
$aData[] = $this->filterToSieveScript($oItem, $aCapa);
|
|
||||||
$aData[] = $oItem->Enabled() ? '' : '*/';
|
|
||||||
$aData[] = '/* END:FILTER */';
|
|
||||||
$aData[] = '';
|
|
||||||
|
|
||||||
$aParts[] = \implode($sNL, $aData);
|
|
||||||
}
|
|
||||||
|
|
||||||
$aCapa = \array_keys($aCapa);
|
|
||||||
$sCapa = 0 < \count($aCapa) ? $sNL.'require '.
|
|
||||||
\str_replace('","', '", "', \json_encode($aCapa)).';'.$sNL : '';
|
|
||||||
|
|
||||||
return $sCapa.$sNL.\implode($sNL, $aParts).$sNL;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function fileStringToCollection(string $sFileString) : array
|
|
||||||
{
|
|
||||||
$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+/s', '', $sEncodedLine));
|
|
||||||
if (!empty($sDecodedLine))
|
|
||||||
{
|
|
||||||
$oItem = new Classes\Filter();
|
|
||||||
if ($oItem && $oItem->unserializeFromJson($sDecodedLine))
|
|
||||||
{
|
|
||||||
$aResult[] = $oItem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $aResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function quote(string $sValue) : string
|
|
||||||
{
|
|
||||||
return \str_replace(array('\\', '"'), array('\\\\', '\\"'), \trim($sValue));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function SetLogger(?\MailSo\Log\Logger $oLogger)
|
public function SetLogger(?\MailSo\Log\Logger $oLogger)
|
||||||
{
|
{
|
||||||
$this->oLogger = $oLogger;
|
$this->oLogger = $oLogger;
|
||||||
|
|
Loading…
Add table
Reference in a new issue