385 lines
16 KiB
PHP
385 lines
16 KiB
PHP
<?php
|
|
/**
|
|
* Copyright 2003-2017 Horde LLC (http://www.horde.org/)
|
|
*
|
|
* See the enclosed file LICENSE for license information (ASL). If you
|
|
* did not receive this file, see http://www.horde.org/licenses/apache.
|
|
*
|
|
* @author Michael Slusarz <slusarz@horde.org>
|
|
* @author Jan Schneider <jan@horde.org>
|
|
* @category Horde
|
|
* @license http://www.horde.org/licenses/apache ASL
|
|
* @package Ingo
|
|
*/
|
|
|
|
/**
|
|
* The Ingo_Script_Imap class represents an IMAP client-side script generator.
|
|
*
|
|
* @author Michael Slusarz <slusarz@horde.org>
|
|
* @author Jan Schneider <jan@horde.org>
|
|
* @category Horde
|
|
* @license http://www.horde.org/licenses/apache ASL
|
|
* @package Ingo
|
|
*/
|
|
class Ingo_Script_Imap extends Ingo_Script_Base
|
|
{
|
|
/**
|
|
* A list of driver features.
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $_features = array(
|
|
/* Can tests be case sensitive? */
|
|
'case_sensitive' => false,
|
|
/* Does the driver support setting IMAP flags? */
|
|
'imap_flags' => true,
|
|
/* Does the driver support the stop-script option? */
|
|
'stop_script' => true,
|
|
/* Can this driver perform on demand filtering? */
|
|
'on_demand' => true,
|
|
/* Does the driver require a script file to be generated? */
|
|
'script_file' => false,
|
|
);
|
|
|
|
/**
|
|
* The list of actions allowed (implemented) for this driver.
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $_actions = array(
|
|
Ingo_Storage::ACTION_KEEP,
|
|
Ingo_Storage::ACTION_MOVE,
|
|
Ingo_Storage::ACTION_DISCARD,
|
|
Ingo_Storage::ACTION_MOVEKEEP
|
|
);
|
|
|
|
/**
|
|
* The categories of filtering allowed.
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $_categories = array(
|
|
Ingo_Storage::ACTION_BLACKLIST,
|
|
Ingo_Storage::ACTION_WHITELIST
|
|
);
|
|
|
|
/**
|
|
* The list of tests allowed (implemented) for this driver.
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $_tests = array(
|
|
'contains', 'not contain'
|
|
);
|
|
|
|
/**
|
|
* The types of tests allowed (implemented) for this driver.
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $_types = array(
|
|
Ingo_Storage::TYPE_HEADER,
|
|
Ingo_Storage::TYPE_SIZE,
|
|
Ingo_Storage::TYPE_BODY
|
|
);
|
|
|
|
/**
|
|
* Performs the filtering specified in the rules.
|
|
*
|
|
* @param integer $change The timestamp of the latest rule change during
|
|
* the current session.
|
|
*/
|
|
protected function _perform($change)
|
|
{
|
|
$api = $this->_params['api'];
|
|
$notification = $this->_params['notification'];
|
|
|
|
/* Indices that will be ignored by subsequent rules. */
|
|
$ignore_ids = array();
|
|
|
|
/* Only do filtering if:
|
|
1. We have not done filtering before -or-
|
|
2. The mailbox has changed -or-
|
|
3. The rules have changed. */
|
|
$cache = $api->getCache();
|
|
if ($cache !== false && $cache == $change) {
|
|
return;
|
|
}
|
|
|
|
/* Grab the rules list. */
|
|
$filters = $this->_params['storage']
|
|
->retrieve(Ingo_Storage::ACTION_FILTERS);
|
|
|
|
/* Parse through the rules, one-by-one. */
|
|
foreach ($filters->getFilterList($this->_params['skip']) as $rule) {
|
|
/* Check to make sure this is a valid rule and that the rule is
|
|
not disabled. */
|
|
if (!$this->_validRule($rule['action']) ||
|
|
!empty($rule['disable'])) {
|
|
continue;
|
|
}
|
|
|
|
switch ($rule['action']) {
|
|
case Ingo_Storage::ACTION_BLACKLIST:
|
|
case Ingo_Storage::ACTION_WHITELIST:
|
|
$bl_folder = null;
|
|
|
|
if ($rule['action'] == Ingo_Storage::ACTION_BLACKLIST) {
|
|
$blacklist = $this->_params['storage']->retrieve(Ingo_Storage::ACTION_BLACKLIST);
|
|
$addr = $blacklist->getBlacklist();
|
|
$bl_folder = $blacklist->getBlacklistFolder();
|
|
} else {
|
|
$whitelist = $this->_params['storage']->retrieve(Ingo_Storage::ACTION_WHITELIST);
|
|
$addr = $whitelist->getWhitelist();
|
|
}
|
|
|
|
/* If list is empty, move on. */
|
|
if (empty($addr)) {
|
|
continue;
|
|
}
|
|
|
|
$addr = new Horde_Mail_Rfc822_List($addr);
|
|
|
|
$query = $this->_getQuery();
|
|
$or_ob = new Horde_Imap_Client_Search_Query();
|
|
|
|
foreach ($addr->bare_addresses as $val) {
|
|
$ob = new Horde_Imap_Client_Search_Query();
|
|
$ob->charset('UTF-8', false);
|
|
$ob->headerText('from', $val);
|
|
$or_ob->orSearch(array($ob));
|
|
}
|
|
$query->andSearch(array($or_ob));
|
|
$indices = $api->search($query);
|
|
|
|
if (!$msgs = $api->fetchEnvelope($indices)) {
|
|
continue;
|
|
}
|
|
|
|
/* Remove any indices that got in there by way of partial
|
|
* address match. */
|
|
$remove = array();
|
|
foreach ($msgs as $v) {
|
|
foreach ($v->getEnvelope()->from as $v2) {
|
|
if (!$addr->contains($v2)) {
|
|
$remove[] = $v->getUid();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($remove) {
|
|
$indices = array_diff($indices, $remove);
|
|
}
|
|
|
|
if ($rule['action'] == Ingo_Storage::ACTION_BLACKLIST) {
|
|
$indices = array_diff($indices, $ignore_ids);
|
|
if (!empty($indices)) {
|
|
if (!empty($bl_folder)) {
|
|
$api->moveMessages($indices, $bl_folder);
|
|
} else {
|
|
$api->deleteMessages($indices);
|
|
}
|
|
$notification->push(sprintf(_("Filter activity: %s message(s) that matched the blacklist were deleted."), count($indices)), 'horde.message');
|
|
}
|
|
} else {
|
|
$ignore_ids = $indices;
|
|
}
|
|
break;
|
|
|
|
case Ingo_Storage::ACTION_KEEP:
|
|
case Ingo_Storage::ACTION_MOVE:
|
|
case Ingo_Storage::ACTION_DISCARD:
|
|
$base_query = $this->_getQuery();
|
|
$query = new Horde_Imap_Client_Search_Query();
|
|
|
|
foreach ($rule['conditions'] as $val) {
|
|
$ob = new Horde_Imap_Client_Search_Query();
|
|
|
|
if (!empty($val['type']) &&
|
|
($val['type'] == Ingo_Storage::TYPE_SIZE)) {
|
|
$ob->size($val['value'], ($val['match'] == 'greater than'));
|
|
} elseif (!empty($val['type']) &&
|
|
($val['type'] == Ingo_Storage::TYPE_BODY)) {
|
|
$ob->charset('UTF-8', false);
|
|
$ob->text($val['value'], true, ($val['match'] == 'not contain'));
|
|
} else {
|
|
if (strpos($val['field'], ',') == false) {
|
|
$ob->charset('UTF-8', false);
|
|
$ob->headerText($val['field'], $val['value'], $val['match'] == 'not contain');
|
|
} else {
|
|
foreach (explode(',', $val['field']) as $header) {
|
|
$hdr_ob = new Horde_Imap_Client_Search_Query();
|
|
$hdr_ob->charset('UTF-8', false);
|
|
$hdr_ob->headerText($header, $val['value'], $val['match'] == 'not contain');
|
|
if ($val['match'] == 'contains') {
|
|
$ob->orSearch(array($hdr_ob));
|
|
} elseif ($val['match'] == 'not contain') {
|
|
$ob->andSearch(array($hdr_ob));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($rule['combine'] == Ingo_Storage::COMBINE_ALL) {
|
|
$query->andSearch(array($ob));
|
|
} else {
|
|
$query->orSearch(array($ob));
|
|
}
|
|
}
|
|
|
|
$base_query->andSearch(array($query));
|
|
$indices = $api->search($base_query);
|
|
|
|
if (($indices = array_diff($indices, $ignore_ids))) {
|
|
if ($rule['stop']) {
|
|
/* If the stop action is set, add these
|
|
* indices to the list of ids that will be
|
|
* ignored by subsequent rules. */
|
|
$ignore_ids = array_unique($indices + $ignore_ids);
|
|
}
|
|
|
|
/* Set the flags. */
|
|
if (!empty($rule['flags']) &&
|
|
($rule['action'] != Ingo_Storage::ACTION_DISCARD)) {
|
|
$flags = array();
|
|
if ($rule['flags'] & Ingo_Storage::FLAG_ANSWERED) {
|
|
$flags[] = '\\answered';
|
|
}
|
|
if ($rule['flags'] & Ingo_Storage::FLAG_DELETED) {
|
|
$flags[] = '\\deleted';
|
|
}
|
|
if ($rule['flags'] & Ingo_Storage::FLAG_FLAGGED) {
|
|
$flags[] = '\\flagged';
|
|
}
|
|
if ($rule['flags'] & Ingo_Storage::FLAG_SEEN) {
|
|
$flags[] = '\\seen';
|
|
}
|
|
$api->setMessageFlags($indices, $flags);
|
|
}
|
|
|
|
if ($rule['action'] == Ingo_Storage::ACTION_KEEP) {
|
|
/* Add these indices to the ignore list. */
|
|
$ignore_ids = array_unique($indices + $ignore_ids);
|
|
} elseif ($rule['action'] == Ingo_Storage::ACTION_MOVE) {
|
|
/* We need to grab the envelope first. */
|
|
if ($this->_params['show_filter_msg'] &&
|
|
!($fetch = $api->fetchEnvelope($indices))) {
|
|
continue;
|
|
}
|
|
|
|
$mbox = new Horde_Imap_Client_Mailbox($rule['action-value']);
|
|
|
|
/* Move the messages to the requested mailbox. */
|
|
$api->moveMessages($indices, strval($mbox));
|
|
|
|
/* Display notification message(s). */
|
|
if ($this->_params['show_filter_msg']) {
|
|
foreach ($fetch as $msg) {
|
|
$envelope = $msg->getEnvelope();
|
|
$notification->push(
|
|
sprintf(_("Filter activity: The message \"%s\" from \"%s\" has been moved to the folder \"%s\"."),
|
|
!empty($envelope->subject) ? Horde_Mime::decode($envelope->subject) : _("[No Subject]"),
|
|
!empty($envelope->from) ? strval($envelope->from) : _("[No Sender]"),
|
|
$mbox),
|
|
'horde.message');
|
|
}
|
|
} else {
|
|
$notification->push(sprintf(_("Filter activity: %s message(s) have been moved to the folder \"%s\"."),
|
|
count($indices),
|
|
$mbox), 'horde.message');
|
|
}
|
|
} elseif ($rule['action'] == Ingo_Storage::ACTION_DISCARD) {
|
|
/* We need to grab the envelope first. */
|
|
if ($this->_params['show_filter_msg'] &&
|
|
!($fetch = $api->fetchEnvelope($indices))) {
|
|
continue;
|
|
}
|
|
|
|
/* Delete the messages now. */
|
|
$api->deleteMessages($indices);
|
|
|
|
/* Display notification message(s). */
|
|
if ($this->_params['show_filter_msg']) {
|
|
foreach ($fetch as $msg) {
|
|
$envelope = $msg->getEnvelope();
|
|
$notification->push(
|
|
sprintf(_("Filter activity: The message \"%s\" from \"%s\" has been deleted."),
|
|
!empty($envelope->subject) ? Horde_Mime::decode($envelope->subject) : _("[No Subject]"),
|
|
!empty($envelope->from) ? strval($envelope->from) : _("[No Sender]")),
|
|
'horde.message');
|
|
}
|
|
} else {
|
|
$notification->push(sprintf(_("Filter activity: %s message(s) have been deleted."), count($indices)), 'horde.message');
|
|
}
|
|
} elseif ($rule['action'] == Ingo_Storage::ACTION_MOVEKEEP) {
|
|
$mbox = new Horde_Imap_Client_Mailbox($rule['action-value']);
|
|
|
|
/* Copy the messages to the requested mailbox. */
|
|
$api->copyMessages($indices, strval($mbox));
|
|
|
|
/* Display notification message(s). */
|
|
if ($this->_params['show_filter_msg']) {
|
|
if (!($fetch = $api->fetchEnvelope($indices))) {
|
|
continue;
|
|
}
|
|
foreach ($fetch as $msg) {
|
|
$envelope = $msg->getEnvelope();
|
|
$notification->push(
|
|
sprintf(_("Filter activity: The message \"%s\" from \"%s\" has been copied to the folder \"%s\"."),
|
|
!empty($envelope->subject) ? Horde_Mime::decode($envelope->subject) : _("[No Subject]"),
|
|
!empty($envelope->from) ? strval($envelope->from) : _("[No Sender]"),
|
|
$mbox),
|
|
'horde.message');
|
|
}
|
|
} else {
|
|
$notification->push(sprintf(_("Filter activity: %s message(s) have been copied to the folder \"%s\"."), count($indices), $mbox), 'horde.message');
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Set cache flag. */
|
|
$api->storeCache($change);
|
|
}
|
|
|
|
/**
|
|
* Is the perform() function available?
|
|
*
|
|
* @return boolean True if perform() is available, false if not.
|
|
*/
|
|
public function canPerform()
|
|
{
|
|
if ($this->_params['registry']->hasMethod('mail/server')) {
|
|
try {
|
|
$server = $this->_params['registry']->call('mail/server');
|
|
return ($server['protocol'] == 'imap');
|
|
} catch (Horde_Exception $e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Returns a query object prepared for adding further criteria.
|
|
*
|
|
* @return Ingo_IMAP_Search_Query A query object.
|
|
*/
|
|
protected function _getQuery()
|
|
{
|
|
$ob = new Horde_Imap_Client_Search_Query();
|
|
$ob->flag('\\deleted', false);
|
|
if ($this->_params['filter_seen'] == Ingo::FILTER_SEEN ||
|
|
$this->_params['filter_seen'] == Ingo::FILTER_UNSEEN) {
|
|
$ob->flag('\\seen', $this->_params['filter_seen'] == Ingo::FILTER_SEEN);
|
|
}
|
|
|
|
return $ob;
|
|
}
|
|
|
|
}
|