From: Michael M Slusarz Date: Fri, 17 Jul 2009 19:19:44 +0000 (-0600) Subject: Horde 4/autoloading conventions X-Git-Url: https://git.internetallee.de/?a=commitdiff_plain;h=9b22223f642b6ab65a71694ff34f709f06dfce9d;p=horde.git Horde 4/autoloading conventions --- diff --git a/ingo/lib/Driver.php b/ingo/lib/Driver.php index a972abb82..b85d5e2d5 100644 --- a/ingo/lib/Driver.php +++ b/ingo/lib/Driver.php @@ -41,7 +41,7 @@ class Ingo_Driver static public function factory($driver, $params = array()) { $driver = basename($driver); - $class = 'Ingo_Driver_' . $driver; + $class = 'Ingo_Driver_' . ucfirst($driver); return class_exists($class) ? new $class($params) diff --git a/ingo/lib/Driver/Ldap.php b/ingo/lib/Driver/Ldap.php new file mode 100644 index 000000000..d6a5ddf31 --- /dev/null +++ b/ingo/lib/Driver/Ldap.php @@ -0,0 +1,263 @@ + + * @package Ingo + */ +class Ingo_Driver_Ldap extends Ingo_Driver +{ + /** + * Constructor. + */ + public function __construct($params = array()) + { + if (!Horde_Util::extensionExists('ldap')) { + Horde::fatal(PEAR::raiseError(_("LDAP support is required but the LDAP module is not available or not loaded.")), __FILE__, __LINE__); + } + + $default_params = array( + 'hostspec' => 'localhost', + 'port' => 389, + 'script_attribute' => 'mailSieveRuleSource' + ); + + parent::__construct(array_merge($default_params, $params)); + } + + /** + * Create a DN from a DN template. + * This is done by substituting the username for %u and the 'dc=' + * components for %d. + * + * @param string $templ The DN template (from the config). + * + * @return string The resulting DN. + */ + protected function _substUser($templ) + { + $domain = ''; + $username = $this->_params['username']; + + if (strpos($username, '@') !== false) { + list($username, $domain) = explode('@', $username); + } + $domain = implode(', dc=', explode('.', $domain)); + if (!empty($domain)) { + $domain = 'dc=' . $domain; + } + + if (preg_match('/^\s|\s$|\s\s|[,+="\r\n<>#;]/', $username)) { + $username = '"' . str_replace('"', '\\"', $username) . '"'; + } + + return str_replace(array('%u', '%d'), + array($username, $domain), + $templ); + } + + /** + * Connect and bind to ldap server. + */ + protected function _connect() + { + if (!($ldapcn = @ldap_connect($this->_params['hostspec'], + $this->_params['port']))) { + return PEAR::raiseError(_("Connection failure")); + } + + /* Set the LDAP protocol version. */ + if (!empty($this->_params['version'])) { + @ldap_set_option($ldapcn, + LDAP_OPT_PROTOCOL_VERSION, + $this->_params['version']); + } + + /* Start TLS if we're using it. */ + if (!empty($this->_params['tls'])) { + if (!@ldap_start_tls($ldapcn)) { + return PEAR::raiseError(sprintf(_("STARTTLS failed: (%s) %s"), + ldap_errno($ldapcn), + ldap_error($ldapcn))); + } + } + + /* Bind to the server. */ + if (isset($this->_params['bind_dn'])) { + $bind_dn = $this->_substUser($this->_params['bind_dn']); + if (is_a($bind_dn, 'PEAR_Error')) { + return $bind_dn; + } + + if (isset($this->_params['bind_password'])) { + $password = $this->_params['bind_password']; + } else { + $password = $this->_params['password']; + } + + if (!@ldap_bind($ldapcn, $bind_dn, $password)) { + return PEAR::raiseError(sprintf(_("Bind failed: (%s) %s"), + ldap_errno($ldapcn), + ldap_error($ldapcn))); + } + } elseif (!(@ldap_bind($ldapcn))) { + return PEAR::raiseError(sprintf(_("Bind failed: (%s) %s"), + ldap_errno($ldapcn), + ldap_error($ldapcn))); + } + + return $ldapcn; + } + + /** + * Retrieve current user's scripts. + * + * @param resource $ldapcn The connection to the LDAP server. + * @param string $userDN Set to the user object's real DN. + * + * @return mixed Array of script sources, or PEAR_Error on failure. + */ + protected function _getScripts($ldapcn, &$userDN) + { + $attrs = array($this->_params['script_attribute'], 'dn'); + $filter = $this->_substUser($this->_params['script_filter']); + + /* Find the user object. */ + $sr = @ldap_search($ldapcn, $this->_params['script_base'], $filter, + $attrs); + if ($sr === false) { + return PEAR::raiseError(sprintf(_("Error retrieving current script: (%d) %s"), + ldap_errno($ldapcn), + ldap_error($ldapcn))); + } + if (@ldap_count_entries($ldapcn, $sr) != 1) { + return PEAR::raiseError(sprintf(_("Expected 1 object, got %d."), + ldap_count_entries($ldapcn, $sr))); + } + $ent = @ldap_first_entry($ldapcn, $sr); + if ($ent === false) { + return PEAR::raiseError(sprintf(_("Error retrieving current script: (%d) %s"), + ldap_errno($ldapcn), + ldap_error($ldapcn))); + } + + /* Retrieve the user's DN. */ + $v = @ldap_get_dn($ldapcn, $ent); + if ($v === false) { + @ldap_free_result($sr); + return PEAR::raiseError(sprintf(_("Error retrieving current script: (%d) %s"), + ldap_errno($ldapcn), + ldap_error($ldapcn))); + } + $userDN = $v; + + /* Retrieve the user's scripts. */ + $attrs = @ldap_get_attributes($ldapcn, $ent); + @ldap_free_result($sr); + if ($attrs === false) { + return PEAR::raiseError(sprintf(_("Error retrieving current script: (%d) %s"), + ldap_errno($ldapcn), + ldap_error($ldapcn))); + } + + /* Attribute can be in any case, and can have a ";binary" + * specifier. */ + $regexp = '/^' . preg_quote($this->_params['script_attribute'], '/') . + '(?:;.*)?$/i'; + unset($attrs['count']); + foreach ($attrs as $name => $values) { + if (preg_match($regexp, $name)) { + unset($values['count']); + return array_values($values); + } + } + + return array(); + } + + /** + * Sets a script running on the backend. + * + * @param string $script The sieve script. + * + * @return mixed True on success, PEAR_Error on error. + */ + protected function setScriptActive($script) + { + $ldapcn = $this->_connect(); + if (is_a($ldapcn, 'PEAR_Error')) { + return $ldapcn; + } + + $values = $this->_getScripts($ldapcn, $userDN); + if (is_a($values, 'PEAR_Error')) { + return $values; + } + + $found = false; + foreach ($values as $i => $value) { + if (strpos($value, "# Sieve Filter\n") !== false) { + if (empty($script)) { + unset($values[$i]); + } else { + $values[$i] = $script; + } + $found = true; + break; + } + } + if (!$found && !empty($script)) { + $values[] = $script; + } + + $replace = array(Horde_String::lower($this->_params['script_attribute']) => $values); + if (empty($values)) { + $r = @ldap_mod_del($ldapcn, $userDN, $replace); + } else { + $r = @ldap_mod_replace($ldapcn, $userDN, $replace); + } + if (!$r) { + return PEAR::raiseError(sprintf(_("Activating the script for \"%s\" failed: (%d) %s"), + $userDN, + ldap_errno($ldapcn), + ldap_error($ldapcn))); + } + + @ldap_close($ldapcn); + return true; + } + + /** + * Returns the content of the currently active script. + * + * @return string The complete ruleset of the specified user. + */ + public function getScript() + { + $ldapcn = $this->_connect(); + if (is_a($ldapcn, 'PEAR_Error')) { + return $ldapcn; + } + + $values = $this->_getScripts($ldapcn, $userDN); + if (is_a($values, 'PEAR_Error')) { + return $values; + } + + $script = ''; + foreach ($values as $value) { + if (strpos($value, "# Sieve Filter\n") !== false) { + $script = $value; + break; + } + } + + @ldap_close($ldapcn); + return $script; + } + +} diff --git a/ingo/lib/Driver/Null.php b/ingo/lib/Driver/Null.php new file mode 100644 index 000000000..231b6d8d0 --- /dev/null +++ b/ingo/lib/Driver/Null.php @@ -0,0 +1,24 @@ + + * @package Ingo + */ + +class Ingo_Driver_Null extends Ingo_Driver +{ + /** + * Constructor. + */ + public function __construct($params = array()) + { + $this->_support_shares = true; + parent::__construct($params); + } + +} diff --git a/ingo/lib/Driver/Sivtest.php b/ingo/lib/Driver/Sivtest.php new file mode 100644 index 000000000..6f3caeddd --- /dev/null +++ b/ingo/lib/Driver/Sivtest.php @@ -0,0 +1,210 @@ + + * + * See the enclosed file LICENSE for license information (ASL). If you + * did not receive this file, see http://www.horde.org/licenses/asl.php. + * + * @author Mike Cochrane + * @author Jan Schneider + * @author Liam Hoekenga + * @package Ingo + */ +class Ingo_Driver_Sivtest extends Ingo_Driver +{ + /** + * The Net_Sieve object. + * + * @var Net_Sieve + */ + protected $_sieve; + + /** + * Constructor. + */ + public function __construct($params = array()) + { + $default_params = array( + 'hostspec' => 'localhost', + 'logintype' => '', + 'port' => 2000, + 'scriptname' => 'ingo', + 'admin' => '', + 'usetls' => true, + 'command' => '', + 'socket' => '', + ); + + parent::__construct(array_merge($default_params, $params)); + } + + /** + * Connect to the sieve server. + * + * @return mixed True on success, PEAR_Error on false. + */ + protected function _connect() + { + if (!empty($this->_sieve)) { + return true; + } + + $this->sivtestSocket($this->_params['username'], + $this->_params['password'], $this->_params['hostspec']); + if (substr(PHP_VERSION, 0, 1) == '5') { + $domain_socket = 'unix://' . $this->_params['socket']; + } else { + $domain_socket = $this->_params['socket']; + } + + $this->_sieve = new Net_Sieve($this->_params['username'], + $this->_params['password'], + $domain_socket, + 0, + null, + null, + false, + true, + $this->_params['usetls']); + + $res = $this->_sieve->getError(); + if (is_a($res, 'PEAR_Error')) { + unset($this->_sieve); + return $res; + } else { + return true; + } + } + + /** + * Sets a script running on the backend. + * + * @param string $script The sieve script. + * + * @return mixed True on success. + * Returns PEAR_Error on error. + */ + public function setScriptActive($script) + { + $res = $this->_connect(); + if (is_a($res, 'PEAR_Error')) { + return $res; + } + + $res = $this->_sieve->haveSpace($this->_params['scriptname'], strlen($script)); + if (is_a($res, 'PEAR_ERROR')) { + return $res; + } + + return $this->_sieve->installScript($this->_params['scriptname'], $script, true); + } + + /** + * Returns the content of the currently active script. + * + * @return string The complete ruleset of the specified user. + */ + public function getScript() + { + $res = $this->_connect(); + if (is_a($res, 'PEAR_Error')) { + return $res; + } + return $this->_sieve->getScript($this->_sieve->getActive()); + } + + /** + * Used to figure out which Sieve server the script will be run + * on, and then open a GSSAPI authenticated socket to said server. + * + * @param string $username The username. + * @param string $password The password. + * @param string $hostspec The hostspec. + * + * @return TODO + */ + public function sivtestSocket($username, $password, $hostspec) + { + $command = ''; + $error_return = ''; + + if (strtolower($this->_params['logintype']) == 'gssapi' + && isset($_SERVER['KRB5CCNAME'])) { + $command .= 'KRB5CCNAME=' . $_SERVER['KRB5CCNAME']; + } + + if (substr(PHP_VERSION, 0, 1) == '5') { + $domain_socket = 'unix://' . $this->_params['socket']; + } else { + $domain_socket = $this->_params['socket']; + } + + $command .= ' ' . $this->_params['command'] + . ' -m ' . $this->_params['logintype'] + . ' -u ' . $username + . ' -a ' . $username + . ' -w ' . $password + . ' -p ' . $this->_params['port'] + . ' -X ' . $this->_params['socket'] + . ' ' . $hostspec; + + $conn_attempts = 0; + while ($conn_attempts++ < 4) { + $attempts = 0; + if (!file_exists($this->_params['socket'])) { + exec($command . ' > /dev/null 2>&1'); + sleep(1); + while (!file_exists($this->_params['socket'])) { + usleep(200000); + if ($attempts++ > 5) { + $error_return = ': No socket after 10 seconds of trying!'; + continue 2; + } + } + } + $socket = new Net_Socket(); + $error = $socket->connect($domain_socket, 0, true, 30); + if (!is_a($error, 'PEAR_Error')) { + break; + } + + // We failed, break this connection. + unlink($this->_params['socket']); + } + + if (!empty($error_return)) { + return PEAR::raiseError(_($error_return)); + } + + $status = $socket->getStatus(); + if (is_a($status, 'PEAR_Error') || $status['eof']) { + return PEAR::raiseError(_('Failed to write to socket: (connection lost!)')); + } + + $error = $socket->writeLine("CAPABILITY"); + if (is_a($error, 'PEAR_Error')) { + return PEAR::raiseError(_('Failed to write to socket: ' . $error->getMessage())); + } + + $result = $socket->readLine(); + if (is_a($result, 'PEAR_Error')) { + return PEAR::raiseError(_('Failed to read from socket: ' . $error->getMessage())); + } + + if (preg_match('|^bye \(referral "(sieve://)?([^"]+)|i', + $result, $matches)) { + $socket->disconnect(); + + $this->sivtestSocket($username, $password, $matches[2]); + } else { + $socket->disconnect(); + exec($command . ' > /dev/null 2>&1'); + sleep(1); + } + } + +} diff --git a/ingo/lib/Driver/Timsieved.php b/ingo/lib/Driver/Timsieved.php new file mode 100644 index 000000000..d600b8d3b --- /dev/null +++ b/ingo/lib/Driver/Timsieved.php @@ -0,0 +1,124 @@ + + * @author Jan Schneider + * @package Ingo + */ +class Ingo_Driver_Timsieved extends Ingo_Driver +{ + /** + * The Net_Sieve object. + * + * @var Net_Sieve + */ + protected $_sieve; + + /** + * Constructor. + */ + public function __construct($params = array()) + { + $this->_support_shares = true; + + $default_params = array( + 'hostspec' => 'localhost', + 'logintype' => 'PLAIN', + 'port' => 2000, + 'scriptname' => 'ingo', + 'admin' => '', + 'usetls' => true + ); + + parent::__construct(array_merge($default_params, $params)); + } + + /** + * Connect to the sieve server. + * + * @return mixed True on success, PEAR_Error on false. + */ + public function _connect() + { + if (!empty($this->_sieve)) { + return true; + } + + if (empty($this->_params['admin'])) { + $auth = $this->_params['username']; + } else { + $auth = $this->_params['admin']; + } + $this->_sieve = new Net_Sieve($auth, + $this->_params['password'], + $this->_params['hostspec'], + $this->_params['port'], + $this->_params['logintype'], + Ingo::getUser(false), + false, + false, + $this->_params['usetls']); + + $res = $this->_sieve->getError(); + if (is_a($res, 'PEAR_Error')) { + unset($this->_sieve); + return $res; + } else { + return true; + } + } + + /** + * Sets a script running on the backend. + * + * @param string $script The sieve script. + * + * @return mixed True on success, PEAR_Error on error. + */ + public function setScriptActive($script) + { + $res = $this->_connect(); + if (is_a($res, 'PEAR_Error')) { + return $res; + } + + if (!strlen($script)) { + return $this->_sieve->setActive(''); + } + + $res = $this->_sieve->haveSpace($this->_params['scriptname'], + strlen($script)); + if (is_a($res, 'PEAR_ERROR')) { + return $res; + } + + return $this->_sieve->installScript($this->_params['scriptname'], + $script, true); + } + + /** + * Returns the content of the currently active script. + * + * @return string The complete ruleset of the specified user. + */ + public function getScript() + { + $res = $this->_connect(); + if (is_a($res, 'PEAR_Error')) { + return $res; + } + $active = $this->_sieve->getActive(); + if (empty($active)) { + return ''; + } + return $this->_sieve->getScript($active); + } + +} diff --git a/ingo/lib/Driver/Vfs.php b/ingo/lib/Driver/Vfs.php new file mode 100644 index 000000000..5e33423f3 --- /dev/null +++ b/ingo/lib/Driver/Vfs.php @@ -0,0 +1,143 @@ + + * @author Jan Schneider + * @package Ingo + */ +class Ingo_Driver_Vfs extends Ingo_Driver +{ + /** + * Constructs a new VFS-based storage driver. + * + * @param array $params A hash containing driver parameters. + */ + public function __construct($params = array()) + { + $this->_support_shares = true; + + $default_params = array( + 'hostspec' => 'localhost', + 'port' => 21, + 'filename' => '.ingo_filter', + 'vfstype' => 'ftp', + 'vfs_path' => '', + 'vfs_forward_path' => '', + ); + + parent::__construct(array_merge($default_params, $params)); + } + + /** + * Sets a script running on the backend. + * + * @param string $script The filter script + * + * @return mixed True on success, or PEAR_Error on failure. + */ + public function setScriptActive($script) + { + $result = $this->_connect(); + if (is_a($result, 'PEAR_Error')) { + return $result; + } + + if (empty($script)) { + $result = $this->_vfs->deleteFile($this->_params['vfs_path'], $this->_params['filename']); + } else { + $result = $this->_vfs->writeData($this->_params['vfs_path'], $this->_params['filename'], $script, true); + } + if (is_a($result, 'PEAR_Error')) { + return $result; + } + + if (isset($this->_params['file_perms']) && !empty($script)) { + $result = $this->_vfs->changePermissions($this->_params['vfs_path'], $this->_params['filename'], $this->_params['file_perms']); + if (is_a($result, 'PEAR_Error')) { + return $result; + } + } + + // Get the backend; necessary if a .forward is needed for + // procmail. + $backend = Ingo::getBackend(); + if ($backend['script'] == 'procmail' && isset($backend['params']['forward_file']) && isset($backend['params']['forward_string'])) { + if (empty($script)) { + $result = $this->_vfs->deleteFile($this->_params['vfs_forward_path'], $backend['params']['forward_file']); + } else { + $result = $this->_vfs->writeData($this->_params['vfs_forward_path'], $backend['params']['forward_file'], $backend['params']['forward_string'], true); + } + if (is_a($result, 'PEAR_Error')) { + return $result; + } + + if (isset($this->_params['file_perms']) && !empty($script)) { + $result = $this->_vfs->changePermissions($this->_params['vfs_forward_path'], $backend['params']['forward_file'], $this->_params['file_perms']); + if (is_a($result, 'PEAR_Error')) { + return $result; + } + } + } + + return true; + } + + /** + * Returns the content of the currently active script. + * + * @return string The complete ruleset of the specified user. + */ + public function getScript() + { + $result = $this->_connect(); + if (is_a($result, 'PEAR_Error')) { + return $result; + } + return $this->_vfs->read('', $this->_params['vfs_path'] . '/' . $this->_params['filename']); + } + + /** + * Connect to the VFS server. + * + * @return boolean True on success, PEAR_Error on false. + */ + protected function _connect() + { + /* Do variable substitution. */ + if (!empty($this->_params['vfs_path'])) { + $user = Ingo::getUser(); + $domain = Ingo::getDomain(); + if ($_SESSION['ingo']['backend']['hordeauth'] !== 'full') { + $pos = strpos($user, '@'); + if ($pos !== false) { + $domain = substr($user, $pos + 1); + $user = substr($user, 0, $pos); + } + } + $this->_params['vfs_path'] = str_replace( + array('%u', '%d', '%U'), + array($user, $domain, $this->_params['username']), + $this->_params['vfs_path']); + } + + if (!empty($this->_vfs)) { + return true; + } + + $this->_vfs = &VFS::singleton($this->_params['vfstype'], $this->_params); + if (is_a($this->_vfs, 'PEAR_Error')) { + $error = $this->_vfs; + $this->_vfs = null; + return $error; + } else { + return true; + } + } + +} diff --git a/ingo/lib/Driver/ldap.php b/ingo/lib/Driver/ldap.php deleted file mode 100644 index 7d1541011..000000000 --- a/ingo/lib/Driver/ldap.php +++ /dev/null @@ -1,263 +0,0 @@ - - * @package Ingo - */ -class Ingo_Driver_ldap extends Ingo_Driver -{ - /** - * Constructor. - */ - public function __construct($params = array()) - { - if (!Horde_Util::extensionExists('ldap')) { - Horde::fatal(PEAR::raiseError(_("LDAP support is required but the LDAP module is not available or not loaded.")), __FILE__, __LINE__); - } - - $default_params = array( - 'hostspec' => 'localhost', - 'port' => 389, - 'script_attribute' => 'mailSieveRuleSource' - ); - - parent::__construct(array_merge($default_params, $params)); - } - - /** - * Create a DN from a DN template. - * This is done by substituting the username for %u and the 'dc=' - * components for %d. - * - * @param string $templ The DN template (from the config). - * - * @return string The resulting DN. - */ - protected function _substUser($templ) - { - $domain = ''; - $username = $this->_params['username']; - - if (strpos($username, '@') !== false) { - list($username, $domain) = explode('@', $username); - } - $domain = implode(', dc=', explode('.', $domain)); - if (!empty($domain)) { - $domain = 'dc=' . $domain; - } - - if (preg_match('/^\s|\s$|\s\s|[,+="\r\n<>#;]/', $username)) { - $username = '"' . str_replace('"', '\\"', $username) . '"'; - } - - return str_replace(array('%u', '%d'), - array($username, $domain), - $templ); - } - - /** - * Connect and bind to ldap server. - */ - protected function _connect() - { - if (!($ldapcn = @ldap_connect($this->_params['hostspec'], - $this->_params['port']))) { - return PEAR::raiseError(_("Connection failure")); - } - - /* Set the LDAP protocol version. */ - if (!empty($this->_params['version'])) { - @ldap_set_option($ldapcn, - LDAP_OPT_PROTOCOL_VERSION, - $this->_params['version']); - } - - /* Start TLS if we're using it. */ - if (!empty($this->_params['tls'])) { - if (!@ldap_start_tls($ldapcn)) { - return PEAR::raiseError(sprintf(_("STARTTLS failed: (%s) %s"), - ldap_errno($ldapcn), - ldap_error($ldapcn))); - } - } - - /* Bind to the server. */ - if (isset($this->_params['bind_dn'])) { - $bind_dn = $this->_substUser($this->_params['bind_dn']); - if (is_a($bind_dn, 'PEAR_Error')) { - return $bind_dn; - } - - if (isset($this->_params['bind_password'])) { - $password = $this->_params['bind_password']; - } else { - $password = $this->_params['password']; - } - - if (!@ldap_bind($ldapcn, $bind_dn, $password)) { - return PEAR::raiseError(sprintf(_("Bind failed: (%s) %s"), - ldap_errno($ldapcn), - ldap_error($ldapcn))); - } - } elseif (!(@ldap_bind($ldapcn))) { - return PEAR::raiseError(sprintf(_("Bind failed: (%s) %s"), - ldap_errno($ldapcn), - ldap_error($ldapcn))); - } - - return $ldapcn; - } - - /** - * Retrieve current user's scripts. - * - * @param resource $ldapcn The connection to the LDAP server. - * @param string $userDN Set to the user object's real DN. - * - * @return mixed Array of script sources, or PEAR_Error on failure. - */ - protected function _getScripts($ldapcn, &$userDN) - { - $attrs = array($this->_params['script_attribute'], 'dn'); - $filter = $this->_substUser($this->_params['script_filter']); - - /* Find the user object. */ - $sr = @ldap_search($ldapcn, $this->_params['script_base'], $filter, - $attrs); - if ($sr === false) { - return PEAR::raiseError(sprintf(_("Error retrieving current script: (%d) %s"), - ldap_errno($ldapcn), - ldap_error($ldapcn))); - } - if (@ldap_count_entries($ldapcn, $sr) != 1) { - return PEAR::raiseError(sprintf(_("Expected 1 object, got %d."), - ldap_count_entries($ldapcn, $sr))); - } - $ent = @ldap_first_entry($ldapcn, $sr); - if ($ent === false) { - return PEAR::raiseError(sprintf(_("Error retrieving current script: (%d) %s"), - ldap_errno($ldapcn), - ldap_error($ldapcn))); - } - - /* Retrieve the user's DN. */ - $v = @ldap_get_dn($ldapcn, $ent); - if ($v === false) { - @ldap_free_result($sr); - return PEAR::raiseError(sprintf(_("Error retrieving current script: (%d) %s"), - ldap_errno($ldapcn), - ldap_error($ldapcn))); - } - $userDN = $v; - - /* Retrieve the user's scripts. */ - $attrs = @ldap_get_attributes($ldapcn, $ent); - @ldap_free_result($sr); - if ($attrs === false) { - return PEAR::raiseError(sprintf(_("Error retrieving current script: (%d) %s"), - ldap_errno($ldapcn), - ldap_error($ldapcn))); - } - - /* Attribute can be in any case, and can have a ";binary" - * specifier. */ - $regexp = '/^' . preg_quote($this->_params['script_attribute'], '/') . - '(?:;.*)?$/i'; - unset($attrs['count']); - foreach ($attrs as $name => $values) { - if (preg_match($regexp, $name)) { - unset($values['count']); - return array_values($values); - } - } - - return array(); - } - - /** - * Sets a script running on the backend. - * - * @param string $script The sieve script. - * - * @return mixed True on success, PEAR_Error on error. - */ - protected function setScriptActive($script) - { - $ldapcn = $this->_connect(); - if (is_a($ldapcn, 'PEAR_Error')) { - return $ldapcn; - } - - $values = $this->_getScripts($ldapcn, $userDN); - if (is_a($values, 'PEAR_Error')) { - return $values; - } - - $found = false; - foreach ($values as $i => $value) { - if (strpos($value, "# Sieve Filter\n") !== false) { - if (empty($script)) { - unset($values[$i]); - } else { - $values[$i] = $script; - } - $found = true; - break; - } - } - if (!$found && !empty($script)) { - $values[] = $script; - } - - $replace = array(Horde_String::lower($this->_params['script_attribute']) => $values); - if (empty($values)) { - $r = @ldap_mod_del($ldapcn, $userDN, $replace); - } else { - $r = @ldap_mod_replace($ldapcn, $userDN, $replace); - } - if (!$r) { - return PEAR::raiseError(sprintf(_("Activating the script for \"%s\" failed: (%d) %s"), - $userDN, - ldap_errno($ldapcn), - ldap_error($ldapcn))); - } - - @ldap_close($ldapcn); - return true; - } - - /** - * Returns the content of the currently active script. - * - * @return string The complete ruleset of the specified user. - */ - public function getScript() - { - $ldapcn = $this->_connect(); - if (is_a($ldapcn, 'PEAR_Error')) { - return $ldapcn; - } - - $values = $this->_getScripts($ldapcn, $userDN); - if (is_a($values, 'PEAR_Error')) { - return $values; - } - - $script = ''; - foreach ($values as $value) { - if (strpos($value, "# Sieve Filter\n") !== false) { - $script = $value; - break; - } - } - - @ldap_close($ldapcn); - return $script; - } - -} diff --git a/ingo/lib/Driver/null.php b/ingo/lib/Driver/null.php deleted file mode 100644 index 54cadee06..000000000 --- a/ingo/lib/Driver/null.php +++ /dev/null @@ -1,24 +0,0 @@ - - * @package Ingo - */ - -class Ingo_Driver_null extends Ingo_Driver -{ - /** - * Constructor. - */ - public function __construct($params = array()) - { - $this->_support_shares = true; - parent::__construct($params); - } - -} diff --git a/ingo/lib/Driver/sivtest.php b/ingo/lib/Driver/sivtest.php deleted file mode 100644 index 3fc39536a..000000000 --- a/ingo/lib/Driver/sivtest.php +++ /dev/null @@ -1,210 +0,0 @@ - - * - * See the enclosed file LICENSE for license information (ASL). If you - * did not receive this file, see http://www.horde.org/licenses/asl.php. - * - * @author Mike Cochrane - * @author Jan Schneider - * @author Liam Hoekenga - * @package Ingo - */ -class Ingo_Driver_sivtest extends Ingo_Driver -{ - /** - * The Net_Sieve object. - * - * @var Net_Sieve - */ - protected $_sieve; - - /** - * Constructor. - */ - public function __construct($params = array()) - { - $default_params = array( - 'hostspec' => 'localhost', - 'logintype' => '', - 'port' => 2000, - 'scriptname' => 'ingo', - 'admin' => '', - 'usetls' => true, - 'command' => '', - 'socket' => '', - ); - - parent::__construct(array_merge($default_params, $params)); - } - - /** - * Connect to the sieve server. - * - * @return mixed True on success, PEAR_Error on false. - */ - protected function _connect() - { - if (!empty($this->_sieve)) { - return true; - } - - $this->sivtestSocket($this->_params['username'], - $this->_params['password'], $this->_params['hostspec']); - if (substr(PHP_VERSION, 0, 1) == '5') { - $domain_socket = 'unix://' . $this->_params['socket']; - } else { - $domain_socket = $this->_params['socket']; - } - - $this->_sieve = new Net_Sieve($this->_params['username'], - $this->_params['password'], - $domain_socket, - 0, - null, - null, - false, - true, - $this->_params['usetls']); - - $res = $this->_sieve->getError(); - if (is_a($res, 'PEAR_Error')) { - unset($this->_sieve); - return $res; - } else { - return true; - } - } - - /** - * Sets a script running on the backend. - * - * @param string $script The sieve script. - * - * @return mixed True on success. - * Returns PEAR_Error on error. - */ - public function setScriptActive($script) - { - $res = $this->_connect(); - if (is_a($res, 'PEAR_Error')) { - return $res; - } - - $res = $this->_sieve->haveSpace($this->_params['scriptname'], strlen($script)); - if (is_a($res, 'PEAR_ERROR')) { - return $res; - } - - return $this->_sieve->installScript($this->_params['scriptname'], $script, true); - } - - /** - * Returns the content of the currently active script. - * - * @return string The complete ruleset of the specified user. - */ - public function getScript() - { - $res = $this->_connect(); - if (is_a($res, 'PEAR_Error')) { - return $res; - } - return $this->_sieve->getScript($this->_sieve->getActive()); - } - - /** - * Used to figure out which Sieve server the script will be run - * on, and then open a GSSAPI authenticated socket to said server. - * - * @param string $username The username. - * @param string $password The password. - * @param string $hostspec The hostspec. - * - * @return TODO - */ - public function sivtestSocket($username, $password, $hostspec) - { - $command = ''; - $error_return = ''; - - if (strtolower($this->_params['logintype']) == 'gssapi' - && isset($_SERVER['KRB5CCNAME'])) { - $command .= 'KRB5CCNAME=' . $_SERVER['KRB5CCNAME']; - } - - if (substr(PHP_VERSION, 0, 1) == '5') { - $domain_socket = 'unix://' . $this->_params['socket']; - } else { - $domain_socket = $this->_params['socket']; - } - - $command .= ' ' . $this->_params['command'] - . ' -m ' . $this->_params['logintype'] - . ' -u ' . $username - . ' -a ' . $username - . ' -w ' . $password - . ' -p ' . $this->_params['port'] - . ' -X ' . $this->_params['socket'] - . ' ' . $hostspec; - - $conn_attempts = 0; - while ($conn_attempts++ < 4) { - $attempts = 0; - if (!file_exists($this->_params['socket'])) { - exec($command . ' > /dev/null 2>&1'); - sleep(1); - while (!file_exists($this->_params['socket'])) { - usleep(200000); - if ($attempts++ > 5) { - $error_return = ': No socket after 10 seconds of trying!'; - continue 2; - } - } - } - $socket = new Net_Socket(); - $error = $socket->connect($domain_socket, 0, true, 30); - if (!is_a($error, 'PEAR_Error')) { - break; - } - - // We failed, break this connection. - unlink($this->_params['socket']); - } - - if (!empty($error_return)) { - return PEAR::raiseError(_($error_return)); - } - - $status = $socket->getStatus(); - if (is_a($status, 'PEAR_Error') || $status['eof']) { - return PEAR::raiseError(_('Failed to write to socket: (connection lost!)')); - } - - $error = $socket->writeLine("CAPABILITY"); - if (is_a($error, 'PEAR_Error')) { - return PEAR::raiseError(_('Failed to write to socket: ' . $error->getMessage())); - } - - $result = $socket->readLine(); - if (is_a($result, 'PEAR_Error')) { - return PEAR::raiseError(_('Failed to read from socket: ' . $error->getMessage())); - } - - if (preg_match('|^bye \(referral "(sieve://)?([^"]+)|i', - $result, $matches)) { - $socket->disconnect(); - - $this->sivtestSocket($username, $password, $matches[2]); - } else { - $socket->disconnect(); - exec($command . ' > /dev/null 2>&1'); - sleep(1); - } - } - -} diff --git a/ingo/lib/Driver/timsieved.php b/ingo/lib/Driver/timsieved.php deleted file mode 100644 index afc9ffa24..000000000 --- a/ingo/lib/Driver/timsieved.php +++ /dev/null @@ -1,124 +0,0 @@ - - * @author Jan Schneider - * @package Ingo - */ -class Ingo_Driver_timsieved extends Ingo_Driver -{ - /** - * The Net_Sieve object. - * - * @var Net_Sieve - */ - protected $_sieve; - - /** - * Constructor. - */ - public function __construct($params = array()) - { - $this->_support_shares = true; - - $default_params = array( - 'hostspec' => 'localhost', - 'logintype' => 'PLAIN', - 'port' => 2000, - 'scriptname' => 'ingo', - 'admin' => '', - 'usetls' => true - ); - - parent::__construct(array_merge($default_params, $params)); - } - - /** - * Connect to the sieve server. - * - * @return mixed True on success, PEAR_Error on false. - */ - public function _connect() - { - if (!empty($this->_sieve)) { - return true; - } - - if (empty($this->_params['admin'])) { - $auth = $this->_params['username']; - } else { - $auth = $this->_params['admin']; - } - $this->_sieve = new Net_Sieve($auth, - $this->_params['password'], - $this->_params['hostspec'], - $this->_params['port'], - $this->_params['logintype'], - Ingo::getUser(false), - false, - false, - $this->_params['usetls']); - - $res = $this->_sieve->getError(); - if (is_a($res, 'PEAR_Error')) { - unset($this->_sieve); - return $res; - } else { - return true; - } - } - - /** - * Sets a script running on the backend. - * - * @param string $script The sieve script. - * - * @return mixed True on success, PEAR_Error on error. - */ - public function setScriptActive($script) - { - $res = $this->_connect(); - if (is_a($res, 'PEAR_Error')) { - return $res; - } - - if (!strlen($script)) { - return $this->_sieve->setActive(''); - } - - $res = $this->_sieve->haveSpace($this->_params['scriptname'], - strlen($script)); - if (is_a($res, 'PEAR_ERROR')) { - return $res; - } - - return $this->_sieve->installScript($this->_params['scriptname'], - $script, true); - } - - /** - * Returns the content of the currently active script. - * - * @return string The complete ruleset of the specified user. - */ - public function getScript() - { - $res = $this->_connect(); - if (is_a($res, 'PEAR_Error')) { - return $res; - } - $active = $this->_sieve->getActive(); - if (empty($active)) { - return ''; - } - return $this->_sieve->getScript($active); - } - -} diff --git a/ingo/lib/Driver/vfs.php b/ingo/lib/Driver/vfs.php deleted file mode 100644 index 257a97c60..000000000 --- a/ingo/lib/Driver/vfs.php +++ /dev/null @@ -1,143 +0,0 @@ - - * @author Jan Schneider - * @package Ingo - */ -class Ingo_Driver_vfs extends Ingo_Driver -{ - /** - * Constructs a new VFS-based storage driver. - * - * @param array $params A hash containing driver parameters. - */ - public function __construct($params = array()) - { - $this->_support_shares = true; - - $default_params = array( - 'hostspec' => 'localhost', - 'port' => 21, - 'filename' => '.ingo_filter', - 'vfstype' => 'ftp', - 'vfs_path' => '', - 'vfs_forward_path' => '', - ); - - parent::__construct(array_merge($default_params, $params)); - } - - /** - * Sets a script running on the backend. - * - * @param string $script The filter script - * - * @return mixed True on success, or PEAR_Error on failure. - */ - public function setScriptActive($script) - { - $result = $this->_connect(); - if (is_a($result, 'PEAR_Error')) { - return $result; - } - - if (empty($script)) { - $result = $this->_vfs->deleteFile($this->_params['vfs_path'], $this->_params['filename']); - } else { - $result = $this->_vfs->writeData($this->_params['vfs_path'], $this->_params['filename'], $script, true); - } - if (is_a($result, 'PEAR_Error')) { - return $result; - } - - if (isset($this->_params['file_perms']) && !empty($script)) { - $result = $this->_vfs->changePermissions($this->_params['vfs_path'], $this->_params['filename'], $this->_params['file_perms']); - if (is_a($result, 'PEAR_Error')) { - return $result; - } - } - - // Get the backend; necessary if a .forward is needed for - // procmail. - $backend = Ingo::getBackend(); - if ($backend['script'] == 'procmail' && isset($backend['params']['forward_file']) && isset($backend['params']['forward_string'])) { - if (empty($script)) { - $result = $this->_vfs->deleteFile($this->_params['vfs_forward_path'], $backend['params']['forward_file']); - } else { - $result = $this->_vfs->writeData($this->_params['vfs_forward_path'], $backend['params']['forward_file'], $backend['params']['forward_string'], true); - } - if (is_a($result, 'PEAR_Error')) { - return $result; - } - - if (isset($this->_params['file_perms']) && !empty($script)) { - $result = $this->_vfs->changePermissions($this->_params['vfs_forward_path'], $backend['params']['forward_file'], $this->_params['file_perms']); - if (is_a($result, 'PEAR_Error')) { - return $result; - } - } - } - - return true; - } - - /** - * Returns the content of the currently active script. - * - * @return string The complete ruleset of the specified user. - */ - public function getScript() - { - $result = $this->_connect(); - if (is_a($result, 'PEAR_Error')) { - return $result; - } - return $this->_vfs->read('', $this->_params['vfs_path'] . '/' . $this->_params['filename']); - } - - /** - * Connect to the VFS server. - * - * @return boolean True on success, PEAR_Error on false. - */ - protected function _connect() - { - /* Do variable substitution. */ - if (!empty($this->_params['vfs_path'])) { - $user = Ingo::getUser(); - $domain = Ingo::getDomain(); - if ($_SESSION['ingo']['backend']['hordeauth'] !== 'full') { - $pos = strpos($user, '@'); - if ($pos !== false) { - $domain = substr($user, $pos + 1); - $user = substr($user, 0, $pos); - } - } - $this->_params['vfs_path'] = str_replace( - array('%u', '%d', '%U'), - array($user, $domain, $this->_params['username']), - $this->_params['vfs_path']); - } - - if (!empty($this->_vfs)) { - return true; - } - - $this->_vfs = &VFS::singleton($this->_params['vfstype'], $this->_params); - if (is_a($this->_vfs, 'PEAR_Error')) { - $error = $this->_vfs; - $this->_vfs = null; - return $error; - } else { - return true; - } - } - -} diff --git a/ingo/lib/Script.php b/ingo/lib/Script.php index b61a28cc3..eda050fdc 100644 --- a/ingo/lib/Script.php +++ b/ingo/lib/Script.php @@ -115,7 +115,7 @@ class Ingo_Script static public function factory($script, $params = array()) { $script = basename($script); - $class = 'Ingo_Script_' . $script; + $class = 'Ingo_Script_' . ucfirst($script); return class_exists($class) ? new $class($params) diff --git a/ingo/lib/Script/Imap.php b/ingo/lib/Script/Imap.php new file mode 100644 index 000000000..a3f0a8742 --- /dev/null +++ b/ingo/lib/Script/Imap.php @@ -0,0 +1,370 @@ + + * @package Ingo + */ +class Ingo_Script_Imap extends Ingo_Script +{ + /** + * 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 + ); + + /** + * Does the driver support setting IMAP flags? + * + * @var boolean + */ + protected $_supportIMAPFlags = true; + + /** + * Does the driver support the stop-script option? + * + * @var boolean + */ + protected $_supportStopScript = true; + + /** + * This driver can perform on demand filtering (in fact, that is all + * it can do). + * + * @var boolean + */ + protected $_ondemand = true; + + /** + * The API to use for IMAP functions. + * + * @var Ingo_Script_Imap_Api + */ + protected $_api; + + /** + * Perform the filtering specified in the rules. + * + * @param array $params The parameter array. It MUST contain: + *
+     * 'mailbox' - The name of the mailbox to filter.
+     * 
+ * + * @return boolean True if filtering performed, false if not. + */ + public function perform($params) + { + if (empty($params['api'])) { + $this->_api = Ingo_Script_Imap_Api::factory('live', $params); + } else { + $this->_api = &$params['api']; + } + + /* 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 = $this->_api->getCache(); + if (($cache !== false) && ($cache == $_SESSION['ingo']['change'])) { + return true; + } + + /* Grab the rules list. */ + $filters = &$GLOBALS['ingo_storage']->retrieve(Ingo_Storage::ACTION_FILTERS); + + /* Should we filter only [un]seen messages? */ + $seen_flag = $GLOBALS['prefs']->getValue('filter_seen'); + + /* Should we use detailed notification messages? */ + $detailmsg = $GLOBALS['prefs']->getValue('show_filter_msg'); + + /* Parse through the rules, one-by-one. */ + foreach ($filters->getFilterList() 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; + } + + $search_array = array(); + + switch ($rule['action']) { + case Ingo_Storage::ACTION_BLACKLIST: + case Ingo_Storage::ACTION_WHITELIST: + $bl_folder = null; + + if ($rule['action'] == Ingo_Storage::ACTION_BLACKLIST) { + $blacklist = &$GLOBALS['ingo_storage']->retrieve(Ingo_Storage::ACTION_BLACKLIST); + $addr = $blacklist->getBlacklist(); + $bl_folder = $blacklist->getBlacklistFolder(); + } else { + $whitelist = &$GLOBALS['ingo_storage']->retrieve(Ingo_Storage::ACTION_WHITELIST); + $addr = $whitelist->getWhitelist(); + } + + /* If list is empty, move on. */ + if (empty($addr)) { + continue; + } + + $query = new Horde_Imap_Client_Search_Query(); + foreach ($addr as $val) { + $ob = new Horde_Imap_Client_Search_Query(); + $ob->flag('\\deleted', false); + if ($seen_flag == Ingo_Script::FILTER_UNSEEN) { + $ob->flag('\\seen', false); + } elseif ($seen_flag == Ingo_Script::FILTER_SEEN) { + $ob->flag('\\seen', true); + } + $ob->headerText('from', $val); + $search_array[] = $ob; + } + $query->orSearch($search_array); + $indices = $this->_api->search($query); + + /* Remove any indices that got in there by way of partial + * address match. */ + $msgs = $this->_api->fetchEnvelope($indices); + foreach ($msgs as $k => $v) { + $from_addr = Horde_Mime_Address::bareAddress(Horde_Mime_Address::addrArray2String($v['envelope']['from'])); + $found = false; + foreach ($addr as $val) { + if (strtolower($from_addr) == strtolower($val)) { + $found = true; + } + } + if (!$found) { + $indices = array_diff($indices, array($k)); + } + } + + if ($rule['action'] == Ingo_Storage::ACTION_BLACKLIST) { + $indices = array_diff($indices, $ignore_ids); + if (!empty($indices)) { + if (!empty($bl_folder)) { + $this->_api->moveMessages($indices, $bl_folder); + } else { + $this->_api->deleteMessages($indices); + } + $GLOBALS['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: + $query = new Horde_Imap_Client_Search_Query(); + foreach ($rule['conditions'] as $val) { + $ob = new Horde_Imap_Client_Search_Query(); + $ob->flag('\\deleted', false); + if ($seen_flag == Ingo_Script::FILTER_UNSEEN) { + $ob->flag('\\seen', false); + } elseif ($seen_flag == Ingo_Script::FILTER_SEEN) { + $ob->flag('\\seen', true); + } + 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->text($val['value'], true, ($val['match'] == 'not contain')); + } else { + $ob->headerText($val['field'], $val['value'], ($val['match'] == 'not contain')); + } + $search_array[] = $ob; + } + + if ($rule['combine'] == Ingo_Storage::COMBINE_ALL) { + $query->andSearch($search_array); + } else { + $query->orSearch($search_array); + } + + $indices = $this->_api->search($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'; + } + $this->_api->setMessageFlags($indices, implode(' ', $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 overview first. */ + if ($detailmsg) { + $overview = $this->_api->fetchEnvelope($indices); + } + + /* Move the messages to the requested mailbox. */ + $this->_api->moveMessages($indices, $rule['action-value']); + + /* Display notification message(s). */ + if ($detailmsg) { + foreach ($overview as $msg) { + $GLOBALS['notification']->push( + sprintf(_("Filter activity: The message \"%s\" from \"%s\" has been moved to the folder \"%s\"."), + !empty($msg['envelope']['subject']) ? Horde_Mime::decode($msg['envelope']['subject'], Horde_Nls::getCharset()) : _("[No Subject]"), + !empty($msg['envelope']['from']) ? Horde_Mime::decode(Horde_Mime_Address::addrArray2String($msg['envelope']['from']), Horde_Nls::getCharset()) : _("[No Sender]"), + Horde_String::convertCharset($rule['action-value'], 'UTF7-IMAP', Horde_Nls::getCharset())), + 'horde.message'); + } + } else { + $GLOBALS['notification']->push(sprintf(_("Filter activity: %s message(s) have been moved to the folder \"%s\"."), + count($indices), + Horde_String::convertCharset($rule['action-value'], 'UTF7-IMAP', Horde_Nls::getCharset())), 'horde.message'); + } + } elseif ($rule['action'] == Ingo_Storage::ACTION_DISCARD) { + /* We need to grab the overview first. */ + if ($detailmsg) { + $overview = $this->_api->fetchEnvelope($indices); + } + + /* Delete the messages now. */ + $this->_api->deleteMessages($indices); + + /* Display notification message(s). */ + if ($detailmsg) { + foreach ($overview as $msg) { + $GLOBALS['notification']->push( + sprintf(_("Filter activity: The message \"%s\" from \"%s\" has been deleted."), + !empty($msg['envelope']['subject']) ? Horde_Mime::decode($msg['envelope']['subject'], Horde_Nls::getCharset()) : _("[No Subject]"), + !empty($msg['envelope']['from']) ? Horde_Mime::decode($msg['envelope']['from'], Horde_Nls::getCharset()) : _("[No Sender]")), + 'horde.message'); + } + } else { + $GLOBALS['notification']->push(sprintf(_("Filter activity: %s message(s) have been deleted."), count($indices)), 'horde.message'); + } + } elseif ($rule['action'] == Ingo_Storage::ACTION_MOVEKEEP) { + /* Copy the messages to the requested mailbox. */ + $this->_api->copyMessages($indices, + $rule['action-value']); + + /* Display notification message(s). */ + if ($detailmsg) { + $overview = $this->_api->fetchEnvelope($indices); + foreach ($overview as $msg) { + $GLOBALS['notification']->push( + sprintf(_("Filter activity: The message \"%s\" from \"%s\" has been copied to the folder \"%s\"."), + !empty($msg['envelope']['subject']) ? Horde_Mime::decode($msg['envelope']['subject'], Horde_Nls::getCharset()) : _("[No Subject]"), + !empty($msg['envelope']['from']) ? Horde_Mime::decode($msg['envelope']['from'], Horde_Nls::getCharset()) : _("[No Sender]"), + Horde_String::convertCharset($rule['action-value'], 'UTF7-IMAP', Horde_Nls::getCharset())), + 'horde.message'); + } + } else { + $GLOBALS['notification']->push(sprintf(_("Filter activity: %s message(s) have been copied to the folder \"%s\"."), count($indices), Horde_String::convertCharset($rule['action-value'], 'UTF7-IMAP', Horde_Nls::getCharset())), 'horde.message'); + } + } + } + break; + } + } + + /* Set cache flag. */ + $this->_api->storeCache($_SESSION['ingo']['change']); + + return true; + } + + /** + * Is the apply() function available? + * + * @return boolean True if apply() is available, false if not. + */ + public function canApply() + { + if ($this->performAvailable() && + $GLOBALS['registry']->hasMethod('mail/server')) { + $server = $GLOBALS['registry']->call('mail/server'); + return ($server['protocol'] == 'imap'); + } + + return false; + } + + /** + * Apply the filters now. + * + * @return boolean See perform(). + */ + public function apply() + { + return $this->canApply() + ? $this->perform(array('mailbox' => 'INBOX')) + : false; + } + +} diff --git a/ingo/lib/Script/Imap/Api.php b/ingo/lib/Script/Imap/Api.php new file mode 100644 index 000000000..a2d2db8da --- /dev/null +++ b/ingo/lib/Script/Imap/Api.php @@ -0,0 +1,100 @@ + + * @package Ingo + */ +class Ingo_Script_Imap_Api +{ + /** + * TODO + */ + protected $_params; + + /** + * TODO + */ + static public function factory($type, $params) + { + $class = 'Ingo_Script_Imap_' . ucfirst($type); + return new $class($params); + } + + /** + * TODO + */ + public function __construct($params = array()) + { + $this->_params = $params; + } + + /** + * TODO + */ + public function deleteMessages($indices) + { + return PEAR::raiseError('Not implemented.'); + } + + /** + * TODO + */ + public function moveMessages($indices, $folder) + { + return PEAR::raiseError('Not implemented.'); + } + + /** + * TODO + */ + public function copyMessages($indices, $folder) + { + return PEAR::raiseError('Not implemented.'); + } + + /** + * TODO + */ + public function setMessageFlags($indices, $flags) + { + return PEAR::raiseError('Not implemented.'); + } + + /** + * TODO + */ + public function fetchEnvelope($indices) + { + return PEAR::raiseError('Not implemented.'); + } + + /** + * TODO + */ + public function search($query) + { + return PEAR::raiseError('Not implemented.'); + } + + /** + * TODO + */ + public function getCache() + { + return false; + } + + /** + * TODO + */ + public function storeCache($timestamp) + { + } + +} diff --git a/ingo/lib/Script/Imap/Live.php b/ingo/lib/Script/Imap/Live.php new file mode 100644 index 000000000..dee3fac41 --- /dev/null +++ b/ingo/lib/Script/Imap/Live.php @@ -0,0 +1,110 @@ + + * @author Michael Slusarz + * @package Ingo + */ +class Ingo_Script_Imap_Live extends Ingo_Script_Imap_Api +{ + /** + */ + public function deleteMessages($indices) + { + return $GLOBALS['registry']->hasMethod('mail/deleteMessages') + ? $GLOBALS['registry']->call('mail/deleteMessages', array($this->_params['mailbox'], $indices)) + : false; + } + + /** + */ + public function moveMessages($indices, $folder) + { + return $GLOBALS['registry']->hasMethod('mail/moveMessages') + ? $GLOBALS['registry']->call('mail/moveMessages', array($this->_params['mailbox'], $indices, $folder)) + : false; + } + + /** + */ + public function copyMessages($indices, $folder) + { + return $GLOBALS['registry']->hasMethod('mail/copyMessages') + ? $GLOBALS['registry']->call('mail/copyMessages', array($this->_params['mailbox'], $indices, $folder)) + : false; + } + + /** + */ + public function setMessageFlags($indices, $flags) + { + return $GLOBALS['registry']->hasMethod('mail/flagMessages') + ? $GLOBALS['registry']->call('mail/flagMessages', array($this->_params['mailbox'], $indices, $flags, true)) + : false; + } + + /** + */ + public function fetchEnvelope($indices) + { + return $GLOBALS['registry']->hasMethod('mail/msgEnvelope') + ? $GLOBALS['registry']->call('mail/msgEnvelope', array($this->_params['mailbox'], $indices)) + : false; + } + + /** + */ + public function search($query) + { + return $GLOBALS['registry']->hasMethod('mail/searchMailbox') + ? $GLOBALS['registry']->call('mail/searchMailbox', array($this->_params['mailbox'], $query)) + : false; + } + + /** + */ + public function getCache() + { + if (empty($_SESSION['ingo']['imapcache'][$this->_params['mailbox']])) { + return false; + } + $ptr = &$_SESSION['ingo']['imapcache'][$this->_params['mailbox']]; + + if ($this->_cacheId() != $ptr['id']) { + $ptr = array(); + return false; + } + + return $ptr['ts']; + } + + /** + */ + public function storeCache($timestamp) + { + if (!isset($_SESSION['ingo']['imapcache'])) { + $_SESSION['ingo']['imapcache'] = array(); + } + + $_SESSION['ingo']['imapcache'][$this->_params['mailbox']] = array( + 'id' => $this->_cacheId(), + 'ts' => $timestamp + ); + } + + /** + */ + protected function _cacheId() + { + return $GLOBALS['registry']->hasMethod('mail/mailboxCacheId') + ? $GLOBALS['registry']->call('mail/mailboxCacheId', array($this->_params['mailbox'])) + : time(); + } + +} diff --git a/ingo/lib/Script/Imap/Mock.php b/ingo/lib/Script/Imap/Mock.php new file mode 100644 index 000000000..a88afe950 --- /dev/null +++ b/ingo/lib/Script/Imap/Mock.php @@ -0,0 +1,125 @@ +_fixtures = array(); + + $dh = opendir($dir); + while (($dent = readdir($dh)) !== false) { + if (!in_array($dent, array('.', '..'))) { + $this->_fixtures[$dent] = Horde_Mime_Part::parseHeaders(file_get_contents($dir . '/' . $dent)); + } + } + closedir($dh); + + $i = 0; + foreach (array_keys($this->_fixtures) as $key) { + $this->_folders['INBOX'][] = array('uid' => ++$i, + 'fixture' => $key, + 'deleted' => false); + } + } + + /** + * TODO + */ + public function hasMessage($fixture, $folder = 'INBOX') + { + if (empty($this->_folders[$folder])) { + return false; + } + foreach ($this->_folders[$folder] as $message) { + if ($message['fixture'] == $fixture) { + return !$message['deleted']; + } + } + return false; + } + + /** + * TODO + */ + public function search(&$query) + { + $result = array(); + foreach ($this->_folders['INBOX'] as $message) { + if ($message['deleted']) { + continue; + } + if ($query->matches($this->_fixtures[$message['fixture']])) { + $result[] = $message['uid']; + } + } + return $result; + } + + /** + * TODO + */ + public function deleteMessages($indices) + { + foreach (array_keys($this->_folders['INBOX']) as $i) { + if (in_array($this->_folders['INBOX'][$i]['uid'], $indices)) { + unset($this->_folders['INBOX'][$i]); + } + } + + // Force renumbering + $this->_folders['INBOX'] = array_merge($this->_folders['INBOX'], array()); + } + + /** + * TODO + */ + public function moveMessages($indices, $folder) + { + foreach (array_keys($this->_folders['INBOX']) as $i) { + if (in_array($this->_folders['INBOX'][$i]['uid'], $indices)) { + $this->_folders[$folder][] = $this->_folders['INBOX'][$i]; + } + } + return $this->deleteMessages($indices); + } + + /** + * TODO + */ + public function fetchEnvelope($indices) + { + $result = array(); + + foreach ($indices as $uid) { + foreach (array_keys($this->_folders['INBOX']) as $i) { + if ($this->_folders['INBOX'][$i]['uid'] == $uid) { + $fixture = $this->_fixtures[$this->_folders['INBOX'][$i]['fixture']]; + $result[] = array( + 'envelope' => array( + 'from' => $fixture->getValue('from'), + 'uid' => $uid + ) + ); + } + } + } + + return $result; + } + +} diff --git a/ingo/lib/Script/Maildrop.php b/ingo/lib/Script/Maildrop.php new file mode 100644 index 000000000..676f9454f --- /dev/null +++ b/ingo/lib/Script/Maildrop.php @@ -0,0 +1,762 @@ + + * + * See the enclosed file LICENSE for license information (ASL). If you + * did not receive this file, see http://www.horde.org/licenses/asl.php. + * + * @author Matt Weyland + * @package Ingo + */ + +/** + * Additional storage action since maildrop does not support the + * "c-flag" as in procmail. + */ +define('MAILDROP_STORAGE_ACTION_STOREANDFORWARD', 100); + +/** + */ +class Ingo_Script_Maildrop extends Ingo_Script { + + /** + * The list of actions allowed (implemented) for this driver. + * + * @var array + */ + var $_actions = array( + Ingo_Storage::ACTION_KEEP, + Ingo_Storage::ACTION_MOVE, + Ingo_Storage::ACTION_DISCARD, + Ingo_Storage::ACTION_REDIRECT, + Ingo_Storage::ACTION_REDIRECTKEEP, + Ingo_Storage::ACTION_REJECT, + ); + + /** + * The categories of filtering allowed. + * + * @var array + */ + var $_categories = array( + Ingo_Storage::ACTION_BLACKLIST, + Ingo_Storage::ACTION_WHITELIST, + Ingo_Storage::ACTION_VACATION, + Ingo_Storage::ACTION_FORWARD, + Ingo_Storage::ACTION_SPAM, + ); + + /** + * The types of tests allowed (implemented) for this driver. + * + * @var array + */ + var $_types = array( + Ingo_Storage::TYPE_HEADER, + ); + + /** + * The list of tests allowed (implemented) for this driver. + * + * @var array + */ + var $_tests = array( + 'contains', 'not contain', + 'is', 'not is', + 'begins with','not begins with', + 'ends with', 'not ends with', + 'regex', + 'matches', 'not matches', + 'exists', 'not exist', + 'less than', 'less than or equal to', + 'equal', 'not equal', + 'greater than', 'greater than or equal to', + ); + + /** + * Can tests be case sensitive? + * + * @var boolean + */ + var $_casesensitive = true; + + /** + * Does the driver support the stop-script option? + * + * @var boolean + */ + var $_supportStopScript = false; + + /** + * Does the driver require a script file to be generated? + * + * @var boolean + */ + var $_scriptfile = true; + + /** + * The recipes that make up the code. + * + * @var array + */ + var $_recipes = array(); + + /** + * Returns a script previously generated with generate(). + * + * @return string The maildrop script. + */ + function toCode() + { + $code = ''; + foreach ($this->_recipes as $item) { + $code .= $item->generate() . "\n"; + } + return rtrim($code); + } + + /** + * Generates the maildrop script to do the filtering specified in + * the rules. + * + * @return string The maildrop script. + */ + function generate() + { + $filters = &$GLOBALS['ingo_storage']->retrieve(Ingo_Storage::ACTION_FILTERS); + + $this->addItem(new Maildrop_Comment(_("maildrop script generated by Ingo") . ' (' . date('F j, Y, g:i a') . ')')); + + /* Add variable information, if present. */ + if (!empty($this->_params['variables']) && + is_array($this->_params['variables'])) { + foreach ($this->_params['variables'] as $key => $val) { + $this->addItem(new Maildrop_Variable(array('name' => $key, 'value' => $val))); + } + } + + foreach ($filters->getFilterList() as $filter) { + switch ($filter['action']) { + case Ingo_Storage::ACTION_BLACKLIST: + $this->generateBlacklist(!empty($filter['disable'])); + break; + + case Ingo_Storage::ACTION_WHITELIST: + $this->generateWhitelist(!empty($filter['disable'])); + break; + + case Ingo_Storage::ACTION_FORWARD: + $this->generateForward(!empty($filter['disable'])); + break; + + case Ingo_Storage::ACTION_VACATION: + $this->generateVacation(!empty($filter['disable'])); + break; + + case Ingo_Storage::ACTION_SPAM: + $this->generateSpamfilter(!empty($filter['disable'])); + break; + + default: + if (in_array($filter['action'], $this->_actions)) { + /* Create filter if using AND. */ + $recipe = new Maildrop_Recipe($filter, $this->_params); + foreach ($filter['conditions'] as $condition) { + $recipe->addCondition($condition); + } + $this->addItem(new Maildrop_Comment($filter['name'], !empty($filter['disable']), true)); + $this->addItem($recipe); + } + } + } + + return $this->toCode(); + } + + /** + * Generates the maildrop script to handle the blacklist specified in + * the rules. + * + * @param boolean $disable Disable the blacklist? + */ + function generateBlacklist($disable = false) + { + $blacklist = &$GLOBALS['ingo_storage']->retrieve(Ingo_Storage::ACTION_BLACKLIST); + $bl_addr = $blacklist->getBlacklist(); + $bl_folder = $blacklist->getBlacklistFolder(); + + $bl_type = (empty($bl_folder)) ? Ingo_Storage::ACTION_DISCARD : Ingo_Storage::ACTION_MOVE; + + if (!empty($bl_addr)) { + $this->addItem(new Maildrop_Comment(_("Blacklisted Addresses"), $disable, true)); + $params = array('action-value' => $bl_folder, + 'action' => $bl_type, + 'disable' => $disable); + + foreach ($bl_addr as $address) { + if (!empty($address)) { + $recipe = new Maildrop_Recipe($params, $this->_params); + $recipe->addCondition(array('field' => 'From', 'value' => $address)); + $this->addItem($recipe); + } + } + } + } + + /** + * Generates the maildrop script to handle the whitelist specified in + * the rules. + * + * @param boolean $disable Disable the whitelist? + */ + function generateWhitelist($disable = false) + { + $whitelist = &$GLOBALS['ingo_storage']->retrieve(Ingo_Storage::ACTION_WHITELIST); + $wl_addr = $whitelist->getWhitelist(); + + if (!empty($wl_addr)) { + $this->addItem(new Maildrop_Comment(_("Whitelisted Addresses"), $disable, true)); + foreach ($wl_addr as $address) { + if (!empty($address)) { + $recipe = new Maildrop_Recipe(array('action' => Ingo_Storage::ACTION_KEEP, 'disable' => $disable), $this->_params); + $recipe->addCondition(array('field' => 'From', 'value' => $address)); + $this->addItem($recipe); + } + } + } + } + + /** + * Generates the maildrop script to handle mail forwards. + * + * @param boolean $disable Disable forwarding? + */ + function generateForward($disable = false) + { + $forward = &$GLOBALS['ingo_storage']->retrieve(Ingo_Storage::ACTION_FORWARD); + $addresses = $forward->getForwardAddresses(); + + if (!empty($addresses)) { + $this->addItem(new Maildrop_Comment(_("Forwards"), $disable, true)); + $params = array('action' => Ingo_Storage::ACTION_FORWARD, + 'action-value' => $addresses, + 'disable' => $disable); + if ($forward->getForwardKeep()) { + $params['action'] = MAILDROP_STORAGE_ACTION_STOREANDFORWARD; + } + $recipe = new Maildrop_Recipe($params, $this->_params); + $recipe->addCondition(array('field' => 'From', 'value' => '')); + $this->addItem($recipe); + } + } + + /** + * Generates the maildrop script to handle vacation messages. + * + * @param boolean $disable Disable forwarding? + */ + function generateVacation($disable = false) + { + $vacation = &$GLOBALS['ingo_storage']->retrieve(Ingo_Storage::ACTION_VACATION); + $addresses = $vacation->getVacationAddresses(); + $actionval = array('addresses' => $addresses, + 'subject' => $vacation->getVacationSubject(), + 'days' => $vacation->getVacationDays(), + 'reason' => $vacation->getVacationReason(), + 'ignorelist' => $vacation->getVacationIgnorelist(), + 'excludes' => $vacation->getVacationExcludes(), + 'start' => $vacation->getVacationStart(), + 'end' => $vacation->getVacationEnd()); + + if (!empty($addresses)) { + $this->addItem(new Maildrop_Comment(_("Vacation"), $disable, true)); + $params = array('action' => Ingo_Storage::ACTION_VACATION, + 'action-value' => $actionval, + 'disable' => $disable); + $recipe = new Maildrop_Recipe($params, $this->_params); + $this->addItem($recipe); + } + } + + /** + * Generates the maildrop script to handle spam as identified by SpamAssassin + * + * @param boolean $disable Disable the spam-filter? + */ + function generateSpamfilter($disable = false) + { + $spam = &$GLOBALS['ingo_storage']->retrieve(Ingo_Storage::ACTION_SPAM); + if ($spam == false) { + return; + } + + $spam_folder = $spam->getSpamFolder(); + $spam_action = (empty($spam_folder)) ? Ingo_Storage::ACTION_DISCARD : Ingo_Storage::ACTION_MOVE; + + $this->addItem(new Maildrop_Comment(_("Spam Filter"), $disable, true)); + + $params = array('action-value' => $spam_folder, + 'action' => $spam_action, + 'disable' => $disable); + $recipe = new Maildrop_Recipe($params, $this->_params); + if ($GLOBALS['conf']['spam']['compare'] == 'numeric') { + $recipe->addCondition(array('match' => 'greater than or equal to', + 'field' => $GLOBALS['conf']['spam']['header'], + 'value' => $spam->getSpamLevel())); + } elseif ($GLOBALS['conf']['spam']['compare'] == 'string') { + $recipe->addCondition(array('match' => 'contains', + 'field' => $GLOBALS['conf']['spam']['header'], + 'value' => str_repeat($GLOBALS['conf']['spam']['char'], $spam->getSpamLevel()))); + } + + $this->addItem($recipe); + } + + /** + * Adds an item to the recipe list. + * + * @param object $item The item to add to the recipe list. + * The object should have a generate() function. + */ + function addItem($item) + { + $this->_recipes[] = $item; + } + +} + +/** + * The Maildrop_Comment:: class represents a maildrop comment. This is + * a pretty simple class, but it makes the code in Ingo_Script_Maildrop:: + * cleaner as it provides a generate() function and can be added to the + * recipe list the same way as a recipe can be. + * + * @author Matt Weyland + * @since Ingo 1.1 + * @package Ingo + */ +class Maildrop_Comment { + + /** + * The comment text. + * + * @var string + */ + var $_comment = ''; + + /** + * Constructs a new maildrop comment. + * + * @param string $comment Comment to be generated. + * @param boolean $disable Output 'DISABLED' comment? + * @param boolean $header Output a 'header' comment? + */ + function Maildrop_Comment($comment, $disable = false, $header = false) + { + if ($disable) { + $comment = _("DISABLED: ") . $comment; + } + + if ($header) { + $this->_comment .= "##### $comment #####"; + } else { + $this->_comment .= "# $comment"; + } + } + + /** + * Returns the comment stored by this object. + * + * @return string The comment stored by this object. + */ + function generate() + { + return $this->_comment; + } + +} + +/** + * The Maildrop_Recipe:: class represents a maildrop recipe. + * + * @author Matt Weyland + * @since Ingo 1.1 + * @package Ingo + */ +class Maildrop_Recipe { + + var $_action = array(); + var $_conditions = array(); + var $_disable = ''; + var $_flags = ''; + var $_params = array(); + var $_combine = ''; + var $_valid = true; + + var $_operators = array( + 'less than' => '<', + 'less than or equal to' => '<=', + 'equal' => '==', + 'not equal' => '!=', + 'greater than' => '>', + 'greater than or equal to' => '>=', + ); + + /** + * Constructs a new maildrop recipe. + * + * @param array $params Array of parameters. + * REQUIRED FIELDS: + * 'action' + * OPTIONAL FIELDS: + * 'action-value' (only used if the + * 'action' requires it) + * @param array $scriptparams Array of parameters passed to + * Ingo_Script_Maildrop::. + */ + function Maildrop_Recipe($params = array(), $scriptparams = array()) + { + $this->_disable = !empty($params['disable']); + $this->_params = $scriptparams; + $this->_action[] = 'exception {'; + + switch ($params['action']) { + case Ingo_Storage::ACTION_KEEP: + $this->_action[] = ' to "${DEFAULT}"'; + break; + + case Ingo_Storage::ACTION_MOVE: + $this->_action[] = ' to ' . $this->maildropPath($params['action-value']); + break; + + case Ingo_Storage::ACTION_DISCARD: + $this->_action[] = ' exit'; + break; + + case Ingo_Storage::ACTION_REDIRECT: + $this->_action[] = ' to "! ' . $params['action-value'] . '"'; + break; + + case Ingo_Storage::ACTION_REDIRECTKEEP: + $this->_action[] = ' cc "! ' . $params['action-value'] . '"'; + $this->_action[] = ' to "${DEFAULT}"'; + break; + + case Ingo_Storage::ACTION_REJECT: + $this->_action[] = ' EXITCODE=77'; # EX_NOPERM (permanent failure) + $this->_action[] = ' echo "5.7.1 ' . $params['action-value'] . '"'; + $this->_action[] = ' exit'; + break; + + case Ingo_Storage::ACTION_VACATION: + $from = ''; + foreach ($params['action-value']['addresses'] as $address) { + $from = $address; + } + + /** + * @TODO + * + * Exclusion and listfilter + */ + $exclude = ''; + foreach ($params['action-value']['excludes'] as $address) { + $exclude .= $address . ' '; + } + + $start = strftime($params['action-value']['start']); + if ($start === false) { + $start = 0; + } + $end = strftime($params['action-value']['end']); + if ($end === false) { + $end = 0; + } + $days = strftime($params['action-value']['days']); + if ($days === false) { + // Set to same value as $_days in ingo/lib/Storage.php + $days = 7; + } + + // Writing vacation.msg file + $reason = Horde_Mime::encode($params['action-value']['reason'], Horde_Nls::getCharset()); + $driver = Ingo::getDriver(); + $driver->_connect(); + $result = $driver->_vfs->writeData($driver->_params['vfs_path'], 'vacation.msg', $reason, true); + + // Rule : Do not send responses to bulk or list messages + if ($params['action-value']['ignorelist'] == 1) { + $params['combine'] = Ingo_Storage::COMBINE_ALL; + $this->addCondition(array('match' => 'filter', 'field' => '', 'value' => '! /^Precedence: (bulk|list|junk)/')); + $this->addCondition(array('match' => 'filter', 'field' => '', 'value' => '! /^Return-Path:.*<#@\[\]>/')); + $this->addCondition(array('match' => 'filter', 'field' => '', 'value' => '! /^Return-Path:.*<>/')); + $this->addCondition(array('match' => 'filter', 'field' => '', 'value' => '! /^From:.*MAILER-DAEMON/')); + $this->addCondition(array('match' => 'filter', 'field' => '', 'value' => '! /^X-ClamAV-Notice-Flag: *YES/')); + $this->addCondition(array('match' => 'filter', 'field' => '', 'value' => '! /^Content-Type:.*message\/delivery-status/')); + $this->addCondition(array('match' => 'filter', 'field' => '', 'value' => '! /^Subject:.*Delivery Status Notification/')); + $this->addCondition(array('match' => 'filter', 'field' => '', 'value' => '! /^Subject:.*Undelivered Mail Returned to Sender/')); + $this->addCondition(array('match' => 'filter', 'field' => '', 'value' => '! /^Subject:.*Delivery failure/')); + $this->addCondition(array('match' => 'filter', 'field' => '', 'value' => '! /^Subject:.*Message delay/')); + $this->addCondition(array('match' => 'filter', 'field' => '', 'value' => '! /^Subject:.*Mail Delivery Subsystem/')); + $this->addCondition(array('match' => 'filter', 'field' => '', 'value' => '! /^Subject:.*Mail System Error.*Returned Mail/')); + $this->addCondition(array('match' => 'filter', 'field' => '', 'value' => '! /^X-Spam-Flag: YES/ ')); + } else { + $this->addCondition(array('field' => 'From', 'value' => '')); + } + + // Rule : Start/End of vacation + if (($start != 0) && ($end !== 0)) { + $this->_action[] = ' flock "vacation.lock" {'; + $this->_action[] = ' current_time=time'; + $this->_action[] = ' if ( \ '; + $this->_action[] = ' ($current_time >= ' . $start . ') && \ '; + $this->_action[] = ' ($current_time <= ' . $end . ')) '; + $this->_action[] = ' {'; + } + $this->_action[] = " cc \"| mailbot -D " . $params['action-value']['days'] . " -c '" . Horde_Nls::getCharset() . "' -t \$HOME/vacation.msg -d \$HOME/vacation -A 'From: $from' -s '" . Horde_Mime::encode($params['action-value']['subject'], Horde_Nls::getCharset()) . "' /usr/sbin/sendmail -t \""; + if (($start != 0) && ($end !== 0)) { + $this->_action[] = ' }'; + $this->_action[] = ' }'; + } + + break; + + case Ingo_Storage::ACTION_FORWARD: + case MAILDROP_STORAGE_ACTION_STOREANDFORWARD: + foreach ($params['action-value'] as $address) { + if (!empty($address)) { + $this->_action[] = ' cc "! ' . $address . '"'; + } + } + + /* The 'to' must be the last action, because maildrop + * stops processing after it. */ + if ($params['action'] == MAILDROP_STORAGE_ACTION_STOREANDFORWARD) { + $this->_action[] = ' to "${DEFAULT}"'; + } else { + $this->_action[] = ' exit'; + } + break; + + default: + $this->_valid = false; + break; + } + + $this->_action[] = '}'; + + if (isset($params['combine']) && + ($params['combine'] == Ingo_Storage::COMBINE_ALL)) { + $this->_combine = '&& '; + } else { + $this->_combine = '|| '; + } + } + + /** + * Adds a flag to the recipe. + * + * @param string $flag String of flags to append to the current flags. + */ + function addFlag($flag) + { + $this->_flags .= $flag; + } + + /** + * Adds a condition to the recipe. + * + * @param optonal array $condition Array of parameters. Required keys + * are 'field' and 'value'. 'case' is + * an optional keys. + */ + function addCondition($condition = array()) + { + $flag = (!empty($condition['case'])) ? 'D' : ''; + if (empty($this->_conditions)) { + $this->addFlag($flag); + } + + $string = ''; + $extra = ''; + + $match = (isset($condition['match'])) ? $condition['match'] : null; + // negate tests starting with 'not ', except 'not equals', which simply uses the != operator + if ($match != 'not equal' && substr($match, 0, 4) == 'not ') { + $string .= '! '; + } + + // convert 'field' to PCRE pattern matching + if (strpos($condition['field'], ',') == false) { + $string .= '/^' . $condition['field'] . ':\\s*'; + } else { + $string .= '/^(' . str_replace(',', '|', $condition['field']) . '):\\s*'; + } + + switch ($match) { + case 'not regex': + case 'regex': + $string .= $condition['value'] . '/:h'; + break; + + case 'filter': + $string = $condition['value']; + break; + + case 'exists': + case 'not exist': + // Just run a match for the header name + $string .= '/:h'; + break; + + case 'less than or equal to': + case 'less than': + case 'equal': + case 'not equal': + case 'greater than or equal to': + case 'greater than': + $string .= '(\d+(\.\d+)?)/:h'; + $extra = ' && $MATCH1 ' . $this->_operators[$match] . ' ' . (int)$condition['value']; + break; + + case 'begins with': + case 'not begins with': + $string .= preg_quote($condition['value'], '/') . '/:h'; + break; + + case 'ends with': + case 'not ends with': + $string .= '.*' . preg_quote($condition['value'], '/') . '$/:h'; + break; + + case 'is': + case 'not is': + $string .= preg_quote($condition['value'], '/') . '$/:h'; + break; + + case 'matches': + case 'not matches': + $string .= str_replace(array('\\*', '\\?'), array('.*', '.'), preg_quote($condition['value'], '/') . '$') . '/:h'; + break; + + case 'contains': + case 'not contain': + default: + $string .= '.*' . preg_quote($condition['value'], '/') . '/:h'; + break; + } + + $this->_conditions[] = array('condition' => $string, 'flags' => $flag, 'extra' => $extra); + } + + /** + * Generates maildrop code to represent the recipe. + * + * @return string maildrop code to represent the recipe. + */ + function generate() + { + $text = array(); + + if (!$this->_valid) { + return ''; + } + + if (count($this->_conditions) > 0) { + + $text[] = "if( \\"; + + $nest = false; + foreach ($this->_conditions as $condition) { + $cond = $nest ? $this->_combine : ' '; + $text[] = $cond . $condition['condition'] . $condition['flags'] . $condition['extra'] . " \\"; + $nest = true; + } + + $text[] = ')'; + } + + foreach ($this->_action as $val) { + $text[] = $val; + } + + if ($this->_disable) { + $code = ''; + foreach ($text as $val) { + $comment = new Maildrop_Comment($val); + $code .= $comment->generate() . "\n"; + } + return $code . "\n"; + } else { + return implode("\n", $text) . "\n"; + } + } + + /** + * Returns a maildrop-ready mailbox path, converting IMAP folder pathname + * conventions as necessary. + * + * @param string $folder The IMAP folder name. + * + * @return string The maildrop mailbox path. + */ + function maildropPath($folder) + { + /* NOTE: '$DEFAULT' here is a literal, not a PHP variable. */ + if (isset($this->_params) && + ($this->_params['path_style'] == 'maildir')) { + if (empty($folder) || ($folder == 'INBOX')) { + return '"${DEFAULT}"'; + } + if ($this->_params['strip_inbox'] && + substr($folder, 0, 6) == 'INBOX.') { + $folder = substr($folder, 6); + } + return '"${DEFAULT}/.' . $folder . '/"'; + } else { + if (empty($folder) || ($folder == 'INBOX')) { + return '${DEFAULT}'; + } + return str_replace(' ', '\ ', $folder); + } + } + +} + +/** + * The Maildrop_Variable:: class represents a Maildrop variable. + * + * @author Matt Weyland + * @since Ingo 1.1 + * @package Ingo + */ +class Maildrop_Variable { + + var $_name; + var $_value; + + /** + * Constructs a new maildrop variable. + * + * @param array $params Array of parameters. Expected fields are 'name' + * and 'value'. + */ + function Maildrop_Variable($params = array()) + { + $this->_name = $params['name']; + $this->_value = $params['value']; + } + + /** + * Generates maildrop code to represent the variable. + * + * @return string maildrop code to represent the variable. + */ + function generate() + { + return $this->_name . '=' . $this->_value . "\n"; + } + +} diff --git a/ingo/lib/Script/Procmail.php b/ingo/lib/Script/Procmail.php new file mode 100644 index 000000000..f0228432d --- /dev/null +++ b/ingo/lib/Script/Procmail.php @@ -0,0 +1,802 @@ + + * @author Ben Chavet + * @package Ingo + */ +class Ingo_Script_Procmail extends Ingo_Script { + + /** + * The list of actions allowed (implemented) for this driver. + * + * @var array + */ + var $_actions = array( + Ingo_Storage::ACTION_KEEP, + Ingo_Storage::ACTION_MOVE, + Ingo_Storage::ACTION_DISCARD, + Ingo_Storage::ACTION_REDIRECT, + Ingo_Storage::ACTION_REDIRECTKEEP, + Ingo_Storage::ACTION_REJECT + ); + + /** + * The categories of filtering allowed. + * + * @var array + */ + var $_categories = array( + Ingo_Storage::ACTION_BLACKLIST, + Ingo_Storage::ACTION_WHITELIST, + Ingo_Storage::ACTION_VACATION, + Ingo_Storage::ACTION_FORWARD + ); + + /** + * The types of tests allowed (implemented) for this driver. + * + * @var array + */ + var $_types = array( + Ingo_Storage::TYPE_HEADER, + Ingo_Storage::TYPE_BODY + ); + + /** + * A list of any special types that this driver supports. + * + * @var array + */ + var $_special_types = array( + 'Destination', + ); + + /** + * The list of tests allowed (implemented) for this driver. + * + * @var array + */ + var $_tests = array( + 'contains', + 'not contain', + 'begins with', + 'not begins with', + 'ends with', + 'not ends with', + 'regex' + ); + + /** + * Can tests be case sensitive? + * + * @var boolean + */ + var $_casesensitive = true; + + /** + * Does the driver support the stop-script option? + * + * @var boolean + */ + var $_supportStopScript = true; + + /** + * Does the driver require a script file to be generated? + * + * @var boolean + */ + var $_scriptfile = true; + + /** + * The recipes that make up the code. + * + * @var array + */ + var $_recipes = array(); + + /** + * Returns a script previously generated with generate(). + * + * @return string The procmail script. + */ + function toCode() + { + $code = ''; + foreach ($this->_recipes as $item) { + $code .= $item->generate() . "\n"; + } + + // If an external delivery program is used, add final rule + // to deliver to $DEFAULT + if (isset($this->_params['delivery_agent'])) { + $code .= ":0 w\n"; + $code .= isset($this->_params['delivery_mailbox_prefix']) ? + '| ' . $this->_params['delivery_agent'] . ' ' . $this->_params['delivery_mailbox_prefix'] . '$DEFAULT' : + '| ' . $this->_params['delivery_agent'] . ' $DEFAULT'; + } + + return rtrim($code) . "\n"; + } + + /** + * Generates the procmail script to do the filtering specified in the + * rules. + * + * @return string The procmail script. + */ + function generate() + { + $filters = &$GLOBALS['ingo_storage']->retrieve(Ingo_Storage::ACTION_FILTERS); + + $this->addItem(new Procmail_Comment(_("procmail script generated by Ingo") . ' (' . date('F j, Y, g:i a') . ')')); + + /* Add variable information, if present. */ + if (!empty($this->_params['variables']) && + is_array($this->_params['variables'])) { + foreach ($this->_params['variables'] as $key => $val) { + $this->addItem(new Procmail_Variable(array('name' => $key, 'value' => $val))); + } + } + + foreach ($filters->getFilterList() as $filter) { + switch ($filter['action']) { + case Ingo_Storage::ACTION_BLACKLIST: + $this->generateBlacklist(!empty($filter['disable'])); + break; + + case Ingo_Storage::ACTION_WHITELIST: + $this->generateWhitelist(!empty($filter['disable'])); + break; + + case Ingo_Storage::ACTION_VACATION: + $this->generateVacation(!empty($filter['disable'])); + break; + + case Ingo_Storage::ACTION_FORWARD: + $this->generateForward(!empty($filter['disable'])); + break; + + default: + if (in_array($filter['action'], $this->_actions)) { + /* Create filter if using AND. */ + if ($filter['combine'] == Ingo_Storage::COMBINE_ALL) { + $recipe = new Procmail_Recipe($filter, $this->_params); + if (!$filter['stop']) { + $recipe->addFlag('c'); + } + foreach ($filter['conditions'] as $condition) { + $recipe->addCondition($condition); + } + $this->addItem(new Procmail_Comment($filter['name'], !empty($filter['disable']), true)); + $this->addItem($recipe); + } else { + /* Create filter if using OR */ + $this->addItem(new Procmail_Comment($filter['name'], !empty($filter['disable']), true)); + $loop = 0; + foreach ($filter['conditions'] as $condition) { + $recipe = new Procmail_Recipe($filter, $this->_params); + if ($loop++) { + $recipe->addFlag('E'); + } + if (!$filter['stop']) { + $recipe->addFlag('c'); + } + $recipe->addCondition($condition); + $this->addItem($recipe); + } + } + } + } + } + + return $this->toCode(); + } + + /** + * Generates the procmail script to handle the blacklist specified in + * the rules. + * + * @param boolean $disable Disable the blacklist? + */ + function generateBlacklist($disable = false) + { + $blacklist = &$GLOBALS['ingo_storage']->retrieve(Ingo_Storage::ACTION_BLACKLIST); + $bl_addr = $blacklist->getBlacklist(); + $bl_folder = $blacklist->getBlacklistFolder(); + + $bl_type = (empty($bl_folder)) ? Ingo_Storage::ACTION_DISCARD : Ingo_Storage::ACTION_MOVE; + + if (!empty($bl_addr)) { + $this->addItem(new Procmail_Comment(_("Blacklisted Addresses"), $disable, true)); + $params = array('action-value' => $bl_folder, + 'action' => $bl_type, + 'disable' => $disable); + + foreach ($bl_addr as $address) { + if (!empty($address)) { + $recipe = new Procmail_Recipe($params, $this->_params); + $recipe->addCondition(array('field' => 'From', 'value' => $address, 'match' => 'address')); + $this->addItem($recipe); + } + } + } + } + + /** + * Generates the procmail script to handle the whitelist specified in + * the rules. + * + * @param boolean $disable Disable the whitelist? + */ + function generateWhitelist($disable = false) + { + $whitelist = &$GLOBALS['ingo_storage']->retrieve(Ingo_Storage::ACTION_WHITELIST); + $wl_addr = $whitelist->getWhitelist(); + + if (!empty($wl_addr)) { + $this->addItem(new Procmail_Comment(_("Whitelisted Addresses"), $disable, true)); + foreach ($wl_addr as $address) { + if (!empty($address)) { + $recipe = new Procmail_Recipe(array('action' => Ingo_Storage::ACTION_KEEP, 'disable' => $disable), $this->_params); + $recipe->addCondition(array('field' => 'From', 'value' => $address, 'match' => 'address')); + $this->addItem($recipe); + } + } + } + } + + /** + * Generates the procmail script to handle vacation. + * + * @param boolean $disable Disable vacation? + */ + function generateVacation($disable = false) + { + $vacation = &$GLOBALS['ingo_storage']->retrieve(Ingo_Storage::ACTION_VACATION); + $addresses = $vacation->getVacationAddresses(); + $actionval = array( + 'addresses' => $addresses, + 'subject' => $vacation->getVacationSubject(), + 'days' => $vacation->getVacationDays(), + 'reason' => $vacation->getVacationReason(), + 'ignorelist' => $vacation->getVacationIgnorelist(), + 'excludes' => $vacation->getVacationExcludes(), + 'start' => $vacation->getVacationStart(), + 'end' => $vacation->getVacationEnd(), + ); + + if (!empty($addresses)) { + $this->addItem(new Procmail_Comment(_("Vacation"), $disable, true)); + $params = array('action' => Ingo_Storage::ACTION_VACATION, + 'action-value' => $actionval, + 'disable' => $disable); + $recipe = new Procmail_Recipe($params, $this->_params); + $this->addItem($recipe); + } + } + + /** + * Generates the procmail script to handle mail forwards. + * + * @param boolean $disable Disable forwarding? + */ + function generateForward($disable = false) + { + $forward = &$GLOBALS['ingo_storage']->retrieve(Ingo_Storage::ACTION_FORWARD); + $addresses = $forward->getForwardAddresses(); + + if (!empty($addresses)) { + $this->addItem(new Procmail_Comment(_("Forwards"), $disable, true)); + $params = array('action' => Ingo_Storage::ACTION_FORWARD, + 'action-value' => $addresses, + 'disable' => $disable); + $recipe = new Procmail_Recipe($params, $this->_params); + if ($forward->getForwardKeep()) { + $recipe->addFlag('c'); + } + $this->addItem($recipe); + } + } + + /** + * Adds an item to the recipe list. + * + * @param object $item The item to add to the recipe list. + * The object should have a generate() function. + */ + function addItem($item) + { + $this->_recipes[] = $item; + } + +} + +/** + * The Procmail_Comment:: class represents a Procmail comment. This is + * a pretty simple class, but it makes the code in Ingo_Script_Procmail:: + * cleaner as it provides a generate() function and can be added to the + * recipe list the same way as a recipe can be. + * + * @author Ben Chavet + * @since Ingo 1.0 + * @package Ingo + */ +class Procmail_Comment { + + /** + * The comment text. + * + * @var string + */ + var $_comment = ''; + + /** + * Constructs a new procmail comment. + * + * @param string $comment Comment to be generated. + * @param boolean $disable Output 'DISABLED' comment? + * @param boolean $header Output a 'header' comment? + */ + function Procmail_Comment($comment, $disable = false, $header = false) + { + if ($disable) { + $comment = _("DISABLED: ") . $comment; + } + + if ($header) { + $this->_comment .= "##### $comment #####"; + } else { + $this->_comment .= "# $comment"; + } + } + + /** + * Returns the comment stored by this object. + * + * @return string The comment stored by this object. + */ + function generate() + { + return $this->_comment; + } + +} + +/** + * The Procmail_Recipe:: class represents a Procmail recipe. + * + * @author Ben Chavet + * @since Ingo 1.0 + * @package Ingo + */ +class Procmail_Recipe { + + var $_action = array(); + var $_conditions = array(); + var $_disable = ''; + var $_flags = ''; + var $_params = array( + 'date' => 'date', + 'echo' => 'echo', + 'ls' => 'ls' + ); + var $_valid = true; + + /** + * Constructs a new procmail recipe. + * + * @param array $params Array of parameters. + * REQUIRED FIELDS: + * 'action' + * OPTIONAL FIELDS: + * 'action-value' (only used if the + * 'action' requires it) + * @param array $scriptparams Array of parameters passed to + * Ingo_Script_Procmail::. + */ + function Procmail_Recipe($params = array(), $scriptparams = array()) + { + $this->_disable = !empty($params['disable']); + $this->_params = array_merge($this->_params, $scriptparams); + + switch ($params['action']) { + case Ingo_Storage::ACTION_KEEP: + // Note: you may have to set the DEFAULT variable in your + // backend configuration. + if (isset($this->_params['delivery_agent']) && isset($this->_params['delivery_mailbox_prefix'])) { + $this->_action[] = '| ' . $this->_params['delivery_agent'] . ' ' . $this->_params['delivery_mailbox_prefix'] . '$DEFAULT'; + } elseif (isset($this->_params['delivery_agent'])) { + $this->_action[] = '| ' . $this->_params['delivery_agent'] . ' $DEFAULT'; + } else { + $this->_action[] = '$DEFAULT'; + } + break; + + case Ingo_Storage::ACTION_MOVE: + if (isset($this->_params['delivery_agent']) && isset($this->_params['delivery_mailbox_prefix'])) { + $this->_action[] = '| ' . $this->_params['delivery_agent'] . ' ' . $this->_params['delivery_mailbox_prefix'] . $this->procmailPath($params['action-value']); + } elseif (isset($this->_params['delivery_agent'])) { + $this->_action[] = '| ' . $this->_params['delivery_agent'] . ' ' . $this->procmailPath($params['action-value']); + } else { + $this->_action[] = $this->procmailPath($params['action-value']); + } + break; + + case Ingo_Storage::ACTION_DISCARD: + $this->_action[] = '/dev/null'; + break; + + case Ingo_Storage::ACTION_REDIRECT: + $this->_action[] = '! ' . $params['action-value']; + break; + + case Ingo_Storage::ACTION_REDIRECTKEEP: + $this->_action[] = '{'; + $this->_action[] = ' :0 c'; + $this->_action[] = ' ! ' . $params['action-value']; + $this->_action[] = ''; + $this->_action[] = ' :0' . (isset($this->_params['delivery_agent']) ? ' w' : ''); + if (isset($this->_params['delivery_agent']) && isset($this->_params['delivery_mailbox_prefix'])) { + $this->_action[] = ' | ' . $this->_params['delivery_agent'] . ' ' . $this->_params['delivery_mailbox_prefix'] . '$DEFAULT'; + } elseif (isset($this->_params['delivery_agent'])) { + $this->_action[] = ' | ' . $this->_params['delivery_agent'] . ' $DEFAULT'; + } else { + $this->_action[] = ' $DEFAULT'; + } + $this->_action[] = '}'; + break; + + case Ingo_Storage::ACTION_REJECT: + $this->_action[] = '{'; + $this->_action[] = ' EXITCODE=' . $params['action-value']; + $this->_action[] = ' HOST="no.address.here"'; + $this->_action[] = '}'; + break; + + case Ingo_Storage::ACTION_VACATION: + $days = $params['action-value']['days']; + $timed = !empty($params['action-value']['start']) && + !empty($params['action-value']['end']); + $this->_action[] = '{'; + foreach ($params['action-value']['addresses'] as $address) { + if (!empty($address)) { + $this->_action[] = ' FILEDATE=`test -f ${VACATION_DIR:-.}/\'.vacation.' . $address . '\' && ' + . $this->_params['ls'] . ' -lcn --time-style=+%s ${VACATION_DIR:-.}/\'.vacation.' . $address . '\' | ' + . 'awk \'{ print $6 + (' . $days * 86400 . ') }\'`'; + $this->_action[] = ' DATE=`' . $this->_params['date'] . ' +%s`'; + $this->_action[] = ' DUMMY=`test -f ${VACATION_DIR:-.}/\'.vacation.' . $address . '\' && ' + . 'test $FILEDATE -le $DATE && ' + . 'rm ${VACATION_DIR:-.}/\'.vacation.' . $address . '\'`'; + if ($timed) { + $this->_action[] = ' START=' . $params['action-value']['start']; + $this->_action[] = ' END=' . $params['action-value']['end']; + } + $this->_action[] = ''; + $this->_action[] = ' :0 h'; + $this->_action[] = ' SUBJECT=| formail -xSubject:'; + $this->_action[] = ''; + $this->_action[] = ' :0 Whc: ${VACATION_DIR:-.}/vacation.lock'; + if ($timed) { + $this->_action[] = ' * ? test $DATE -gt $START && test $END -gt $DATE'; + $this->_action[] = ' {'; + $this->_action[] = ' :0 Whc'; + } + $this->_action[] = ' * ^TO_' . $address; + $this->_action[] = ' * !^X-Loop: ' . $address; + $this->_action[] = ' * !^X-Spam-Flag: YES'; + if (count($params['action-value']['excludes']) > 0) { + foreach ($params['action-value']['excludes'] as $exclude) { + if (!empty($exclude)) { + $this->_action[] = ' * !^From.*' . $exclude; + } + } + } + if ($params['action-value']['ignorelist']) { + $this->_action[] = ' * !^FROM_DAEMON'; + } + $this->_action[] = ' | formail -rD 8192 ${VACATION_DIR:-.}/.vacation.' . $address; + $this->_action[] = ' :0 ehc'; + $this->_action[] = ' | (formail -rI"Precedence: junk" \\'; + $this->_action[] = ' -a"From: <' . $address . '>" \\'; + $this->_action[] = ' -A"X-Loop: ' . $address . '" \\'; + if (Horde_Mime::is8bit($params['action-value']['reason'])) { + $this->_action[] = ' -i"Subject: ' . Horde_Mime::encode($params['action-value']['subject'] . ' (Re: $SUBJECT)', Horde_Nls::getCharset()) . '" \\'; + $this->_action[] = ' -i"Content-Transfer-Encoding: quoted-printable" \\'; + $this->_action[] = ' -i"Content-Type: text/plain; charset=' . Horde_Nls::getCharset() . '" ; \\'; + $reason = Horde_Mime::quotedPrintableEncode($params['action-value']['reason'], "\n"); + } else { + $this->_action[] = ' -i"Subject: ' . Horde_Mime::encode($params['action-value']['subject'] . ' (Re: $SUBJECT)', Horde_Nls::getCharset()) . '" ; \\'; + $reason = $params['action-value']['reason']; + } + $reason = addcslashes($reason, "\\\n\r\t\"`"); + $this->_action[] = ' ' . $this->_params['echo'] . ' -e "' . $reason . '" \\'; + $this->_action[] = ' ) | $SENDMAIL -f' . $address . ' -oi -t'; + $this->_action[] = ''; + $this->_action[] = ' :0'; + $this->_action[] = ' /dev/null'; + if ($timed) { + $this->_action[] = ' }'; + } + } + } + $this->_action[] = '}'; + break; + + case Ingo_Storage::ACTION_FORWARD: + /* Make sure that we prevent mail loops using 3 methods. + * + * First, we call sendmail -f to set the envelope sender to be the + * same as the original sender, so bounces will go to the original + * sender rather than to us. This unfortunately triggers lots of + * Authentication-Warning: messages in sendmail's logs. + * + * Second, add an X-Loop header, to handle the case where the + * address we forward to forwards back to us. + * + * Third, don't forward mailer daemon messages (i.e., bounces). + * Method 1 above should make this redundant, unless we're sending + * mail from this account and have a bad forward-to account. + * + * Get the from address, saving a call to formail if possible. + * The procmail code for doing this is borrowed from the + * Procmail Library Project, http://pm-lib.sourceforge.net/. + * The Ingo project has the permission to use Procmail Library code + * under Apache licence v 1.x or any later version. + * Permission obtained 2006-04-04 from Author Jari Aalto. */ + $this->_action[] = '{'; + $this->_action[] = ' :0 '; + $this->_action[] = ' *$ ! ^From *\/[^ ]+'; + $this->_action[] = ' *$ ! ^Sender: *\/[^ ]+'; + $this->_action[] = ' *$ ! ^From: *\/[^ ]+'; + $this->_action[] = ' *$ ! ^Reply-to: *\/[^ ]+'; + $this->_action[] = ' {'; + $this->_action[] = ' OUTPUT = `formail -zxFrom:`'; + $this->_action[] = ' }'; + $this->_action[] = ' :0 E'; + $this->_action[] = ' {'; + $this->_action[] = ' OUTPUT = $MATCH'; + $this->_action[] = ' }'; + $this->_action[] = ''; + + /* Forward to each address on our list. */ + foreach ($params['action-value'] as $address) { + if (!empty($address)) { + $this->_action[] = ' :0 c'; + $this->_action[] = ' * !^FROM_MAILER'; + $this->_action[] = ' * !^X-Loop: to-' . $address; + $this->_action[] = ' | formail -A"X-Loop: to-' . $address . '" | $SENDMAIL -oi -f $OUTPUT ' . $address; + } + } + + /* In case of mail loop or bounce, store a copy locally. Note + * that if we forward to more than one address, only a mail loop + * on the last address will cause a local copy to be saved. TODO: + * The next two lines are redundant (and create an extra copy of + * the message) if "Keep a copy of messages in this account" is + * checked. */ + $this->_action[] = ' :0 E' . (isset($this->_params['delivery_agent']) ? 'w' : ''); + if (isset($this->_params['delivery_agent'])) { + $this->_action[] = isset($this->_params['delivery_mailbox_prefix']) ? + ' | ' . $this->_params['delivery_agent'] . ' ' . $this->_params['delivery_mailbox_prefix'] . '$DEFAULT' : + ' | ' . $this->_params['delivery_agent'] . ' $DEFAULT'; + } else { + $this->_action[] = ' $DEFAULT'; + } + $this->_action[] = ' :0 '; + $this->_action[] = ' /dev/null'; + $this->_action[] = '}'; + break; + + default: + $this->_valid = false; + break; + } + } + + /** + * Adds a flag to the recipe. + * + * @param string $flag String of flags to append to the current flags. + */ + function addFlag($flag) + { + $this->_flags .= $flag; + } + + /** + * Adds a condition to the recipe. + * + * @param array $condition Array of parameters. Required keys are 'field' + * and 'value'. 'case' is an optional key. + */ + function addCondition($condition = array()) + { + $flag = !empty($condition['case']) ? 'D' : ''; + $match = isset($condition['match']) ? $condition['match'] : null; + $string = ''; + $prefix = ''; + + switch ($condition['field']) { + case 'Destination': + $string = '^TO_'; + break; + + case 'Body': + $flag .= 'B'; + break; + + default: + // convert 'field' to PCRE pattern matching + if (strpos($condition['field'], ',') == false) { + $string = '^' . $condition['field'] . ':'; + } else { + $string .= '/^(' . str_replace(',', '|', $condition['field']) . '):'; + } + $prefix = ' '; + } + + $reverseCondition = false; + switch ($match) { + case 'regex': + $string .= $prefix . $condition['value']; + break; + + case 'address': + $string .= '(.*\<)?' . quotemeta($condition['value']); + break; + + case 'not begins with': + $reverseCondition = true; + // fall through + case 'begins with': + $string .= $prefix . quotemeta($condition['value']); + break; + + case 'not ends with': + $reverseCondition = true; + // fall through + case 'ends with': + $string .= '.*' . quotemeta($condition['value']) . '$'; + break; + + case 'not contain': + $reverseCondition = true; + // fall through + case 'contains': + default: + $string .= '.*' . quotemeta($condition['value']); + break; + } + + $this->_conditions[] = array('condition' => ($reverseCondition ? '* !' : '* ') . $string, + 'flags' => $flag); + } + + /** + * Generates procmail code to represent the recipe. + * + * @return string Procmail code to represent the recipe. + */ + function generate() + { + $nest = 0; + $prefix = ''; + $text = array(); + + if (!$this->_valid) { + return ''; + } + + // Set the global flags for the whole rule, each condition + // will add its own (such as Body or Case Sensitive) + $global = $this->_flags; + if (isset($this->_conditions[0])) { + $global .= $this->_conditions[0]['flags']; + } + $text[] = ':0 ' . $global . (isset($this->_params['delivery_agent']) ? 'w' : ''); + foreach ($this->_conditions as $condition) { + if ($nest > 0) { + $text[] = str_repeat(' ', $nest - 1) . '{'; + $text[] = str_repeat(' ', $nest) . ':0 ' . $condition['flags']; + $text[] = str_repeat(' ', $nest) . $condition['condition']; + } else { + $text[] = $condition['condition']; + } + $nest++; + } + + if (--$nest > 0) { + $prefix = str_repeat(' ', $nest); + } + foreach ($this->_action as $val) { + $text[] = $prefix . $val; + } + + for ($i = $nest; $i > 0; $i--) { + $text[] = str_repeat(' ', $i - 1) . '}'; + } + + if ($this->_disable) { + $code = ''; + foreach ($text as $val) { + $comment = new Procmail_Comment($val); + $code .= $comment->generate() . "\n"; + } + return $code . "\n"; + } else { + return implode("\n", $text) . "\n"; + } + } + + /** + * Returns a procmail-ready mailbox path, converting IMAP folder + * pathname conventions as necessary. + * + * @param string $folder The IMAP folder name. + * + * @return string The procmail mailbox path. + */ + function procmailPath($folder) + { + /* NOTE: '$DEFAULT' here is a literal, not a PHP variable. */ + if (isset($this->_params) && + ($this->_params['path_style'] == 'maildir')) { + if (empty($folder) || ($folder == 'INBOX')) { + return '$DEFAULT'; + } + if (substr($folder, 0, 6) == 'INBOX.') { + $folder = substr($folder, 6); + } + return '"$DEFAULT/.' . escapeshellcmd($folder) . '/"'; + } else { + if (empty($folder) || ($folder == 'INBOX')) { + return '$DEFAULT'; + } + return str_replace(' ', '\ ', escapeshellcmd($folder)); + } + } + +} + +/** + * The Procmail_Variable:: class represents a Procmail variable. + * + * @author Michael Slusarz + * @since Ingo 1.0 + * @package Ingo + */ +class Procmail_Variable { + + var $_name; + var $_value; + + /** + * Constructs a new procmail variable. + * + * @param array $params Array of parameters. Expected fields are 'name' + * and 'value'. + */ + function Procmail_Variable($params = array()) + { + $this->_name = $params['name']; + $this->_value = $params['value']; + } + + /** + * Generates procmail code to represent the variable. + * + * @return string Procmail code to represent the variable. + */ + function generate() + { + return $this->_name . '=' . $this->_value . "\n"; + } + +} diff --git a/ingo/lib/Script/Sieve.php b/ingo/lib/Script/Sieve.php new file mode 100644 index 000000000..6dd2fd90a --- /dev/null +++ b/ingo/lib/Script/Sieve.php @@ -0,0 +1,2975 @@ + + * @package Ingo + */ +class Ingo_Script_Sieve extends Ingo_Script { + + /** + * The list of actions allowed (implemented) for this driver. + * + * @var array + */ + var $_actions = array( + Ingo_Storage::ACTION_KEEP, + Ingo_Storage::ACTION_MOVE, + Ingo_Storage::ACTION_DISCARD, + Ingo_Storage::ACTION_REDIRECT, + Ingo_Storage::ACTION_REDIRECTKEEP, + Ingo_Storage::ACTION_MOVEKEEP, + Ingo_Storage::ACTION_REJECT, + Ingo_Storage::ACTION_FLAGONLY, + Ingo_Storage::ACTION_NOTIFY + ); + + /** + * The categories of filtering allowed. + * + * @var array + */ + var $_categories = array( + Ingo_Storage::ACTION_BLACKLIST, + Ingo_Storage::ACTION_WHITELIST, + Ingo_Storage::ACTION_VACATION, + Ingo_Storage::ACTION_FORWARD, + Ingo_Storage::ACTION_SPAM + ); + + /** + * The list of tests allowed (implemented) for this driver. + * + * @var array + */ + var $_tests = array( + 'contains', 'not contain', 'is', 'not is', 'begins with', + 'not begins with', 'ends with', 'not ends with', 'exists', 'not exist', + 'less than', 'less than or equal to', 'equal', 'not equal', + 'greater than', 'greater than or equal to', 'regex', 'matches', + 'not matches' + ); + + /** + * The types of tests allowed (implemented) for this driver. + * + * @var array + */ + var $_types = array( + Ingo_Storage::TYPE_HEADER, + Ingo_Storage::TYPE_SIZE, + Ingo_Storage::TYPE_BODY + ); + + /** + * Can tests be case sensitive? + * + * @var boolean + */ + var $_casesensitive = true; + + /** + * Does the driver support setting IMAP flags? + * + * @var boolean + */ + var $_supportIMAPFlags = true; + + /** + * Does the driver support the stop-script option? + * + * @var boolean + */ + var $_supportStopScript = true; + + /** + * Does the driver require a script file to be generated? + * + * @var boolean + */ + var $_scriptfile = true; + + /** + * The blocks that make up the code. + * + * @var array + */ + var $_blocks = array(); + + /** + * The blocks that have to appear at the end of the code. + * + * @var array + */ + var $_endBlocks = array(); + + /** + * Returns a script previously generated with generate(). + * + * @return string The Sieve script. + */ + function toCode() + { + $date_format = $GLOBALS['prefs']->getValue('date_format'); + // %R and %r don't work on Windows, but who runs a Sieve backend on a + // Windows server? + $time_format = $GLOBALS['prefs']->getValue('twentyFour') ? '%R' : '%r'; + $code = "# Sieve Filter\n# " + . _("Generated by Ingo (http://www.horde.org/ingo/)") . ' (' + . trim(strftime($date_format . ', ' . $time_format)) + . ")\n\n"; + $code = Horde_String::convertCharset($code, Horde_Nls::getCharset(), 'UTF-8'); + $requires = $this->requires(); + + if (count($requires) > 1) { + $stringlist = ''; + foreach ($this->requires() as $require) { + $stringlist .= (empty($stringlist)) ? '"' : ', "'; + $stringlist .= $require . '"'; + } + $code .= 'require [' . $stringlist . '];' . "\n\n"; + } elseif (count($requires) == 1) { + foreach ($this->requires() as $require) { + $code .= 'require "' . $require . '";' . "\n\n"; + } + } + + foreach ($this->_blocks as $block) { + $code .= $block->toCode() . "\n"; + } + + return rtrim($code) . "\n"; + } + + /** + * Escape a string according to Sieve RFC 3028 [2.4.2]. + * + * @param string $string The string to escape. + * @param boolean $regexmode Is the escaped string a regex value? + * Defaults to no. + * + * @return string The escaped string. + */ + function escapeString($string, $regexmode = false) + { + /* Remove any backslashes in front of commas. */ + $string = str_replace('\,', ',', $string); + + if ($regexmode) { + return str_replace('"', addslashes('"'), $string); + } else { + return str_replace(array('\\', '"'), array(addslashes('\\'), addslashes('"')), $string); + } + } + + /** + * Checks if all rules are valid. + * + * @return boolean|string True if all rules are valid, an error message + * otherwise. + */ + function check() + { + foreach ($this->_blocks as $block) { + $res = $block->check(); + if ($res !== true) { + return $res; + } + } + + return true; + } + + /** + * Returns a list of sieve extensions required for this rule and any + * sub-rules. + * + * @return array A Sieve extension list. + */ + function requires() + { + $requires = array(); + foreach ($this->_blocks as $block) { + $requires = array_merge($requires, $block->requires()); + } + + return array_unique($requires); + } + + /** + * Adds all blocks necessary for the forward rule. + */ + function _addForwardBlocks() + { + if (!$this->_validRule(Ingo_Storage::ACTION_FORWARD)) { + return; + } + + $forward = &$GLOBALS['ingo_storage']->retrieve(Ingo_Storage::ACTION_FORWARD); + $fwd_addr = $forward->getForwardAddresses(); + if (empty($fwd_addr)) { + return; + } + + $action = array(); + foreach ($fwd_addr as $addr) { + $addr = trim($addr); + if (!empty($addr)) { + $action[] = new Sieve_Action_Redirect(array('address' => $addr)); + } + } + + if (count($action)) { + if($forward->getForwardKeep()) { + $this->_endBlocks[] = new Sieve_Comment(_("Forward Keep Action")); + $if = new Sieve_If(new Sieve_Test_True()); + $if->setActions(array(new Sieve_Action_Keep(), + new Sieve_Action_Stop())); + $this->_endBlocks[] = $if; + } else { + $action[] = new Sieve_Action_Stop(); + } + } + + $this->_blocks[] = new Sieve_Comment(_("Forwards")); + + $test = new Sieve_Test_True(); + $if = new Sieve_If($test); + $if->setActions($action); + $this->_blocks[] = $if; + } + + /** + * Adds all blocks necessary for the blacklist rule. + */ + function _addBlacklistBlocks() + { + if (!$this->_validRule(Ingo_Storage::ACTION_BLACKLIST)) { + return; + } + + $blacklist = &$GLOBALS['ingo_storage']->retrieve(Ingo_Storage::ACTION_BLACKLIST); + $bl_addr = $blacklist->getBlacklist(); + $folder = $blacklist->getBlacklistFolder(); + if (empty($bl_addr)) { + return; + } + + $action = array(); + if (empty($folder)) { + $action[] = new Sieve_Action_Discard(); + } elseif ($folder == INGO_BLACKLIST_MARKER) { + $action[] = new Sieve_Action_Addflag(array('flags' => Ingo_Storage::FLAG_DELETED)); + $action[] = new Sieve_Action_Keep(); + $action[] = new Sieve_Action_Removeflag(array('flags' => Ingo_Storage::FLAG_DELETED)); + } else { + $action[] = new Sieve_Action_Fileinto(array('folder' => $folder)); + } + + $action[] = new Sieve_Action_Stop(); + + $this->_blocks[] = new Sieve_Comment(_("Blacklisted Addresses")); + + /* Split the test up to only do 5 addresses at a time. */ + $temp = array(); + $wildcards = array(); + foreach ($bl_addr as $address) { + if (!empty($address)) { + if ((strstr($address, '*') !== false) || + (strstr($address, '?') !== false)) { + $wildcards[] = $address; + } else { + $temp[] = $address; + } + } + if (count($temp) == 5) { + $test = new Sieve_Test_Address(array('headers' => "From\nSender\nResent-From", 'addresses' => implode("\n", $temp))); + $if = new Sieve_If($test); + $if->setActions($action); + $this->_blocks[] = $if; + $temp = array(); + } + if (count($wildcards) == 5) { + $test = new Sieve_Test_Address(array('headers' => "From\nSender\nResent-From", 'match-type' => ':matches', 'addresses' => implode("\n", $wildcards))); + $if = new Sieve_If($test); + $if->setActions($action); + $this->_blocks[] = $if; + $wildcards = array(); + } + } + + if ($temp) { + $test = new Sieve_Test_Address(array('headers' => "From\nSender\nResent-From", 'addresses' => implode("\n", $temp))); + $if = new Sieve_If($test); + $if->setActions($action); + $this->_blocks[] = $if; + } + + if ($wildcards) { + $test = new Sieve_Test_Address(array('headers' => "From\nSender\nResent-From", 'match-type' => ':matches', 'addresses' => implode("\n", $wildcards))); + $if = new Sieve_If($test); + $if->setActions($action); + $this->_blocks[] = $if; + } + } + + /** + * Adds all blocks necessary for the whitelist rule. + */ + function _addWhitelistBlocks() + { + if (!$this->_validRule(Ingo_Storage::ACTION_WHITELIST)) { + return; + } + + $whitelist = &$GLOBALS['ingo_storage']->retrieve(Ingo_Storage::ACTION_WHITELIST); + $wl_addr = $whitelist->getWhitelist(); + if (empty($wl_addr)) { + return; + } + + $this->_blocks[] = new Sieve_Comment(_("Whitelisted Addresses")); + + $action = array(new Sieve_Action_Keep(), new Sieve_Action_Stop()); + $test = new Sieve_Test_Address(array('headers' => "From\nSender\nResent-From", 'addresses' => implode("\n", $wl_addr))); + $if = new Sieve_If($test); + $if->setActions($action); + $this->_blocks[] = $if; + } + + /** + * Adds all blocks necessary for the vacation rule. + */ + function _addVacationBlocks() + { + if (!$this->_validRule(Ingo_Storage::ACTION_VACATION)) { + return; + } + + $vacation = &$GLOBALS['ingo_storage']->retrieve(Ingo_Storage::ACTION_VACATION); + $vacation_addr = $vacation->getVacationAddresses(); + if (!count($vacation_addr)) { + return; + } + + $vals = array( + 'subject' => Horde_String::convertCharset($vacation->getVacationSubject(), Horde_Nls::getCharset(), 'UTF-8'), + 'days' => $vacation->getVacationDays(), + 'addresses' => $vacation_addr, + 'start' => $vacation->getVacationStart(), + 'start_year' => $vacation->getVacationStartYear(), + 'start_month' => $vacation->getVacationStartMonth(), + 'start_day' => $vacation->getVacationStartDay(), + 'end' => $vacation->getVacationEnd(), + 'end_year' => $vacation->getVacationEndYear(), + 'end_month' => $vacation->getVacationEndMonth(), + 'end_day' => $vacation->getVacationEndDay(), + 'reason' => Horde_String::convertCharset($vacation->getVacationReason(), Horde_Nls::getCharset(), 'UTF-8'), + ); + + $action = $tests = array(); + $action[] = new Sieve_Action_Vacation($vals); + + if ($vacation->getVacationIgnorelist()) { + $mime_headers = new Horde_Mime_Headers(); + $headers = $mime_headers->listHeaders(); + $headers['Mailing-List'] = null; + $tmp = new Sieve_Test_Exists(array('headers' => implode("\n", array_keys($headers)))); + $tests[] = new Sieve_Test_Not($tmp); + $vals = array('headers' => 'Precedence', + 'match-type' => ':is', + 'strings' => "list\nbulk\njunk", + 'comparator' => 'i;ascii-casemap'); + $tmp = new Sieve_Test_Header($vals); + $tests[] = new Sieve_Test_Not($tmp); + $vals = array('headers' => 'To', + 'match-type' => ':matches', + 'strings' => 'Multiple recipients of*', + 'comparator' => 'i;ascii-casemap'); + $tmp = new Sieve_Test_Header($vals); + $tests[] = new Sieve_Test_Not($tmp); + } + + $addrs = array(); + foreach ($vacation->getVacationExcludes() as $addr) { + $addr = trim($addr); + if (!empty($addr)) { + $addrs[] = $addr; + } + } + + if ($addrs) { + $tmp = new Sieve_Test_Address(array('headers' => "From\nSender\nResent-From", 'addresses' => implode("\n", $addrs))); + $tests[] = new Sieve_Test_Not($tmp); + } + + $this->_blocks[] = new Sieve_Comment(_("Vacation")); + + if ($tests) { + $test = new Sieve_Test_Allof($tests); + $if = new Sieve_If($test); + $if->setActions($action); + $this->_blocks[] = $if; + } else { + $this->_blocks[] = $action[0]; + } + } + + /** + * Adds all blocks necessary for the spam rule. + */ + function _addSpamBlocks() + { + if (!$this->_validRule(Ingo_Storage::ACTION_SPAM)) { + return; + } + + $spam = &$GLOBALS['ingo_storage']->retrieve(Ingo_Storage::ACTION_SPAM); + if ($spam === false) { + return; + } + + $this->_blocks[] = new Sieve_Comment(_("Spam Filter")); + + $actions = array(); + $actions[] = new Sieve_Action_Fileinto(array( + 'folder' => $spam->getSpamFolder() + )); + + if ($GLOBALS['conf']['spam']['compare'] == 'numeric') { + $vals = array( + 'headers' => $GLOBALS['conf']['spam']['header'], + 'comparison' => 'ge', + 'value' => $spam->getSpamLevel(), + ); + $test = new Sieve_Test_Relational($vals); + } elseif ($GLOBALS['conf']['spam']['compare'] == 'string') { + $vals = array( + 'headers' => $GLOBALS['conf']['spam']['header'], + 'match-type' => ':contains', + 'strings' => str_repeat($GLOBALS['conf']['spam']['char'], + $spam->getSpamLevel()), + 'comparator' => 'i;ascii-casemap', + ); + $test = new Sieve_Test_Header($vals); + } + + $actions[] = new Sieve_Action_Stop(); + + $if = new Sieve_If($test); + $if->setActions($actions); + $this->_blocks[] = $if; + } + + /** + * Generates the Sieve script to do the filtering specified in + * the rules. + * + * @return string The Sieve script. + */ + function generate() + { + global $ingo_storage; + + $filters = &$ingo_storage->retrieve(Ingo_Storage::ACTION_FILTERS); + foreach ($filters->getFilterList() as $filter) { + /* Check to make sure this is a valid rule and that the rule + is not disabled. */ + if (!$this->_validRule($filter['action']) || + !empty($filter['disable'])) { + continue; + } + + $action = array(); + switch ($filter['action']) { + case Ingo_Storage::ACTION_KEEP: + if (!empty($filter['flags'])) { + $action[] = new Sieve_Action_Addflag(array('flags' => $filter['flags'])); + } + + $action[] = new Sieve_Action_Keep(); + + if (!empty($filter['flags'])) { + $action[] = new Sieve_Action_RemoveFlag(array('flags' => $filter['flags'])); + } + break; + + case Ingo_Storage::ACTION_DISCARD: + $action[] = new Sieve_Action_Discard(); + break; + + case Ingo_Storage::ACTION_MOVE: + if (!empty($filter['flags'])) { + $action[] = new Sieve_Action_Addflag(array('flags' => $filter['flags'])); + } + + $action[] = new Sieve_Action_Fileinto(array('folder' => $filter['action-value'])); + + if (!empty($filter['flags'])) { + $action[] = new Sieve_Action_RemoveFlag(array('flags' => $filter['flags'])); + } + break; + + case Ingo_Storage::ACTION_REJECT: + $action[] = new Sieve_Action_Reject(array('reason' => $filter['action-value'])); + break; + + case Ingo_Storage::ACTION_REDIRECT: + $action[] = new Sieve_Action_Redirect(array('address' => $filter['action-value'])); + break; + + case Ingo_Storage::ACTION_REDIRECTKEEP: + if (!empty($filter['flags'])) { + $action[] = new Sieve_Action_Addflag(array('flags' => $filter['flags'])); + } + + $action[] = new Sieve_Action_Redirect(array('address' => $filter['action-value'])); + $action[] = new Sieve_Action_Keep(); + + if (!empty($filter['flags'])) { + $action[] = new Sieve_Action_RemoveFlag(array('flags' => $filter['flags'])); + } + break; + + case Ingo_Storage::ACTION_MOVEKEEP: + if (!empty($filter['flags'])) { + $action[] = new Sieve_Action_Addflag(array('flags' => $filter['flags'])); + } + + $action[] = new Sieve_Action_Keep(); + $action[] = new Sieve_Action_Fileinto(array('folder' => $filter['action-value'])); + + if (!empty($filter['flags'])) { + $action[] = new Sieve_Action_RemoveFlag(array('flags' => $filter['flags'])); + } + break; + + case Ingo_Storage::ACTION_FLAGONLY: + if (!empty($filter['flags'])) { + $action[] = new Sieve_Action_Addflag(array('flags' => $filter['flags'])); + } + break; + + case Ingo_Storage::ACTION_NOTIFY: + $action[] = new Sieve_Action_Notify(array('address' => $filter['action-value'], 'name' => $filter['name'])); + break; + + case Ingo_Storage::ACTION_WHITELIST: + $this->_addWhitelistBlocks(); + continue 2; + + case Ingo_Storage::ACTION_BLACKLIST: + $this->_addBlacklistBlocks(); + continue 2; + + case Ingo_Storage::ACTION_VACATION: + $this->_addVacationBlocks(); + continue 2; + + case Ingo_Storage::ACTION_FORWARD: + $this->_addForwardBlocks(); + continue 2; + + case Ingo_Storage::ACTION_SPAM: + $this->_addSpamBlocks(); + continue 2; + } + + $this->_blocks[] = new Sieve_Comment($filter['name']); + + if ($filter['stop']) { + $action[] = new Sieve_Action_Stop(); + } + + $test = new Sieve_Test(); + if ($filter['combine'] == Ingo_Storage::COMBINE_ANY) { + $test = new Sieve_Test_Anyof(); + } else { + $test = new Sieve_Test_Allof(); + } + + foreach ($filter['conditions'] as $condition) { + $tmp = ''; + switch ($condition['match']) { + case 'equal': + $tmp = new Sieve_Test_Relational(array('comparison' => 'eq', 'headers' => $condition['field'], 'value' => $condition['value'])); + $test->addTest($tmp); + break; + + case 'not equal': + $tmp = new Sieve_Test_Relational(array('comparison' => 'ne', 'headers' => $condition['field'], 'value' => $condition['value'])); + $test->addTest($tmp); + break; + + case 'less than': + if ($condition['field'] == 'Size') { + /* Message Size Test. */ + $tmp = new Sieve_Test_Size(array('comparison' => ':under', 'size' => $condition['value'])); + } else { + /* Relational Test. */ + $tmp = new Sieve_Test_Relational(array('comparison' => 'lt', 'headers' => $condition['field'], 'value' => $condition['value'])); + } + $test->addTest($tmp); + break; + + case 'less than or equal to': + $tmp = new Sieve_Test_Relational(array('comparison' => 'le', 'headers' => $condition['field'], 'value' => $condition['value'])); + $test->addTest($tmp); + break; + + case 'greater than': + if ($condition['field'] == 'Size') { + /* Message Size Test. */ + $tmp = new Sieve_Test_Size(array('comparison' => ':over', 'size' => $condition['value'])); + } else { + /* Relational Test. */ + $tmp = new Sieve_Test_Relational(array('comparison' => 'gt', 'headers' => $condition['field'], 'value' => $condition['value'])); + } + $test->addTest($tmp); + break; + + case 'greater than or equal to': + $tmp = new Sieve_Test_Relational(array('comparison' => 'ge', 'headers' => $condition['field'], 'value' => $condition['value'])); + $test->addTest($tmp); + break; + + case 'exists': + $tmp = new Sieve_Test_Exists(array('headers' => $condition['field'])); + $test->addTest($tmp); + break; + + case 'not exist': + $tmp = new Sieve_Test_Exists(array('headers' => $condition['field'])); + $test->addTest(new Sieve_Test_Not($tmp)); + break; + + case 'contains': + case 'not contain': + case 'is': + case 'not is': + case 'begins with': + case 'not begins with': + case 'ends with': + case 'not ends with': + case 'regex': + case 'matches': + case 'not matches': + $comparator = (isset($condition['case']) && + $condition['case']) + ? 'i;octet' + : 'i;ascii-casemap'; + $vals = array('headers' => preg_replace('/(.)(? $comparator); + $use_address_test = false; + + if ($condition['match'] != 'regex') { + $condition['value'] = preg_replace('/(.)(?addTest($tmp); + break; + + case 'not contain': + $vals['match-type'] = ':contains'; + if ($use_address_test) { + $tmp = new Sieve_Test_Address($vals); + } elseif ($condition['field'] == 'Body') { + $tmp = new Sieve_Test_Body($vals); + } else { + $tmp = new Sieve_Test_Header($vals); + } + $test->addTest(new Sieve_Test_Not($tmp)); + break; + + case 'is': + $vals['match-type'] = ':is'; + if ($use_address_test) { + $tmp = new Sieve_Test_Address($vals); + } elseif ($condition['field'] == 'Body') { + $tmp = new Sieve_Test_Body($vals); + } else { + $tmp = new Sieve_Test_Header($vals); + } + $test->addTest($tmp); + break; + + case 'not is': + $vals['match-type'] = ':is'; + if ($use_address_test) { + $tmp = new Sieve_Test_Address($vals); + } elseif ($condition['field'] == 'Body') { + $tmp = new Sieve_Test_Body($vals); + } else { + $tmp = new Sieve_Test_Header($vals); + } + $test->addTest(new Sieve_Test_Not($tmp)); + break; + + case 'begins with': + $vals['match-type'] = ':matches'; + if ($use_address_test) { + $add_arr = preg_split('(\r\n|\n|\r)', $vals['addresses']); + if (count($add_arr) > 1) { + foreach ($add_arr as $k => $v) { + $add_arr[$k] = $v . '*'; + } + $vals['addresses'] = implode("\r\n", $add_arr); + } else { + $vals['addresses'] .= '*'; + } + $tmp = new Sieve_Test_Address($vals); + } else { + $add_arr = preg_split('(\r\n|\n|\r)', $vals['strings']); + if (count($add_arr) > 1) { + foreach ($add_arr as $k => $v) { + $add_arr[$k] = $v . '*'; + } + $vals['strings'] = implode("\r\n", $add_arr); + } else { + $vals['strings'] .= '*'; + } + if ($condition['field'] == 'Body') { + $tmp = new Sieve_Test_Body($vals); + } else { + $tmp = new Sieve_Test_Header($vals); + } + } + $test->addTest($tmp); + break; + + case 'not begins with': + $vals['match-type'] = ':matches'; + if ($use_address_test) { + $add_arr = preg_split('(\r\n|\n|\r)', $vals['addresses']); + if (count($add_arr) > 1) { + foreach ($add_arr as $k => $v) { + $add_arr[$k] = $v . '*'; + } + $vals['addresses'] = implode("\r\n", $add_arr); + } else { + $vals['addresses'] .= '*'; + } + $tmp = new Sieve_Test_Address($vals); + } else { + $add_arr = preg_split('(\r\n|\n|\r)', $vals['strings']); + if (count($add_arr) > 1) { + foreach ($add_arr as $k => $v) { + $add_arr[$k] = $v . '*'; + } + $vals['strings'] = implode("\r\n", $add_arr); + } else { + $vals['strings'] .= '*'; + } + if ($condition['field'] == 'Body') { + $tmp = new Sieve_Test_Body($vals); + } else { + $tmp = new Sieve_Test_Header($vals); + } + } + $test->addTest(new Sieve_Test_Not($tmp)); + break; + + case 'ends with': + $vals['match-type'] = ':matches'; + if ($use_address_test) { + $add_arr = preg_split('(\r\n|\n|\r)', $vals['addresses']); + if (count($add_arr) > 1) { + foreach ($add_arr as $k => $v) { + $add_arr[$k] = '*' . $v; + } + $vals['addresses'] = implode("\r\n", $add_arr); + } else { + $vals['addresses'] = '*' . $vals['addresses']; + } + $tmp = new Sieve_Test_Address($vals); + } else { + $add_arr = preg_split('(\r\n|\n|\r)', $vals['strings']); + if (count($add_arr) > 1) { + foreach ($add_arr as $k => $v) { + $add_arr[$k] = '*' . $v; + } + $vals['strings'] = implode("\r\n", $add_arr); + } else { + $vals['strings'] = '*' . $vals['strings']; + } + if ($condition['field'] == 'Body') { + $tmp = new Sieve_Test_Body($vals); + } else { + $tmp = new Sieve_Test_Header($vals); + } + } + $test->addTest($tmp); + break; + + case 'not ends with': + $vals['match-type'] = ':matches'; + if ($use_address_test) { + $add_arr = preg_split('(\r\n|\n|\r)', $vals['addresses']); + if (count($add_arr) > 1) { + foreach ($add_arr as $k => $v) { + $add_arr[$k] = '*' . $v; + } + $vals['addresses'] = implode("\r\n", $add_arr); + } else { + $vals['addresses'] = '*' . $vals['addresses']; + } + $tmp = new Sieve_Test_Address($vals); + } else { + $add_arr = preg_split('(\r\n|\n|\r)', $vals['strings']); + if (count($add_arr) > 1) { + foreach ($add_arr as $k => $v) { + $add_arr[$k] = '*' . $v; + } + $vals['strings'] = implode("\r\n", $add_arr); + } else { + $vals['strings'] = '*' . $vals['strings']; + } + if ($condition['field'] == 'Body') { + $tmp = new Sieve_Test_Body($vals); + } else { + $tmp = new Sieve_Test_Header($vals); + } + } + $test->addTest(new Sieve_Test_Not($tmp)); + break; + + case 'regex': + $vals['match-type'] = ':regex'; + if ($use_address_test) { + $tmp = new Sieve_Test_Address($vals); + } elseif ($condition['field'] == 'Body') { + $tmp = new Sieve_Test_Body($vals); + } else { + $tmp = new Sieve_Test_Header($vals); + } + $test->addTest($tmp); + break; + + case 'matches': + $vals['match-type'] = ':matches'; + if ($use_address_test) { + $tmp = new Sieve_Test_Address($vals); + } elseif ($condition['field'] == 'Body') { + $tmp = new Sieve_Test_Body($vals); + } else { + $tmp = new Sieve_Test_Header($vals); + } + $test->addTest($tmp); + break; + + case 'not matches': + $vals['match-type'] = ':matches'; + if ($use_address_test) { + $tmp = new Sieve_Test_Address($vals); + } elseif ($condition['field'] == 'Body') { + $tmp = new Sieve_Test_Body($vals); + } else { + $tmp = new Sieve_Test_Header($vals); + } + $test->addTest(new Sieve_Test_Not($tmp)); + break; + } + } + } + + $if = new Sieve_If($test); + $if->setActions($action); + $this->_blocks[] = $if; + } + + /* Add blocks that have to go to the end. */ + foreach ($this->_endBlocks as $block) { + $this->_blocks[] = $block; + } + + return $this->toCode(); + } + +} + +/** + * The Sieve_If class represents a Sieve If Statement + * + * @author Mike Cochrane + * @since Ingo 0.1 + * @package Ingo + */ +class Sieve_If { + + /** + * The Sieve_Test object for the if test. + * + * @var Sieve_Test + */ + var $_test; + + /** + * A list of Sieve_Action objects that go into the if clause. + * + * @var array + */ + var $_actions = array(); + + /** + * A list of Sieve_Elseif objects that create optional elsif clauses. + * + * @var array + */ + var $_elsifs = array(); + + /** + * A Sieve_Else object that creates an optional else clause. + * + * @var Sieve_Else + */ + var $_else; + + /** + * Constructor. + * + * @params Sieve_Test $test A Sieve_Test object. + */ + function Sieve_If($test = null) + { + if (is_null($test)) { + $this->_test = new Sieve_Test_False(); + } else { + $this->_test = $test; + } + + $this->_actions[] = new Sieve_Action_Keep(); + $this->_else = new Sieve_Else(); + } + + function getTest() + { + return $this->_test; + } + + function setTest($test) + { + $this->_test = $test; + } + + function getActions() + { + return $this->_actions; + } + + function setActions($actions) + { + $this->_actions = $actions; + } + + function getElsifs() + { + return $this->_elsifs; + } + + function setElsifs($elsifs) + { + $this->_elsifs = $elsifs; + } + + function addElsif($elsif) + { + $this->_elsifs[] = $elsif; + } + + function getElse() + { + return $this->_else; + } + + function setElse($else) + { + $this->_else = $else; + } + + /** + * Returns a script snippet representing this rule and any sub-rules. + * + * @return string A Sieve script snippet. + */ + function toCode() + { + $code = 'if ' . $this->_test->toCode() . " { \n"; + foreach ($this->_actions as $action) { + $code .= ' ' . $action->toCode() . "\n"; + } + $code .= "} "; + + foreach ($this->_elsifs as $elsif) { + $code .= $elsif->toCode(); + } + + $code .= $this->_else->toCode(); + + return $code . "\n"; + } + + /** + * Checks if all sub-rules are valid. + * + * @return boolean|string True if all rules are valid, an error message + * otherwise. + */ + function check() + { + $res = $this->_test->check(); + if ($res !== true) { + return $res; + } + + foreach ($this->_elsifs as $elsif) { + $res = $elsif->check(); + if ($res !== true) { + return $res; + } + } + + $res = $this->_else->check(); + if ($res !== true) { + return $res; + } + + foreach ($this->_actions as $action) { + $res = $action->check(); + if ($res !== true) { + return $res; + } + } + + return true; + } + + /** + * Returns a list of sieve extensions required for this rule and any + * sub-rules. + * + * @return array A Sieve extension list. + */ + function requires() + { + $requires = array(); + + foreach ($this->_actions as $action) { + $requires = array_merge($requires, $action->requires()); + } + + foreach ($this->_elsifs as $elsif) { + $requires = array_merge($requires, $elsif->requires()); + } + + $requires = array_merge($requires, $this->_test->requires(), $this->_else->requires()); + + return $requires; + } + +} + +/** + * The Sieve_Else class represents a Sieve Else Statement + * + * @author Mike Cochrane + * @since Ingo 0.1 + * @package Ingo + */ +class Sieve_Else { + + /** + * A list of Sieve_Action objects that go into the else clause. + * + * @var array + */ + var $_actions = array(); + + /** + * Constructor. + * + * @params Sieve_Action|array $actions A Sieve_Action object or a list of + * Sieve_Action objects. + */ + function Sieve_Else($actions = null) + { + if (is_array($actions)) { + $this->_actions = $actions; + } elseif (!is_null($actions)) { + $this->_actions[] = $actions; + } + } + + /** + * Returns a script snippet representing this rule and any sub-rules. + * + * @return string A Sieve script snippet. + */ + function toCode() + { + if (count($this->_actions) == 0) { + return ''; + } + + $code = 'else' . " { \n"; + foreach ($this->_actions as $action) { + $code .= ' ' . $action->toCode() . "\n"; + } + $code .= "} "; + + return $code; + } + + function setActions($actions) + { + $this->_actions = $actions; + } + + function getActions() + { + return $this->_actions; + } + + /** + * Checks if all sub-rules are valid. + * + * @return boolean|string True if all rules are valid, an error message + * otherwise. + */ + function check() + { + foreach ($this->_actions as $action) { + $res = $action->check(); + if ($res !== true) { + return $res; + } + } + + return true; + } + + /** + * Returns a list of sieve extensions required for this rule and any + * sub-rules. + * + * @return array A Sieve extension list. + */ + function requires() + { + $requires = array(); + + foreach ($this->_actions as $action) { + $requires = array_merge($requires, $action->requires()); + } + + return $requires; + } + +} + +/** + * The Sieve_Elsif class represents a Sieve Elsif Statement + * + * @author Mike Cochrane + * @since Ingo 0.1 + * @package Ingo + */ +class Sieve_Elsif { + + /** + * The Sieve_Test object for the if test. + * + * @var Sieve_Test + */ + var $_test; + + /** + * A list of Sieve_Action objects that go into the if clause. + * + * @var array + */ + var $_actions = array(); + + /** + * Constructor. + * + * @params Sieve_Test $test A Sieve_Test object. + */ + function Sieve_Elsif($test = null) + { + if (is_null($test)) { + $this->_test = new Sieve_Test_False(); + } else { + $this->_test = $test; + } + $this->_actions[] = new Sieve_Action_Keep(); + } + + function getTest() + { + return $this->_test; + } + + function setTest($test) + { + $this->_test = $test; + } + + function getActions() + { + return $this->_actions; + } + + function setActions($actions) + { + $this->_actions = $actions; + } + + /** + * Returns a script snippet representing this rule and any sub-rules. + * + * @return string A Sieve script snippet. + */ + function toCode() + { + $code = 'elsif ' . $this->_test->toCode() . " { \n"; + foreach ($this->_actions as $action) { + $code .= ' ' . $action->toCode() . "\n"; + } + $code .= "} "; + + return $code; + } + + /** + * Checks if all sub-rules are valid. + * + * @return boolean|string True if all rules are valid, an error message + * otherwise. + */ + function check() + { + $res = $this->_test->check(); + if ($res !== true) { + return $res; + } + + foreach ($this->_actions as $action) { + $res = $action->check(); + if ($res !== true) { + return $res; + } + } + + return true; + } + + /** + * Returns a list of sieve extensions required for this rule and any + * sub-rules. + * + * @return array A Sieve extension list. + */ + function requires() + { + $requires = array(); + + foreach ($this->_actions as $action) { + $requires = array_merge($requires, $action->requires()); + } + + $requires = array_merge($requires, $this->_test->requires()); + + return $requires; + } + +} + +/** + * The Sieve_Test class represents a Sieve Test. + * + * A test is a piece of code that evaluates to true or false. + * + * @author Mike Cochrane + * @since Ingo 0.1 + * @package Ingo + */ +class Sieve_Test { + + /** + * Any necessary test parameters. + * + * @var array + */ + var $_vars = array(); + + /** + * Returns a script snippet representing this rule and any sub-rules. + * + * @return string A Sieve script snippet. + */ + function toCode() + { + return 'toCode() Function Not Implemented in class ' . get_class($this); + } + + /** + * Checks if the rule parameters are valid. + * + * @return boolean|string True if this rule is valid, an error message + * otherwise. + */ + function check() + { + return 'check() Function Not Implemented in class ' . get_class($this); + } + + /** + * Returns a list of sieve extensions required for this rule and any + * sub-rules. + * + * @return array A Sieve extension list. + */ + function requires() + { + return array(); + } + +} + +/** + * The Sieve_Test_True class represents a test that always evaluates to true. + * + * @author Mike Cochrane + * @since Ingo 0.1 + * @package Ingo + */ +class Sieve_Test_True extends Sieve_Test { + + /** + * Returns a script snippet representing this rule and any sub-rules. + * + * @return string A Sieve script snippet. + */ + function toCode() + { + return 'true'; + } + + /** + * Checks if the rule parameters are valid. + * + * @return boolean|string True if this rule is valid, an error message + * otherwise. + */ + function check() + { + return true; + } + +} + +/** + * The Sieve_Test_False class represents a test that always evaluates to + * false. + * + * @author Mike Cochrane + * @since Ingo 0.1 + * @package Ingo + */ +class Sieve_Test_False extends Sieve_Test { + + /** + * Returns a script snippet representing this rule and any sub-rules. + * + * @return string A Sieve script snippet. + */ + function toCode() + { + return 'false'; + } + + /** + * Checks if the rule parameters are valid. + * + * @return boolean|string True if this rule is valid, an error message + * otherwise. + */ + function check() + { + return true; + } + +} + +/** + * The Sieve_Test_Allof class represents a Allof test structure. + * + * Equivalent to a logical AND of all the tests it contains. + * + * @author Mike Cochrane + * @since Ingo 0.1 + * @package Ingo + */ +class Sieve_Test_Allof extends Sieve_Test { + + var $_tests = array(); + + /** + * Constructor. + * + * @params Sieve_Test|array $test A Sieve_Test object or a list of + * Sieve_Test objects. + */ + function Sieve_Test_Allof($test = null) + { + if (is_array($test)) { + $this->_tests = $test; + } elseif (!is_null($test)) { + $this->_tests[] = $test; + } + } + + /** + * Returns a script snippet representing this rule and any sub-rules. + * + * @return string A Sieve script snippet. + */ + function toCode() + { + $code = ''; + if (count($this->_tests) > 1) { + $testlist = ''; + foreach ($this->_tests as $test) { + $testlist .= (empty($testlist)) ? '' : ', '; + $testlist .= trim($test->toCode()); + } + + $code = "allof ( $testlist )"; + } elseif (count($this->_tests) == 1) { + $code = $this->_tests[0]->toCode(); + } else { + return 'true'; + } + return $code; + } + + /** + * Checks if all sub-rules are valid. + * + * @return boolean|string True if all rules are valid, an error message + * otherwise. + */ + function check() + { + foreach ($this->_tests as $test) { + $res = $test->check(); + if ($res !== true) { + return $res; + } + } + + return true; + } + + function addTest($test) + { + $this->_tests[] = $test; + } + + function getTests() + { + return $this->_tests; + } + + /** + * Returns a list of sieve extensions required for this rule and any + * sub-rules. + * + * @return array A Sieve extension list. + */ + function requires() + { + $requires = array(); + + foreach ($this->_tests as $test) { + $requires = array_merge($requires, $test->requires()); + } + + return $requires; + } + +} + +/** + * The Sieve_Test_Anyof class represents a Anyof test structure. + * + * Equivalent to a logical OR of all the tests it contains. + * + * @author Mike Cochrane + * @since Ingo 0.1 + * @package Ingo + */ +class Sieve_Test_Anyof extends Sieve_Test { + + var $_tests = array(); + + /** + * Constructor. + * + * @params Sieve_Test|array $test A Sieve_Test object or a list of + * Sieve_Test objects. + */ + function Sieve_Test_Anyof($test = null) + { + if (is_array($test)) { + $this->_tests = $test; + } elseif (!is_null($test)) { + $this->_tests[] = $test; + } + } + + /** + * Returns a script snippet representing this rule and any sub-rules. + * + * @return string A Sieve script snippet. + */ + function toCode() + { + $testlist = ''; + if (count($this->_tests) > 1) { + $testlist = ''; + foreach ($this->_tests as $test) { + $testlist .= (empty($testlist)) ? '' : ', '; + $testlist .= trim($test->toCode()); + } + + $code = "anyof ( $testlist )"; + } elseif (count($this->_tests) == 1) { + $code = $this->_tests[0]->toCode(); + } else { + return 'true'; + } + return $code; + } + + function addTest($test) + { + $this->_tests[] = $test; + } + + function getTests() + { + return $this->_tests; + } + + /** + * Checks if all sub-rules are valid. + * + * @return boolean|string True if all rules are valid, an error message + * otherwise. + */ + function check() + { + foreach ($this->_tests as $test) { + $res = $test->check(); + if ($res !== true) { + return $res; + } + } + + return true; + } + + /** + * Returns a list of sieve extensions required for this rule and any + * sub-rules. + * + * @return array A Sieve extension list. + */ + function requires() + { + $requires = array(); + + foreach ($this->_tests as $test) { + $requires = array_merge($requires, $test->requires()); + } + + return $requires; + } + +} + +/** + * The Sieve_Test_Relational class represents a relational test. + * + * @author Todd Merritt + * @since Ingo 1.0 + * @package Ingo + */ +class Sieve_Test_Relational extends Sieve_Test { + + /** + * Constructor. + * + * @params array $vars Any required parameters. + */ + function Sieve_Test_Relational($vars = array()) + { + $this->_vars['comparison'] = (isset($vars['comparison'])) ? $vars['comparison'] : ''; + $this->_vars['headers'] = (isset($vars['headers'])) ? $vars['headers'] : ''; + $this->_vars['value'] = (isset($vars['value'])) ? $vars['value'] : 0; + } + + /** + * Returns a script snippet representing this rule and any sub-rules. + * + * @return string A Sieve script snippet. + */ + function toCode() + { + $code = 'header :value "' . + $this->_vars['comparison'] . '" ' . + ':comparator "i;ascii-numeric" '; + + $headers = preg_split('(\r\n|\n|\r)', $this->_vars['headers']); + $header_count = count($headers); + + if ($header_count > 1) { + $code .= "["; + $headerstr = ''; + + foreach ($headers as $val) { + $headerstr .= (empty($headerstr) ? '"' : ', "') . + Ingo_Script_Sieve::escapeString($val) . '"'; + } + + $code .= $headerstr . "] "; + } elseif ($header_count == 1) { + $code .= '"' . Ingo_Script_Sieve::escapeString($headers[0]) . '" '; + } + + return $code . '["' . $this->_vars['value'] . '"]'; + } + + /** + * Checks if the rule parameters are valid. + * + * @return boolean|string True if this rule is valid, an error message + * otherwise. + */ + function check() + { + $headers = preg_split('(\r\n|\n|\r)', $this->_vars['headers']); + return $headers ? true : _("No headers specified"); + } + + /** + * Returns a list of sieve extensions required for this rule and any + * sub-rules. + * + * @return array A Sieve extension list. + */ + function requires() + { + return array('relational', 'comparator-i;ascii-numeric'); + } + +} + +/** + * The Sieve_Test_Size class represents a message size test. + * + * @author Mike Cochrane + * @since Ingo 0.1 + * @package Ingo + */ +class Sieve_Test_Size extends Sieve_Test { + + /** + * Constructor. + * + * @params array $vars Any required parameters. + */ + function Sieve_Test_Size($vars = array()) + { + $this->_vars['comparison'] = (isset($vars['comparison'])) ? $vars['comparison'] : ''; + $this->_vars['size'] = (isset($vars['size'])) ? $vars['size'] : ''; + } + + /** + * Returns a script snippet representing this rule and any sub-rules. + * + * @return string A Sieve script snippet. + */ + function toCode() + { + return 'size ' . $this->_vars['comparison'] . ' ' . $this->_vars['size']; + } + + /** + * Checks if the rule parameters are valid. + * + * @return boolean|string True if this rule is valid, an error message + * otherwise. + */ + function check() + { + if (!(isset($this->_vars['comparison']) && + isset($this->_vars['size']))) { + return false; + } + + return true; + } + +} + +/** + * The Sieve_Test_Not class represents the inverse of a given test. + * + * @author Mike Cochrane + * @since Ingo 0.1 + * @package Ingo + */ +class Sieve_Test_Not extends Sieve_Test { + + var $_test = array(); + + /** + * Constructor. + * + * @params Sieve_Test $test A Sieve_Test object. + */ + function Sieve_Test_Not($test) + { + $this->_test = $test; + } + + /** + * Checks if the sub-rule is valid. + * + * @return boolean|string True if this rule is valid, an error message + * otherwise. + */ + function check() + { + return $this->_test->check(); + } + + /** + * Returns a script snippet representing this rule and any sub-rules. + * + * @return string A Sieve script snippet. + */ + function toCode() + { + return 'not ' . $this->_test->toCode(); + } + + /** + * Returns a list of sieve extensions required for this rule and any + * sub-rules. + * + * @return array A Sieve extension list. + */ + function requires() + { + return $this->_test->requires(); + } + +} + +/** + * The Sieve_Test_Exists class represents a test for the existsance of one or + * more headers in a message. + * + * @author Mike Cochrane + * @since Ingo 0.1 + * @package Ingo + */ +class Sieve_Test_Exists extends Sieve_Test { + + /** + * Constructor. + * + * @params array $vars Any required parameters. + */ + function Sieve_Test_Exists($vars = array()) + { + $this->_vars['headers'] = (isset($vars['headers'])) ? $vars['headers'] : ''; + } + + /** + * Checks if the rule parameters are valid. + * + * @return boolean|string True if this rule is valid, an error message + * otherwise. + */ + function check() + { + $headers = preg_split('(\r\n|\n|\r)', $this->_vars['headers']); + if (!$headers) { + return _("No headers specified"); + } + + return true; + } + + /** + * Returns a script snippet representing this rule and any sub-rules. + * + * @return string A Sieve script snippet. + */ + function toCode() + { + $code = 'exists '; + $headers = preg_split('(\r\n|\n|\r)', $this->_vars['headers']); + if (count($headers) > 1) { + $code .= "["; + $headerstr = ''; + foreach ($headers as $header) { + $headerstr .= (empty($headerstr) ? '"' : ', "') . + Ingo_Script_Sieve::escapeString($header) . '"'; + } + $code .= $headerstr . "] "; + } elseif (count($headers) == 1) { + $code .= '"' . Ingo_Script_Sieve::escapeString($headers[0]) . '" '; + } else { + return "**error** No Headers Specified"; + } + + return $code; + } + +} + +/** + * The Sieve_Test_Address class represents a test on parts or all of the + * addresses in the given fields. + * + * @author Mike Cochrane + * @since Ingo 0.1 + * @package Ingo + */ +class Sieve_Test_Address extends Sieve_Test { + + /** + * Constructor. + * + * @params array $vars Any required parameters. + */ + function Sieve_Test_Address($vars) + { + $this->_vars['headers'] = (isset($vars['headers'])) ? $vars['headers'] : ''; + $this->_vars['comparator'] = (isset($vars['comparator'])) ? $vars['comparator'] : 'i;ascii-casemap'; + $this->_vars['match-type'] = (isset($vars['match-type'])) ? $vars['match-type'] : ':is'; + $this->_vars['address-part'] = (isset($vars['address-part'])) ? $vars['address-part'] : ':all'; + $this->_vars['addresses'] = (isset($vars['addresses'])) ? $vars['addresses'] : ''; + } + + /** + * Checks if the rule parameters are valid. + * + * @return boolean|string True if this rule is valid, an error message + * otherwise. + */ + function check() + { + $headers = preg_split('(\r\n|\n|\r)', $this->_vars['headers']); + if (!$headers) { + return false; + } + + $addresses = preg_split('(\r\n|\n|\r)', $this->_vars['addresses']); + if (!$addresses) { + return false; + } + + return true; + } + + /** + * Returns a script snippet representing this rule and any sub-rules. + * + * @return string A Sieve script snippet. + */ + function toCode() + { + $code = 'address ' . + $this->_vars['address-part'] . ' ' . + ':comparator "' . $this->_vars['comparator'] . '" ' . + $this->_vars['match-type'] . ' '; + + $headers = preg_split('(\r\n|\n|\r|,)', $this->_vars['headers']); + $headers = array_filter($headers); + if (count($headers) > 1) { + $code .= "["; + $headerstr = ''; + foreach ($headers as $header) { + $header = trim($header); + if (!empty($header)) { + $headerstr .= empty($headerstr) ? '"' : ', "'; + $headerstr .= Ingo_Script_Sieve::escapeString($header, $this->_vars['match-type'] == ':regex') . '"'; + } + } + $code .= $headerstr . "] "; + } elseif (count($headers) == 1) { + $code .= '"' . Ingo_Script_Sieve::escapeString($headers[0], $this->_vars['match-type'] == ':regex') . '" '; + } else { + return "No Headers Specified"; + } + + $addresses = preg_split('(\r\n|\n|\r)', $this->_vars['addresses']); + $addresses = array_filter($addresses); + if (count($addresses) > 1) { + $code .= "["; + $addressstr = ''; + foreach ($addresses as $addr) { + $addr = trim($addr); + if (!empty($addr)) { + $addressstr .= empty($addressstr) ? '"' : ', "'; + $addressstr .= Ingo_Script_Sieve::escapeString($addr, $this->_vars['match-type'] == ':regex') . '"'; + } + } + $code .= $addressstr . "] "; + } elseif (count($addresses) == 1) { + $code .= '"' . Ingo_Script_Sieve::escapeString($addresses[0], $this->_vars['match-type'] == ':regex') . '" '; + } else { + return "No Addresses Specified"; + } + + return $code; + } + + /** + * Returns a list of sieve extensions required for this rule and any + * sub-rules. + * + * @return array A Sieve extension list. + */ + function requires() + { + if ($this->_vars['match-type'] == ':regex') { + return array('regex'); + } + return array(); + } + +} + +/** + * The Sieve_Test_Header class represents a test on the contents of one or + * more headers in a message. + * + * @author Mike Cochrane + * @since Ingo 0.1 + * @package Ingo + */ +class Sieve_Test_Header extends Sieve_Test { + + /** + * Constructor. + * + * @params array $vars Any required parameters. + */ + function Sieve_Test_Header($vars = array()) + { + $this->_vars['headers'] = isset($vars['headers']) + ? $vars['headers'] + : 'Subject'; + $this->_vars['comparator'] = isset($vars['comparator']) + ? $vars['comparator'] + : 'i;ascii-casemap'; + $this->_vars['match-type'] = isset($vars['match-type']) + ? $vars['match-type'] + : ':is'; + $this->_vars['strings'] = isset($vars['strings']) + ? $vars['strings'] + : ''; + } + + /** + * Checks if the rule parameters are valid. + * + * @return boolean|string True if this rule is valid, an error message + * otherwise. + */ + function check() + { + $headers = preg_split('((?_vars['headers']); + if (!$headers) { + return false; + } + + $strings = preg_split('((?_vars['strings']); + if (!$strings) { + return false; + } + + return true; + } + + /** + * Returns a script snippet representing this rule and any sub-rules. + * + * @return string A Sieve script snippet. + */ + function toCode() + { + $code = 'header ' . + ':comparator "' . $this->_vars['comparator'] . '" ' . + $this->_vars['match-type'] . ' '; + + $headers = preg_split('(\r\n|\n|\r)', $this->_vars['headers']); + $headers = array_filter($headers); + if (count($headers) > 1) { + $code .= "["; + $headerstr = ''; + foreach ($headers as $header) { + $headerstr .= empty($headerstr) ? '"' : ', "'; + $headerstr .= Ingo_Script_Sieve::escapeString($header, $this->_vars['match-type'] == ':regex') . '"'; + } + $code .= $headerstr . "] "; + } elseif (count($headers) == 1) { + $code .= '"' . $headers[0] . '" '; + } else { + return _("No headers specified"); + } + + $strings = preg_split('(\r\n|\n|\r)', $this->_vars['strings']); + $strings = array_filter($strings); + if (count($strings) > 1) { + $code .= "["; + $stringlist = ''; + foreach ($strings as $str) { + $stringlist .= empty($stringlist) ? '"' : ', "'; + $stringlist .= Ingo_Script_Sieve::escapeString($str, $this->_vars['match-type'] == ':regex') . '"'; + } + $code .= $stringlist . "] "; + } elseif (count($strings) == 1) { + $code .= '"' . Ingo_Script_Sieve::escapeString(reset($strings), $this->_vars['match-type'] == ':regex') . '" '; + } else { + return _("No strings specified"); + } + + return $code; + } + + /** + * Returns a list of sieve extensions required for this rule and any + * sub-rules. + * + * @return array A Sieve extension list. + */ + function requires() + { + if ($this->_vars['match-type'] == ':regex') { + return array('regex'); + } + return array(); + } + +} + +/** + * The Sieve_Test_Body class represents a test on the contents of the body in + * a message. + * + * @author Michael Menge + * @since Ingo 1.2 + * @package Ingo + */ +class Sieve_Test_Body extends Sieve_Test { + + /** + * Constructor. + * + * @params array $vars Any required parameters. + */ + function Sieve_Test_Body($vars = array()) + { + $this->_vars['comparator'] = (isset($vars['comparator'])) ? $vars['comparator'] : 'i;ascii-casemap'; + $this->_vars['match-type'] = (isset($vars['match-type'])) ? $vars['match-type'] : ':is'; + $this->_vars['strings'] = (isset($vars['strings'])) ? $vars['strings'] : ''; + } + + /** + * Checks if the rule parameters are valid. + * + * @return boolean|string True if this rule is valid, an error message + * otherwise. + */ + function check() + { + $strings = preg_split('((?_vars['strings']); + if (!$strings) { + return false; + } + + return true; + } + + /** + * Returns a script snippet representing this rule and any sub-rules. + * + * @return string A Sieve script snippet. + */ + function toCode() + { + $code = 'body ' . + ':comparator "' . $this->_vars['comparator'] . '" ' . + $this->_vars['match-type'] . ' '; + + $strings = preg_split('(\r\n|\n|\r)', $this->_vars['strings']); + $strings = array_filter($strings); + if (count($strings) > 1) { + $code .= "["; + $stringlist = ''; + foreach ($strings as $str) { + $stringlist .= empty($stringlist) ? '"' : ', "'; + $stringlist .= Ingo_Script_Sieve::escapeString($str, $this->_vars['match-type'] == ':regex') . '"'; + } + $code .= $stringlist . "] "; + } elseif (count($strings) == 1) { + $code .= '"' . Ingo_Script_Sieve::escapeString($strings[0], $this->_vars['match-type'] == ':regex') . '" '; + } else { + return _("No strings specified"); + } + + return $code; + } + + /** + * Returns a list of sieve extensions required for this rule and any + * sub-rules. + * + * @return array A Sieve extension list. + */ + function requires() + { + if ($this->_vars['match-type'] == ':regex') { + return array('regex', 'body'); + } + + return array('body'); + } + +} + +/** + * A Comment. + * + * @author Mike Cochrane + * @since Ingo 0.1 + * @package Ingo + * @todo This and Sieve_If should really extends a Sieve_Block eventually. + */ +class Sieve_Comment { + + var $_comment; + + /** + * Constructor. + * + * @params string $comment The comment text. + */ + function Sieve_Comment($comment) + { + $this->_comment = $comment; + } + + /** + * Returns a script snippet representing this rule and any sub-rules. + * + * @return string A Sieve script snippet. + */ + function toCode() + { + $code = ''; + $lines = preg_split('(\r\n|\n|\r)', $this->_comment); + foreach ($lines as $line) { + $line = trim($line); + if (strlen($line)) { + $code .= (empty($code) ? '' : "\n") . '# ' . $line; + } + } + return Horde_String::convertCharset($code, Horde_Nls::getCharset(), 'UTF-8'); + } + + /** + * Checks if the rule parameters are valid. + * + * @return boolean|string True if this rule is valid, an error message + * otherwise. + */ + function check() + { + return true; + } + + /** + * Returns a list of sieve extensions required for this rule and any + * sub-rules. + * + * @return array A Sieve extension list. + */ + function requires() + { + return array(); + } + +} + +/** + * The Sieve_Action class represents an action in a Sieve script. + * + * An action is anything that has a side effect eg: discard, redirect. + * + * @author Mike Cochrane + * @since Ingo 0.1 + * @package Ingo + */ +class Sieve_Action { + + /** + * Any necessary action parameters. + * + * @var array + */ + var $_vars = array(); + + /** + * Returns a script snippet representing this rule and any sub-rules. + * + * @return string A Sieve script snippet. + */ + function toCode() + { + return 'toCode() Function Not Implemented in class ' . get_class($this) ; + } + + function toString() + { + return $this->toCode(); + } + + /** + * Checks if the rule parameters are valid. + * + * @return boolean|string True if this rule is valid, an error message + * otherwise. + */ + function check() + { + return 'check() Function Not Implemented in class ' . get_class($this) ; + } + + /** + * Returns a list of sieve extensions required for this rule and any + * sub-rules. + * + * @return array A Sieve extension list. + */ + function requires() + { + return array(); + } + +} + +/** + * The Sieve_Action_Redirect class represents a redirect action. + * + * @author Mike Cochrane + * @since Ingo 0.1 + * @package Ingo + */ +class Sieve_Action_Redirect extends Sieve_Action { + + /** + * Constructor. + * + * @params array $vars Any required parameters. + */ + function Sieve_Action_Redirect($vars = array()) + { + $this->_vars['address'] = (isset($vars['address'])) ? $vars['address'] : ''; + } + + function toCode($depth = 0) + { + return str_repeat(' ', $depth * 4) . 'redirect ' . + '"' . Ingo_Script_Sieve::escapeString($this->_vars['address']) . '";'; + } + + /** + * Checks if the rule parameters are valid. + * + * @return boolean|string True if this rule is valid, an error message + * otherwise. + */ + function check() + { + if (empty($this->_vars['address'])) { + return _("Missing address to redirect message to"); + } + + return true; + } + +} + +/** + * The Sieve_Action_Reject class represents a reject action. + * + * @author Mike Cochrane + * @since Ingo 0.1 + * @package Ingo + */ +class Sieve_Action_Reject extends Sieve_Action { + + /** + * Constructor. + * + * @params array $vars Any required parameters. + */ + function Sieve_Action_Reject($vars = array()) + { + $this->_vars['reason'] = (isset($vars['reason'])) ? $vars['reason'] : ''; + } + + /** + * Returns a script snippet representing this rule and any sub-rules. + * + * @return string A Sieve script snippet. + */ + function toCode() + { + return 'reject "' . Ingo_Script_Sieve::escapeString($this->_vars['reason']) . '";'; + } + + /** + * Checks if the rule parameters are valid. + * + * @return boolean|string True if this rule is valid, an error message + * otherwise. + */ + function check() + { + if (empty($this->_vars['reason'])) { + return _("Missing reason for reject"); + } + + return true; + } + + /** + * Returns a list of sieve extensions required for this rule and any + * sub-rules. + * + * @return array A Sieve extension list. + */ + function requires() + { + return array('reject'); + } + +} + +/** + * The Sieve_Action_Keep class represents a keep action. + * + * @author Mike Cochrane + * @since Ingo 0.1 + * @package Ingo + */ +class Sieve_Action_Keep extends Sieve_Action { + + /** + * Returns a script snippet representing this rule and any sub-rules. + * + * @return string A Sieve script snippet. + */ + function toCode() + { + return 'keep;'; + } + + /** + * Checks if the rule parameters are valid. + * + * @return boolean|string True if this rule is valid, an error message + * otherwise. + */ + function check() + { + return true; + } + +} + +/** + * The Sieve_Action_Discard class represents a discard action. + * + * @author Mike Cochrane + * @since Ingo 0.1 + * @package Ingo + */ +class Sieve_Action_Discard extends Sieve_Action { + + /** + * Returns a script snippet representing this rule and any sub-rules. + * + * @return string A Sieve script snippet. + */ + function toCode() + { + return 'discard;'; + } + + /** + * Checks if the rule parameters are valid. + * + * @return boolean|string True if this rule is valid, an error message + * otherwise. + */ + function check() + { + return true; + } + +} + +/** + * The Sieve_Action_Stop class represents a stop action. + * + * @author Mike Cochrane + * @since Ingo 0.1 + * @package Ingo + */ +class Sieve_Action_Stop extends Sieve_Action { + + /** + * Returns a script snippet representing this rule and any sub-rules. + * + * @return string A Sieve script snippet. + */ + function toCode() + { + return 'stop;'; + } + + /** + * Checks if the rule parameters are valid. + * + * @return boolean|string True if this rule is valid, an error message + * otherwise. + */ + function check() + { + return true; + } + +} + +/** + * The Sieve_Action_Fileinto class represents a fileinto action. + * + * @author Mike Cochrane + * @since Ingo 0.1 + * @package Ingo + */ +class Sieve_Action_Fileinto extends Sieve_Action { + + /** + * Constructor. + * + * @params array $vars Any required parameters. + */ + function Sieve_Action_Fileinto($vars = array()) + { + $this->_vars['folder'] = (isset($vars['folder'])) ? $vars['folder'] : ''; + } + + /** + * Returns a script snippet representing this rule and any sub-rules. + * + * @return string A Sieve script snippet. + */ + function toCode() + { + return 'fileinto "' . Ingo_Script_Sieve::escapeString($this->_vars['folder']) . '";'; + } + + /** + * Checks if the rule parameters are valid. + * + * @return boolean|string True if this rule is valid, an error message + * otherwise. + */ + function check() + { + if (empty($this->_vars['folder'])) { + return _("Inexistant mailbox specified for message delivery."); + } + + return true; + } + + /** + * Returns a list of sieve extensions required for this rule and any + * sub-rules. + * + * @return array A Sieve extension list. + */ + function requires() + { + return array('fileinto'); + } + +} + +/** + * The Sieve_Action_Vacation class represents a vacation action. + * + * @author Mike Cochrane + * @since Ingo 0.1 + * @package Ingo + */ +class Sieve_Action_Vacation extends Sieve_Action { + + /** + * Constructor. + * + * @params array $vars Any required parameters. + */ + function Sieve_Action_Vacation($vars = array()) + { + $this->_vars['days'] = isset($vars['days']) ? intval($vars['days']) : ''; + $this->_vars['addresses'] = isset($vars['addresses']) ? $vars['addresses'] : ''; + $this->_vars['subject'] = isset($vars['subject']) ? $vars['subject'] : ''; + $this->_vars['reason'] = isset($vars['reason']) ? $vars['reason'] : ''; + $this->_vars['start'] = isset($vars['start']) ? $vars['start'] : ''; + $this->_vars['start_year'] = isset($vars['start_year']) ? $vars['start_year'] : ''; + $this->_vars['start_month'] = isset($vars['start_month']) ? $vars['start_month'] : ''; + $this->_vars['start_day'] = isset($vars['start_day']) ? $vars['start_day'] : ''; + $this->_vars['end'] = isset($vars['end']) ? $vars['end'] : ''; + $this->_vars['end_year'] = isset($vars['end_year']) ? $vars['end_year'] : ''; + $this->_vars['end_month'] = isset($vars['end_month']) ? $vars['end_month'] : ''; + $this->_vars['end_day'] = isset($vars['end_day']) ? $vars['end_day'] : ''; + } + + /** + * Returns a script snippet representing this rule and any sub-rules. + * + * @return string A Sieve script snippet. + */ + function toCode() + { + $start_year = $this->_vars['start_year']; + $start_month = $this->_vars['start_month']; + $start_day = $this->_vars['start_day']; + + $end_year = $this->_vars['end_year']; + $end_month = $this->_vars['end_month']; + $end_day = $this->_vars['end_day']; + + $code = ''; + + if (empty($this->_vars['start']) || empty($this->_vars['end'])) { + return $this->_vacationCode(); + } elseif ($end_year > $start_year + 1) { + $code .= $this->_yearCheck($start_year + 1, $end_year - 1) + . $this->_vacationCode() + . "\n}\n" + . $this->_yearCheck($start_year, $start_year); + if ($start_month < 12) { + $code .= $this->_monthCheck($start_month + 1, 12) + . $this->_vacationCode() + . "\n}\n"; + } + $code .= $this->_monthCheck($start_month, $start_month) + . $this->_dayCheck($start_day, 31) + . $this->_vacationCode() + . "\n}\n}\n}\n" + . $this->_yearCheck($end_year, $end_year); + if ($end_month > 1) { + $code .= $this->_monthCheck(1, $end_month - 1) + . $this->_vacationCode() + . "\n}\n"; + } + $code .= $this->_monthCheck($end_month, $end_month) + . $this->_dayCheck(1, $end_day) + . $this->_vacationCode() + . "\n}\n}\n}\n"; + } elseif ($end_year == $start_year + 1) { + $code .= $this->_yearCheck($start_year, $start_year); + if ($start_month < 12) { + $code .= $this->_monthCheck($start_month + 1, 12) + . $this->_vacationCode() + . "\n}\n"; + } + $code .= $this->_monthCheck($start_month, $start_month) + . $this->_dayCheck($start_day, 31) + . $this->_vacationCode() + . "\n}\n}\n}\n" + . $this->_yearCheck($end_year, $end_year); + if ($end_month > 1) { + $code .= $this->_monthCheck(1, $end_month - 1) + . $this->_vacationCode() + . "\n}\n"; + } + $code .= $this->_monthCheck($end_month, $end_month) + . $this->_dayCheck(1, $end_day) + . $this->_vacationCode() + . "\n}\n}\n}\n"; + } elseif ($end_year == $start_year) { + $code .= $this->_yearCheck($start_year, $start_year); + if ($end_month > $start_month) { + if ($end_month > $start_month + 1) { + $code .= $this->_monthCheck($start_month + 1, $end_month - 1) + . $this->_vacationCode() + . "\n}\n"; + } + $code .= $this->_monthCheck($start_month, $start_month) + . $this->_dayCheck($start_day, 31) + . $this->_vacationCode() + . "\n}\n}\n" + . $this->_monthCheck($end_month, $end_month) + . $this->_dayCheck(1, $end_day) + . $this->_vacationCode() + . "\n}\n}\n"; + } elseif ($end_month == $start_month) { + $code .= $this->_monthCheck($start_month, $start_month) + . $this->_dayCheck($start_day, $end_day) + . $this->_vacationCode() + . "\n}\n}\n"; + } + $code .= "}\n"; + } + + return $code; + } + + /** + * Checks if the rule parameters are valid. + * + * @return boolean|string True if this rule is valid, an error message + * otherwise. + */ + function check() + { + if (empty($this->_vars['reason'])) { + return _("Missing reason in vacation."); + } + + return true; + } + + /** + * Returns a list of sieve extensions required for this rule and any + * sub-rules. + * + * @return array A Sieve extension list. + */ + function requires() + { + return array('vacation', 'regex'); + } + + /** + */ + function _vacationCode() + { + $code = 'vacation :days ' . $this->_vars['days'] . ' '; + $addresses = $this->_vars['addresses']; + $stringlist = ''; + if (count($addresses) > 1) { + foreach ($addresses as $address) { + $address = trim($address); + if (!empty($address)) { + $stringlist .= empty($stringlist) ? '"' : ', "'; + $stringlist .= Ingo_Script_Sieve::escapeString($address) . '"'; + } + } + $stringlist = "[" . $stringlist . "] "; + } elseif (count($addresses) == 1) { + $stringlist = '"' . Ingo_Script_Sieve::escapeString($addresses[0]) . '" '; + } + + if (!empty($stringlist)) { + $code .= ':addresses ' . $stringlist; + } + + if (!empty($this->_vars['subject'])) { + $code .= ':subject "' . Horde_Mime::encode(Ingo_Script_Sieve::escapeString($this->_vars['subject']), 'UTF-8') . '" '; + } + return $code + . '"' . Ingo_Script_Sieve::escapeString($this->_vars['reason']) + . '";'; + } + + /** + */ + function _yearCheck($begin, $end) + { + $code = 'if header :regex "Received" "^.*(' . $begin; + for ($i = $begin + 1; $i <= $end; $i++) { + $code .= '|' . $i; + } + return $code + . ') (\\\\(.*\\\\) )?..:..:.. (\\\\(.*\\\\) )?(\\\\+|\\\\-)....( \\\\(.*\\\\))?$" {' + . "\n "; + } + + /** + */ + function _monthCheck($begin, $end) + { + $months = array('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', + 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'); + $code = 'if header :regex "Received" "^.*(' . $months[$begin - 1]; + for ($i = $begin + 1; $i <= $end; $i++) { + $code .= '|' . $months[$i - 1]; + } + return $code + . ') (\\\\(.*\\\\) )?.... (\\\\(.*\\\\) )?..:..:.. (\\\\(.*\\\\) )?(\\\\+|\\\\-)....( \\\\(.*\\\\))?$" {' + . "\n "; + } + + /** + */ + function _dayCheck($begin, $end) + { + $code = 'if header :regex "Received" "^.*(' . str_repeat('[0 ]', 2 - strlen($begin)) . $begin; + for ($i = $begin + 1; $i <= $end; $i++) { + $code .= '|' . str_repeat('[0 ]', 2 - strlen($i)) . $i; + } + return $code + . ') (\\\\(.*\\\\) )?... (\\\\(.*\\\\) )?.... (\\\\(.*\\\\) )?..:..:.. (\\\\(.*\\\\) )?(\\\\+|\\\\-)....( \\\\(.*\\\\))?$" {' + . "\n "; + } + +} + +/** + * The Sieve_Action_Flag class is the base class for flag actions. + * + * @author Michael Slusarz + * @since Ingo 0.1 + * @package Ingo + */ +class Sieve_Action_Flag extends Sieve_Action { + + /** + * Constructor. + * + * @params array $vars Any required parameters. + */ + function Sieve_Action_Flag($vars = array()) + { + if (isset($vars['flags'])) { + if ($vars['flags'] & Ingo_Storage::FLAG_ANSWERED) { + $this->_vars['flags'][] = '\Answered'; + } + if ($vars['flags'] & Ingo_Storage::FLAG_DELETED) { + $this->_vars['flags'][] = '\Deleted'; + } + if ($vars['flags'] & Ingo_Storage::FLAG_FLAGGED) { + $this->_vars['flags'][] = '\Flagged'; + } + if ($vars['flags'] & Ingo_Storage::FLAG_SEEN) { + $this->_vars['flags'][] = '\Seen'; + } + } else { + $this->_vars['flags'] = ''; + } + } + + /** + * Returns a script snippet representing this rule and any sub-rules. + * + * @param string $mode The sieve flag command to use. Either 'removeflag' + * or 'addflag'. + * + * @return string A Sieve script snippet. + */ + function _toCode($mode) + { + $code = ''; + + if (is_array($this->_vars['flags']) && !empty($this->_vars['flags'])) { + $code .= $mode . ' '; + if (count($this->_vars['flags']) > 1) { + $stringlist = ''; + foreach ($this->_vars['flags'] as $flag) { + $flag = trim($flag); + if (!empty($flag)) { + $stringlist .= empty($stringlist) ? '"' : ', "'; + $stringlist .= Ingo_Script_Sieve::escapeString($flag) . '"'; + } + } + $stringlist = '[' . $stringlist . ']'; + $code .= $stringlist . ';'; + } else { + $code .= '"' . Ingo_Script_Sieve::escapeString($this->_vars['flags'][0]) . '";'; + } + } + return $code; + } + + /** + * Checks if the rule parameters are valid. + * + * @return boolean|string True if this rule is valid, an error message + * otherwise. + */ + function check() + { + return true; + } + + /** + * Returns a list of sieve extensions required for this rule and any + * sub-rules. + * + * @return array A Sieve extension list. + */ + function requires() + { + return array('imapflags'); + } + +} + +/** + * The Sieve_Action_Addflag class represents an add flag action. + * + * @author Mike Cochrane + * @since Ingo 0.1 + * @package Ingo + */ +class Sieve_Action_Addflag extends Sieve_Action_Flag { + + /** + * Returns a script snippet representing this rule and any sub-rules. + * + * @return string A Sieve script snippet. + */ + function toCode() + { + return $this->_toCode('addflag'); + } + +} + +/** + * The Sieve_Action_Removeflag class represents a remove flag action. + * + * @author Mike Cochrane + * @since Ingo 0.1 + * @package Ingo + */ +class Sieve_Action_Removeflag extends Sieve_Action_Flag { + + /** + * Returns a script snippet representing this rule and any sub-rules. + * + * @return string A Sieve script snippet. + */ + function toCode() + { + return $this->_toCode('removeflag'); + } + +} + +/** + * The Sieve_Action_Notify class represents a notify action. + * + * @author Paul Wolstenholme + * @since Ingo 1.1 + * @package Ingo + */ +class Sieve_Action_Notify extends Sieve_Action { + + /** + * Constructor. + * + * @params array $vars Any required parameters. + */ + function Sieve_Action_Notify($vars = array()) + { + $this->_vars['address'] = isset($vars['address']) ? $vars['address'] : ''; + $this->_vars['name'] = isset($vars['name']) ? $vars['name'] : ''; + } + + /** + * Returns a script snippet representing this rule and any sub-rules. + * + * @return string A Sieve script snippet. + */ + function toCode() + { + return 'notify :method "mailto" :options "' . + Ingo_Script_Sieve::escapeString($this->_vars['address']) . + '" :message "' . + _("You have received a new message") . "\n" . + _("From:") . " \$from\$ \n" . + _("Subject:") . " \$subject\$ \n" . + _("Rule:") . ' ' . $this->_vars['name'] . '";'; + } + + /** + * Checks if the rule parameters are valid. + * + * @return boolean|string True if this rule is valid, an error message + * otherwise. + */ + function check() + { + if (empty($this->_vars['address'])) { + return _("Missing address to notify"); + } + + return true; + } + + /** + * Returns a list of sieve extensions required for this rule and any + * sub-rules. + * + * @return array A Sieve extension list. + */ + function requires() + { + return array('notify'); + } + +} diff --git a/ingo/lib/Script/imap.php b/ingo/lib/Script/imap.php deleted file mode 100644 index aff92dedf..000000000 --- a/ingo/lib/Script/imap.php +++ /dev/null @@ -1,459 +0,0 @@ - - * @package Ingo - */ -class Ingo_Script_imap extends Ingo_Script -{ - /** - * 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 - ); - - /** - * Does the driver support setting IMAP flags? - * - * @var boolean - */ - protected $_supportIMAPFlags = true; - - /** - * Does the driver support the stop-script option? - * - * @var boolean - */ - protected $_supportStopScript = true; - - /** - * This driver can perform on demand filtering (in fact, that is all - * it can do). - * - * @var boolean - */ - protected $_ondemand = true; - - /** - * The API to use for IMAP functions. - * - * @var Ingo_Script_imap_api - */ - protected $_api; - - /** - * Perform the filtering specified in the rules. - * - * @param array $params The parameter array. It MUST contain: - *
-     * 'mailbox' - The name of the mailbox to filter.
-     * 
- * - * @return boolean True if filtering performed, false if not. - */ - public function perform($params) - { - if (empty($params['api'])) { - $this->_api = Ingo_Script_imap_api::factory('live', $params); - } else { - $this->_api = &$params['api']; - } - - /* 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 = $this->_api->getCache(); - if (($cache !== false) && ($cache == $_SESSION['ingo']['change'])) { - return true; - } - - /* Grab the rules list. */ - $filters = &$GLOBALS['ingo_storage']->retrieve(Ingo_Storage::ACTION_FILTERS); - - /* Should we filter only [un]seen messages? */ - $seen_flag = $GLOBALS['prefs']->getValue('filter_seen'); - - /* Should we use detailed notification messages? */ - $detailmsg = $GLOBALS['prefs']->getValue('show_filter_msg'); - - /* Parse through the rules, one-by-one. */ - foreach ($filters->getFilterList() 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; - } - - $search_array = array(); - - switch ($rule['action']) { - case Ingo_Storage::ACTION_BLACKLIST: - case Ingo_Storage::ACTION_WHITELIST: - $bl_folder = null; - - if ($rule['action'] == Ingo_Storage::ACTION_BLACKLIST) { - $blacklist = &$GLOBALS['ingo_storage']->retrieve(Ingo_Storage::ACTION_BLACKLIST); - $addr = $blacklist->getBlacklist(); - $bl_folder = $blacklist->getBlacklistFolder(); - } else { - $whitelist = &$GLOBALS['ingo_storage']->retrieve(Ingo_Storage::ACTION_WHITELIST); - $addr = $whitelist->getWhitelist(); - } - - /* If list is empty, move on. */ - if (empty($addr)) { - continue; - } - - $query = new Horde_Imap_Client_Search_Query(); - foreach ($addr as $val) { - $ob = new Horde_Imap_Client_Search_Query(); - $ob->flag('\\deleted', false); - if ($seen_flag == Ingo_Script::FILTER_UNSEEN) { - $ob->flag('\\seen', false); - } elseif ($seen_flag == Ingo_Script::FILTER_SEEN) { - $ob->flag('\\seen', true); - } - $ob->headerText('from', $val); - $search_array[] = $ob; - } - $query->orSearch($search_array); - $indices = $this->_api->search($query); - - /* Remove any indices that got in there by way of partial - * address match. */ - $msgs = $this->_api->fetchEnvelope($indices); - foreach ($msgs as $k => $v) { - $from_addr = Horde_Mime_Address::bareAddress(Horde_Mime_Address::addrArray2String($v['envelope']['from'])); - $found = false; - foreach ($addr as $val) { - if (strtolower($from_addr) == strtolower($val)) { - $found = true; - } - } - if (!$found) { - $indices = array_diff($indices, array($k)); - } - } - - if ($rule['action'] == Ingo_Storage::ACTION_BLACKLIST) { - $indices = array_diff($indices, $ignore_ids); - if (!empty($indices)) { - if (!empty($bl_folder)) { - $this->_api->moveMessages($indices, $bl_folder); - } else { - $this->_api->deleteMessages($indices); - } - $GLOBALS['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: - $query = new Horde_Imap_Client_Search_Query(); - foreach ($rule['conditions'] as $val) { - $ob = new Horde_Imap_Client_Search_Query(); - $ob->flag('\\deleted', false); - if ($seen_flag == Ingo_Script::FILTER_UNSEEN) { - $ob->flag('\\seen', false); - } elseif ($seen_flag == Ingo_Script::FILTER_SEEN) { - $ob->flag('\\seen', true); - } - 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->text($val['value'], true, ($val['match'] == 'not contain')); - } else { - $ob->headerText($val['field'], $val['value'], ($val['match'] == 'not contain')); - } - $search_array[] = $ob; - } - - if ($rule['combine'] == Ingo_Storage::COMBINE_ALL) { - $query->andSearch($search_array); - } else { - $query->orSearch($search_array); - } - - $indices = $this->_api->search($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'; - } - $this->_api->setMessageFlags($indices, implode(' ', $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 overview first. */ - if ($detailmsg) { - $overview = $this->_api->fetchEnvelope($indices); - } - - /* Move the messages to the requested mailbox. */ - $this->_api->moveMessages($indices, $rule['action-value']); - - /* Display notification message(s). */ - if ($detailmsg) { - foreach ($overview as $msg) { - $GLOBALS['notification']->push( - sprintf(_("Filter activity: The message \"%s\" from \"%s\" has been moved to the folder \"%s\"."), - !empty($msg['envelope']['subject']) ? Horde_Mime::decode($msg['envelope']['subject'], Horde_Nls::getCharset()) : _("[No Subject]"), - !empty($msg['envelope']['from']) ? Horde_Mime::decode(Horde_Mime_Address::addrArray2String($msg['envelope']['from']), Horde_Nls::getCharset()) : _("[No Sender]"), - Horde_String::convertCharset($rule['action-value'], 'UTF7-IMAP', Horde_Nls::getCharset())), - 'horde.message'); - } - } else { - $GLOBALS['notification']->push(sprintf(_("Filter activity: %s message(s) have been moved to the folder \"%s\"."), - count($indices), - Horde_String::convertCharset($rule['action-value'], 'UTF7-IMAP', Horde_Nls::getCharset())), 'horde.message'); - } - } elseif ($rule['action'] == Ingo_Storage::ACTION_DISCARD) { - /* We need to grab the overview first. */ - if ($detailmsg) { - $overview = $this->_api->fetchEnvelope($indices); - } - - /* Delete the messages now. */ - $this->_api->deleteMessages($indices); - - /* Display notification message(s). */ - if ($detailmsg) { - foreach ($overview as $msg) { - $GLOBALS['notification']->push( - sprintf(_("Filter activity: The message \"%s\" from \"%s\" has been deleted."), - !empty($msg['envelope']['subject']) ? Horde_Mime::decode($msg['envelope']['subject'], Horde_Nls::getCharset()) : _("[No Subject]"), - !empty($msg['envelope']['from']) ? Horde_Mime::decode($msg['envelope']['from'], Horde_Nls::getCharset()) : _("[No Sender]")), - 'horde.message'); - } - } else { - $GLOBALS['notification']->push(sprintf(_("Filter activity: %s message(s) have been deleted."), count($indices)), 'horde.message'); - } - } elseif ($rule['action'] == Ingo_Storage::ACTION_MOVEKEEP) { - /* Copy the messages to the requested mailbox. */ - $this->_api->copyMessages($indices, - $rule['action-value']); - - /* Display notification message(s). */ - if ($detailmsg) { - $overview = $this->_api->fetchEnvelope($indices); - foreach ($overview as $msg) { - $GLOBALS['notification']->push( - sprintf(_("Filter activity: The message \"%s\" from \"%s\" has been copied to the folder \"%s\"."), - !empty($msg['envelope']['subject']) ? Horde_Mime::decode($msg['envelope']['subject'], Horde_Nls::getCharset()) : _("[No Subject]"), - !empty($msg['envelope']['from']) ? Horde_Mime::decode($msg['envelope']['from'], Horde_Nls::getCharset()) : _("[No Sender]"), - Horde_String::convertCharset($rule['action-value'], 'UTF7-IMAP', Horde_Nls::getCharset())), - 'horde.message'); - } - } else { - $GLOBALS['notification']->push(sprintf(_("Filter activity: %s message(s) have been copied to the folder \"%s\"."), count($indices), Horde_String::convertCharset($rule['action-value'], 'UTF7-IMAP', Horde_Nls::getCharset())), 'horde.message'); - } - } - } - break; - } - } - - /* Set cache flag. */ - $this->_api->storeCache($_SESSION['ingo']['change']); - - return true; - } - - /** - * Is the apply() function available? - * - * @return boolean True if apply() is available, false if not. - */ - public function canApply() - { - if ($this->performAvailable() && - $GLOBALS['registry']->hasMethod('mail/server')) { - $server = $GLOBALS['registry']->call('mail/server'); - return ($server['protocol'] == 'imap'); - } - - return false; - } - - /** - * Apply the filters now. - * - * @return boolean See perform(). - */ - public function apply() - { - return $this->canApply() - ? $this->perform(array('mailbox' => 'INBOX')) - : false; - } - -} - -class Ingo_Script_imap_api -{ - /** - * TODO - */ - protected $_params; - - /** - * TODO - */ - static public function factory($type, $params) - { - $class = 'Ingo_Script_imap_' . $type; - return new $class($params); - } - - /** - * TODO - */ - public function __construct($params = array()) - { - $this->_params = $params; - } - - /** - * TODO - */ - public function deleteMessages($indices) - { - return PEAR::raiseError('Not implemented.'); - } - - /** - * TODO - */ - public function moveMessages($indices, $folder) - { - return PEAR::raiseError('Not implemented.'); - } - - /** - * TODO - */ - public function copyMessages($indices, $folder) - { - return PEAR::raiseError('Not implemented.'); - } - - /** - * TODO - */ - public function setMessageFlags($indices, $flags) - { - return PEAR::raiseError('Not implemented.'); - } - - /** - * TODO - */ - public function fetchEnvelope($indices) - { - return PEAR::raiseError('Not implemented.'); - } - - /** - * TODO - */ - public function search($query) - { - return PEAR::raiseError('Not implemented.'); - } - - /** - * TODO - */ - public function getCache() - { - return false; - } - - /** - * TODO - */ - public function storeCache($timestamp) - { - } - -} diff --git a/ingo/lib/Script/imap/live.php b/ingo/lib/Script/imap/live.php deleted file mode 100644 index a2ad225ad..000000000 --- a/ingo/lib/Script/imap/live.php +++ /dev/null @@ -1,110 +0,0 @@ - - * @author Michael Slusarz - * @package Ingo - */ -class Ingo_Script_imap_live extends Ingo_Script_imap_api -{ - /** - */ - public function deleteMessages($indices) - { - return $GLOBALS['registry']->hasMethod('mail/deleteMessages') - ? $GLOBALS['registry']->call('mail/deleteMessages', array($this->_params['mailbox'], $indices)) - : false; - } - - /** - */ - public function moveMessages($indices, $folder) - { - return $GLOBALS['registry']->hasMethod('mail/moveMessages') - ? $GLOBALS['registry']->call('mail/moveMessages', array($this->_params['mailbox'], $indices, $folder)) - : false; - } - - /** - */ - public function copyMessages($indices, $folder) - { - return $GLOBALS['registry']->hasMethod('mail/copyMessages') - ? $GLOBALS['registry']->call('mail/copyMessages', array($this->_params['mailbox'], $indices, $folder)) - : false; - } - - /** - */ - public function setMessageFlags($indices, $flags) - { - return $GLOBALS['registry']->hasMethod('mail/flagMessages') - ? $GLOBALS['registry']->call('mail/flagMessages', array($this->_params['mailbox'], $indices, $flags, true)) - : false; - } - - /** - */ - public function fetchEnvelope($indices) - { - return $GLOBALS['registry']->hasMethod('mail/msgEnvelope') - ? $GLOBALS['registry']->call('mail/msgEnvelope', array($this->_params['mailbox'], $indices)) - : false; - } - - /** - */ - public function search($query) - { - return $GLOBALS['registry']->hasMethod('mail/searchMailbox') - ? $GLOBALS['registry']->call('mail/searchMailbox', array($this->_params['mailbox'], $query)) - : false; - } - - /** - */ - public function getCache() - { - if (empty($_SESSION['ingo']['imapcache'][$this->_params['mailbox']])) { - return false; - } - $ptr = &$_SESSION['ingo']['imapcache'][$this->_params['mailbox']]; - - if ($this->_cacheId() != $ptr['id']) { - $ptr = array(); - return false; - } - - return $ptr['ts']; - } - - /** - */ - public function storeCache($timestamp) - { - if (!isset($_SESSION['ingo']['imapcache'])) { - $_SESSION['ingo']['imapcache'] = array(); - } - - $_SESSION['ingo']['imapcache'][$this->_params['mailbox']] = array( - 'id' => $this->_cacheId(), - 'ts' => $timestamp - ); - } - - /** - */ - protected function _cacheId() - { - return $GLOBALS['registry']->hasMethod('mail/mailboxCacheId') - ? $GLOBALS['registry']->call('mail/mailboxCacheId', array($this->_params['mailbox'])) - : time(); - } - -} diff --git a/ingo/lib/Script/imap/mock.php b/ingo/lib/Script/imap/mock.php deleted file mode 100644 index ecd4784a1..000000000 --- a/ingo/lib/Script/imap/mock.php +++ /dev/null @@ -1,125 +0,0 @@ -_fixtures = array(); - - $dh = opendir($dir); - while (($dent = readdir($dh)) !== false) { - if (!in_array($dent, array('.', '..'))) { - $this->_fixtures[$dent] = Horde_Mime_Part::parseHeaders(file_get_contents($dir . '/' . $dent)); - } - } - closedir($dh); - - $i = 0; - foreach (array_keys($this->_fixtures) as $key) { - $this->_folders['INBOX'][] = array('uid' => ++$i, - 'fixture' => $key, - 'deleted' => false); - } - } - - /** - * TODO - */ - public function hasMessage($fixture, $folder = 'INBOX') - { - if (empty($this->_folders[$folder])) { - return false; - } - foreach ($this->_folders[$folder] as $message) { - if ($message['fixture'] == $fixture) { - return !$message['deleted']; - } - } - return false; - } - - /** - * TODO - */ - public function search(&$query) - { - $result = array(); - foreach ($this->_folders['INBOX'] as $message) { - if ($message['deleted']) { - continue; - } - if ($query->matches($this->_fixtures[$message['fixture']])) { - $result[] = $message['uid']; - } - } - return $result; - } - - /** - * TODO - */ - public function deleteMessages($indices) - { - foreach (array_keys($this->_folders['INBOX']) as $i) { - if (in_array($this->_folders['INBOX'][$i]['uid'], $indices)) { - unset($this->_folders['INBOX'][$i]); - } - } - - // Force renumbering - $this->_folders['INBOX'] = array_merge($this->_folders['INBOX'], array()); - } - - /** - * TODO - */ - public function moveMessages($indices, $folder) - { - foreach (array_keys($this->_folders['INBOX']) as $i) { - if (in_array($this->_folders['INBOX'][$i]['uid'], $indices)) { - $this->_folders[$folder][] = $this->_folders['INBOX'][$i]; - } - } - return $this->deleteMessages($indices); - } - - /** - * TODO - */ - public function fetchEnvelope($indices) - { - $result = array(); - - foreach ($indices as $uid) { - foreach (array_keys($this->_folders['INBOX']) as $i) { - if ($this->_folders['INBOX'][$i]['uid'] == $uid) { - $fixture = $this->_fixtures[$this->_folders['INBOX'][$i]['fixture']]; - $result[] = array( - 'envelope' => array( - 'from' => $fixture->getValue('from'), - 'uid' => $uid - ) - ); - } - } - } - - return $result; - } - -} diff --git a/ingo/lib/Script/maildrop.php b/ingo/lib/Script/maildrop.php deleted file mode 100644 index 2c3b4483b..000000000 --- a/ingo/lib/Script/maildrop.php +++ /dev/null @@ -1,762 +0,0 @@ - - * - * See the enclosed file LICENSE for license information (ASL). If you - * did not receive this file, see http://www.horde.org/licenses/asl.php. - * - * @author Matt Weyland - * @package Ingo - */ - -/** - * Additional storage action since maildrop does not support the - * "c-flag" as in procmail. - */ -define('MAILDROP_STORAGE_ACTION_STOREANDFORWARD', 100); - -/** - */ -class Ingo_Script_maildrop extends Ingo_Script { - - /** - * The list of actions allowed (implemented) for this driver. - * - * @var array - */ - var $_actions = array( - Ingo_Storage::ACTION_KEEP, - Ingo_Storage::ACTION_MOVE, - Ingo_Storage::ACTION_DISCARD, - Ingo_Storage::ACTION_REDIRECT, - Ingo_Storage::ACTION_REDIRECTKEEP, - Ingo_Storage::ACTION_REJECT, - ); - - /** - * The categories of filtering allowed. - * - * @var array - */ - var $_categories = array( - Ingo_Storage::ACTION_BLACKLIST, - Ingo_Storage::ACTION_WHITELIST, - Ingo_Storage::ACTION_VACATION, - Ingo_Storage::ACTION_FORWARD, - Ingo_Storage::ACTION_SPAM, - ); - - /** - * The types of tests allowed (implemented) for this driver. - * - * @var array - */ - var $_types = array( - Ingo_Storage::TYPE_HEADER, - ); - - /** - * The list of tests allowed (implemented) for this driver. - * - * @var array - */ - var $_tests = array( - 'contains', 'not contain', - 'is', 'not is', - 'begins with','not begins with', - 'ends with', 'not ends with', - 'regex', - 'matches', 'not matches', - 'exists', 'not exist', - 'less than', 'less than or equal to', - 'equal', 'not equal', - 'greater than', 'greater than or equal to', - ); - - /** - * Can tests be case sensitive? - * - * @var boolean - */ - var $_casesensitive = true; - - /** - * Does the driver support the stop-script option? - * - * @var boolean - */ - var $_supportStopScript = false; - - /** - * Does the driver require a script file to be generated? - * - * @var boolean - */ - var $_scriptfile = true; - - /** - * The recipes that make up the code. - * - * @var array - */ - var $_recipes = array(); - - /** - * Returns a script previously generated with generate(). - * - * @return string The maildrop script. - */ - function toCode() - { - $code = ''; - foreach ($this->_recipes as $item) { - $code .= $item->generate() . "\n"; - } - return rtrim($code); - } - - /** - * Generates the maildrop script to do the filtering specified in - * the rules. - * - * @return string The maildrop script. - */ - function generate() - { - $filters = &$GLOBALS['ingo_storage']->retrieve(Ingo_Storage::ACTION_FILTERS); - - $this->addItem(new Maildrop_Comment(_("maildrop script generated by Ingo") . ' (' . date('F j, Y, g:i a') . ')')); - - /* Add variable information, if present. */ - if (!empty($this->_params['variables']) && - is_array($this->_params['variables'])) { - foreach ($this->_params['variables'] as $key => $val) { - $this->addItem(new Maildrop_Variable(array('name' => $key, 'value' => $val))); - } - } - - foreach ($filters->getFilterList() as $filter) { - switch ($filter['action']) { - case Ingo_Storage::ACTION_BLACKLIST: - $this->generateBlacklist(!empty($filter['disable'])); - break; - - case Ingo_Storage::ACTION_WHITELIST: - $this->generateWhitelist(!empty($filter['disable'])); - break; - - case Ingo_Storage::ACTION_FORWARD: - $this->generateForward(!empty($filter['disable'])); - break; - - case Ingo_Storage::ACTION_VACATION: - $this->generateVacation(!empty($filter['disable'])); - break; - - case Ingo_Storage::ACTION_SPAM: - $this->generateSpamfilter(!empty($filter['disable'])); - break; - - default: - if (in_array($filter['action'], $this->_actions)) { - /* Create filter if using AND. */ - $recipe = new Maildrop_Recipe($filter, $this->_params); - foreach ($filter['conditions'] as $condition) { - $recipe->addCondition($condition); - } - $this->addItem(new Maildrop_Comment($filter['name'], !empty($filter['disable']), true)); - $this->addItem($recipe); - } - } - } - - return $this->toCode(); - } - - /** - * Generates the maildrop script to handle the blacklist specified in - * the rules. - * - * @param boolean $disable Disable the blacklist? - */ - function generateBlacklist($disable = false) - { - $blacklist = &$GLOBALS['ingo_storage']->retrieve(Ingo_Storage::ACTION_BLACKLIST); - $bl_addr = $blacklist->getBlacklist(); - $bl_folder = $blacklist->getBlacklistFolder(); - - $bl_type = (empty($bl_folder)) ? Ingo_Storage::ACTION_DISCARD : Ingo_Storage::ACTION_MOVE; - - if (!empty($bl_addr)) { - $this->addItem(new Maildrop_Comment(_("Blacklisted Addresses"), $disable, true)); - $params = array('action-value' => $bl_folder, - 'action' => $bl_type, - 'disable' => $disable); - - foreach ($bl_addr as $address) { - if (!empty($address)) { - $recipe = new Maildrop_Recipe($params, $this->_params); - $recipe->addCondition(array('field' => 'From', 'value' => $address)); - $this->addItem($recipe); - } - } - } - } - - /** - * Generates the maildrop script to handle the whitelist specified in - * the rules. - * - * @param boolean $disable Disable the whitelist? - */ - function generateWhitelist($disable = false) - { - $whitelist = &$GLOBALS['ingo_storage']->retrieve(Ingo_Storage::ACTION_WHITELIST); - $wl_addr = $whitelist->getWhitelist(); - - if (!empty($wl_addr)) { - $this->addItem(new Maildrop_Comment(_("Whitelisted Addresses"), $disable, true)); - foreach ($wl_addr as $address) { - if (!empty($address)) { - $recipe = new Maildrop_Recipe(array('action' => Ingo_Storage::ACTION_KEEP, 'disable' => $disable), $this->_params); - $recipe->addCondition(array('field' => 'From', 'value' => $address)); - $this->addItem($recipe); - } - } - } - } - - /** - * Generates the maildrop script to handle mail forwards. - * - * @param boolean $disable Disable forwarding? - */ - function generateForward($disable = false) - { - $forward = &$GLOBALS['ingo_storage']->retrieve(Ingo_Storage::ACTION_FORWARD); - $addresses = $forward->getForwardAddresses(); - - if (!empty($addresses)) { - $this->addItem(new Maildrop_Comment(_("Forwards"), $disable, true)); - $params = array('action' => Ingo_Storage::ACTION_FORWARD, - 'action-value' => $addresses, - 'disable' => $disable); - if ($forward->getForwardKeep()) { - $params['action'] = MAILDROP_STORAGE_ACTION_STOREANDFORWARD; - } - $recipe = new Maildrop_Recipe($params, $this->_params); - $recipe->addCondition(array('field' => 'From', 'value' => '')); - $this->addItem($recipe); - } - } - - /** - * Generates the maildrop script to handle vacation messages. - * - * @param boolean $disable Disable forwarding? - */ - function generateVacation($disable = false) - { - $vacation = &$GLOBALS['ingo_storage']->retrieve(Ingo_Storage::ACTION_VACATION); - $addresses = $vacation->getVacationAddresses(); - $actionval = array('addresses' => $addresses, - 'subject' => $vacation->getVacationSubject(), - 'days' => $vacation->getVacationDays(), - 'reason' => $vacation->getVacationReason(), - 'ignorelist' => $vacation->getVacationIgnorelist(), - 'excludes' => $vacation->getVacationExcludes(), - 'start' => $vacation->getVacationStart(), - 'end' => $vacation->getVacationEnd()); - - if (!empty($addresses)) { - $this->addItem(new Maildrop_Comment(_("Vacation"), $disable, true)); - $params = array('action' => Ingo_Storage::ACTION_VACATION, - 'action-value' => $actionval, - 'disable' => $disable); - $recipe = new Maildrop_Recipe($params, $this->_params); - $this->addItem($recipe); - } - } - - /** - * Generates the maildrop script to handle spam as identified by SpamAssassin - * - * @param boolean $disable Disable the spam-filter? - */ - function generateSpamfilter($disable = false) - { - $spam = &$GLOBALS['ingo_storage']->retrieve(Ingo_Storage::ACTION_SPAM); - if ($spam == false) { - return; - } - - $spam_folder = $spam->getSpamFolder(); - $spam_action = (empty($spam_folder)) ? Ingo_Storage::ACTION_DISCARD : Ingo_Storage::ACTION_MOVE; - - $this->addItem(new Maildrop_Comment(_("Spam Filter"), $disable, true)); - - $params = array('action-value' => $spam_folder, - 'action' => $spam_action, - 'disable' => $disable); - $recipe = new Maildrop_Recipe($params, $this->_params); - if ($GLOBALS['conf']['spam']['compare'] == 'numeric') { - $recipe->addCondition(array('match' => 'greater than or equal to', - 'field' => $GLOBALS['conf']['spam']['header'], - 'value' => $spam->getSpamLevel())); - } elseif ($GLOBALS['conf']['spam']['compare'] == 'string') { - $recipe->addCondition(array('match' => 'contains', - 'field' => $GLOBALS['conf']['spam']['header'], - 'value' => str_repeat($GLOBALS['conf']['spam']['char'], $spam->getSpamLevel()))); - } - - $this->addItem($recipe); - } - - /** - * Adds an item to the recipe list. - * - * @param object $item The item to add to the recipe list. - * The object should have a generate() function. - */ - function addItem($item) - { - $this->_recipes[] = $item; - } - -} - -/** - * The Maildrop_Comment:: class represents a maildrop comment. This is - * a pretty simple class, but it makes the code in Ingo_Script_maildrop:: - * cleaner as it provides a generate() function and can be added to the - * recipe list the same way as a recipe can be. - * - * @author Matt Weyland - * @since Ingo 1.1 - * @package Ingo - */ -class Maildrop_Comment { - - /** - * The comment text. - * - * @var string - */ - var $_comment = ''; - - /** - * Constructs a new maildrop comment. - * - * @param string $comment Comment to be generated. - * @param boolean $disable Output 'DISABLED' comment? - * @param boolean $header Output a 'header' comment? - */ - function Maildrop_Comment($comment, $disable = false, $header = false) - { - if ($disable) { - $comment = _("DISABLED: ") . $comment; - } - - if ($header) { - $this->_comment .= "##### $comment #####"; - } else { - $this->_comment .= "# $comment"; - } - } - - /** - * Returns the comment stored by this object. - * - * @return string The comment stored by this object. - */ - function generate() - { - return $this->_comment; - } - -} - -/** - * The Maildrop_Recipe:: class represents a maildrop recipe. - * - * @author Matt Weyland - * @since Ingo 1.1 - * @package Ingo - */ -class Maildrop_Recipe { - - var $_action = array(); - var $_conditions = array(); - var $_disable = ''; - var $_flags = ''; - var $_params = array(); - var $_combine = ''; - var $_valid = true; - - var $_operators = array( - 'less than' => '<', - 'less than or equal to' => '<=', - 'equal' => '==', - 'not equal' => '!=', - 'greater than' => '>', - 'greater than or equal to' => '>=', - ); - - /** - * Constructs a new maildrop recipe. - * - * @param array $params Array of parameters. - * REQUIRED FIELDS: - * 'action' - * OPTIONAL FIELDS: - * 'action-value' (only used if the - * 'action' requires it) - * @param array $scriptparams Array of parameters passed to - * Ingo_Script_maildrop::. - */ - function Maildrop_Recipe($params = array(), $scriptparams = array()) - { - $this->_disable = !empty($params['disable']); - $this->_params = $scriptparams; - $this->_action[] = 'exception {'; - - switch ($params['action']) { - case Ingo_Storage::ACTION_KEEP: - $this->_action[] = ' to "${DEFAULT}"'; - break; - - case Ingo_Storage::ACTION_MOVE: - $this->_action[] = ' to ' . $this->maildropPath($params['action-value']); - break; - - case Ingo_Storage::ACTION_DISCARD: - $this->_action[] = ' exit'; - break; - - case Ingo_Storage::ACTION_REDIRECT: - $this->_action[] = ' to "! ' . $params['action-value'] . '"'; - break; - - case Ingo_Storage::ACTION_REDIRECTKEEP: - $this->_action[] = ' cc "! ' . $params['action-value'] . '"'; - $this->_action[] = ' to "${DEFAULT}"'; - break; - - case Ingo_Storage::ACTION_REJECT: - $this->_action[] = ' EXITCODE=77'; # EX_NOPERM (permanent failure) - $this->_action[] = ' echo "5.7.1 ' . $params['action-value'] . '"'; - $this->_action[] = ' exit'; - break; - - case Ingo_Storage::ACTION_VACATION: - $from = ''; - foreach ($params['action-value']['addresses'] as $address) { - $from = $address; - } - - /** - * @TODO - * - * Exclusion and listfilter - */ - $exclude = ''; - foreach ($params['action-value']['excludes'] as $address) { - $exclude .= $address . ' '; - } - - $start = strftime($params['action-value']['start']); - if ($start === false) { - $start = 0; - } - $end = strftime($params['action-value']['end']); - if ($end === false) { - $end = 0; - } - $days = strftime($params['action-value']['days']); - if ($days === false) { - // Set to same value as $_days in ingo/lib/Storage.php - $days = 7; - } - - // Writing vacation.msg file - $reason = Horde_Mime::encode($params['action-value']['reason'], Horde_Nls::getCharset()); - $driver = Ingo::getDriver(); - $driver->_connect(); - $result = $driver->_vfs->writeData($driver->_params['vfs_path'], 'vacation.msg', $reason, true); - - // Rule : Do not send responses to bulk or list messages - if ($params['action-value']['ignorelist'] == 1) { - $params['combine'] = Ingo_Storage::COMBINE_ALL; - $this->addCondition(array('match' => 'filter', 'field' => '', 'value' => '! /^Precedence: (bulk|list|junk)/')); - $this->addCondition(array('match' => 'filter', 'field' => '', 'value' => '! /^Return-Path:.*<#@\[\]>/')); - $this->addCondition(array('match' => 'filter', 'field' => '', 'value' => '! /^Return-Path:.*<>/')); - $this->addCondition(array('match' => 'filter', 'field' => '', 'value' => '! /^From:.*MAILER-DAEMON/')); - $this->addCondition(array('match' => 'filter', 'field' => '', 'value' => '! /^X-ClamAV-Notice-Flag: *YES/')); - $this->addCondition(array('match' => 'filter', 'field' => '', 'value' => '! /^Content-Type:.*message\/delivery-status/')); - $this->addCondition(array('match' => 'filter', 'field' => '', 'value' => '! /^Subject:.*Delivery Status Notification/')); - $this->addCondition(array('match' => 'filter', 'field' => '', 'value' => '! /^Subject:.*Undelivered Mail Returned to Sender/')); - $this->addCondition(array('match' => 'filter', 'field' => '', 'value' => '! /^Subject:.*Delivery failure/')); - $this->addCondition(array('match' => 'filter', 'field' => '', 'value' => '! /^Subject:.*Message delay/')); - $this->addCondition(array('match' => 'filter', 'field' => '', 'value' => '! /^Subject:.*Mail Delivery Subsystem/')); - $this->addCondition(array('match' => 'filter', 'field' => '', 'value' => '! /^Subject:.*Mail System Error.*Returned Mail/')); - $this->addCondition(array('match' => 'filter', 'field' => '', 'value' => '! /^X-Spam-Flag: YES/ ')); - } else { - $this->addCondition(array('field' => 'From', 'value' => '')); - } - - // Rule : Start/End of vacation - if (($start != 0) && ($end !== 0)) { - $this->_action[] = ' flock "vacation.lock" {'; - $this->_action[] = ' current_time=time'; - $this->_action[] = ' if ( \ '; - $this->_action[] = ' ($current_time >= ' . $start . ') && \ '; - $this->_action[] = ' ($current_time <= ' . $end . ')) '; - $this->_action[] = ' {'; - } - $this->_action[] = " cc \"| mailbot -D " . $params['action-value']['days'] . " -c '" . Horde_Nls::getCharset() . "' -t \$HOME/vacation.msg -d \$HOME/vacation -A 'From: $from' -s '" . Horde_Mime::encode($params['action-value']['subject'], Horde_Nls::getCharset()) . "' /usr/sbin/sendmail -t \""; - if (($start != 0) && ($end !== 0)) { - $this->_action[] = ' }'; - $this->_action[] = ' }'; - } - - break; - - case Ingo_Storage::ACTION_FORWARD: - case MAILDROP_STORAGE_ACTION_STOREANDFORWARD: - foreach ($params['action-value'] as $address) { - if (!empty($address)) { - $this->_action[] = ' cc "! ' . $address . '"'; - } - } - - /* The 'to' must be the last action, because maildrop - * stops processing after it. */ - if ($params['action'] == MAILDROP_STORAGE_ACTION_STOREANDFORWARD) { - $this->_action[] = ' to "${DEFAULT}"'; - } else { - $this->_action[] = ' exit'; - } - break; - - default: - $this->_valid = false; - break; - } - - $this->_action[] = '}'; - - if (isset($params['combine']) && - ($params['combine'] == Ingo_Storage::COMBINE_ALL)) { - $this->_combine = '&& '; - } else { - $this->_combine = '|| '; - } - } - - /** - * Adds a flag to the recipe. - * - * @param string $flag String of flags to append to the current flags. - */ - function addFlag($flag) - { - $this->_flags .= $flag; - } - - /** - * Adds a condition to the recipe. - * - * @param optonal array $condition Array of parameters. Required keys - * are 'field' and 'value'. 'case' is - * an optional keys. - */ - function addCondition($condition = array()) - { - $flag = (!empty($condition['case'])) ? 'D' : ''; - if (empty($this->_conditions)) { - $this->addFlag($flag); - } - - $string = ''; - $extra = ''; - - $match = (isset($condition['match'])) ? $condition['match'] : null; - // negate tests starting with 'not ', except 'not equals', which simply uses the != operator - if ($match != 'not equal' && substr($match, 0, 4) == 'not ') { - $string .= '! '; - } - - // convert 'field' to PCRE pattern matching - if (strpos($condition['field'], ',') == false) { - $string .= '/^' . $condition['field'] . ':\\s*'; - } else { - $string .= '/^(' . str_replace(',', '|', $condition['field']) . '):\\s*'; - } - - switch ($match) { - case 'not regex': - case 'regex': - $string .= $condition['value'] . '/:h'; - break; - - case 'filter': - $string = $condition['value']; - break; - - case 'exists': - case 'not exist': - // Just run a match for the header name - $string .= '/:h'; - break; - - case 'less than or equal to': - case 'less than': - case 'equal': - case 'not equal': - case 'greater than or equal to': - case 'greater than': - $string .= '(\d+(\.\d+)?)/:h'; - $extra = ' && $MATCH1 ' . $this->_operators[$match] . ' ' . (int)$condition['value']; - break; - - case 'begins with': - case 'not begins with': - $string .= preg_quote($condition['value'], '/') . '/:h'; - break; - - case 'ends with': - case 'not ends with': - $string .= '.*' . preg_quote($condition['value'], '/') . '$/:h'; - break; - - case 'is': - case 'not is': - $string .= preg_quote($condition['value'], '/') . '$/:h'; - break; - - case 'matches': - case 'not matches': - $string .= str_replace(array('\\*', '\\?'), array('.*', '.'), preg_quote($condition['value'], '/') . '$') . '/:h'; - break; - - case 'contains': - case 'not contain': - default: - $string .= '.*' . preg_quote($condition['value'], '/') . '/:h'; - break; - } - - $this->_conditions[] = array('condition' => $string, 'flags' => $flag, 'extra' => $extra); - } - - /** - * Generates maildrop code to represent the recipe. - * - * @return string maildrop code to represent the recipe. - */ - function generate() - { - $text = array(); - - if (!$this->_valid) { - return ''; - } - - if (count($this->_conditions) > 0) { - - $text[] = "if( \\"; - - $nest = false; - foreach ($this->_conditions as $condition) { - $cond = $nest ? $this->_combine : ' '; - $text[] = $cond . $condition['condition'] . $condition['flags'] . $condition['extra'] . " \\"; - $nest = true; - } - - $text[] = ')'; - } - - foreach ($this->_action as $val) { - $text[] = $val; - } - - if ($this->_disable) { - $code = ''; - foreach ($text as $val) { - $comment = new Maildrop_Comment($val); - $code .= $comment->generate() . "\n"; - } - return $code . "\n"; - } else { - return implode("\n", $text) . "\n"; - } - } - - /** - * Returns a maildrop-ready mailbox path, converting IMAP folder pathname - * conventions as necessary. - * - * @param string $folder The IMAP folder name. - * - * @return string The maildrop mailbox path. - */ - function maildropPath($folder) - { - /* NOTE: '$DEFAULT' here is a literal, not a PHP variable. */ - if (isset($this->_params) && - ($this->_params['path_style'] == 'maildir')) { - if (empty($folder) || ($folder == 'INBOX')) { - return '"${DEFAULT}"'; - } - if ($this->_params['strip_inbox'] && - substr($folder, 0, 6) == 'INBOX.') { - $folder = substr($folder, 6); - } - return '"${DEFAULT}/.' . $folder . '/"'; - } else { - if (empty($folder) || ($folder == 'INBOX')) { - return '${DEFAULT}'; - } - return str_replace(' ', '\ ', $folder); - } - } - -} - -/** - * The Maildrop_Variable:: class represents a Maildrop variable. - * - * @author Matt Weyland - * @since Ingo 1.1 - * @package Ingo - */ -class Maildrop_Variable { - - var $_name; - var $_value; - - /** - * Constructs a new maildrop variable. - * - * @param array $params Array of parameters. Expected fields are 'name' - * and 'value'. - */ - function Maildrop_Variable($params = array()) - { - $this->_name = $params['name']; - $this->_value = $params['value']; - } - - /** - * Generates maildrop code to represent the variable. - * - * @return string maildrop code to represent the variable. - */ - function generate() - { - return $this->_name . '=' . $this->_value . "\n"; - } - -} diff --git a/ingo/lib/Script/procmail.php b/ingo/lib/Script/procmail.php deleted file mode 100644 index 494cfd260..000000000 --- a/ingo/lib/Script/procmail.php +++ /dev/null @@ -1,802 +0,0 @@ - - * @author Ben Chavet - * @package Ingo - */ -class Ingo_Script_procmail extends Ingo_Script { - - /** - * The list of actions allowed (implemented) for this driver. - * - * @var array - */ - var $_actions = array( - Ingo_Storage::ACTION_KEEP, - Ingo_Storage::ACTION_MOVE, - Ingo_Storage::ACTION_DISCARD, - Ingo_Storage::ACTION_REDIRECT, - Ingo_Storage::ACTION_REDIRECTKEEP, - Ingo_Storage::ACTION_REJECT - ); - - /** - * The categories of filtering allowed. - * - * @var array - */ - var $_categories = array( - Ingo_Storage::ACTION_BLACKLIST, - Ingo_Storage::ACTION_WHITELIST, - Ingo_Storage::ACTION_VACATION, - Ingo_Storage::ACTION_FORWARD - ); - - /** - * The types of tests allowed (implemented) for this driver. - * - * @var array - */ - var $_types = array( - Ingo_Storage::TYPE_HEADER, - Ingo_Storage::TYPE_BODY - ); - - /** - * A list of any special types that this driver supports. - * - * @var array - */ - var $_special_types = array( - 'Destination', - ); - - /** - * The list of tests allowed (implemented) for this driver. - * - * @var array - */ - var $_tests = array( - 'contains', - 'not contain', - 'begins with', - 'not begins with', - 'ends with', - 'not ends with', - 'regex' - ); - - /** - * Can tests be case sensitive? - * - * @var boolean - */ - var $_casesensitive = true; - - /** - * Does the driver support the stop-script option? - * - * @var boolean - */ - var $_supportStopScript = true; - - /** - * Does the driver require a script file to be generated? - * - * @var boolean - */ - var $_scriptfile = true; - - /** - * The recipes that make up the code. - * - * @var array - */ - var $_recipes = array(); - - /** - * Returns a script previously generated with generate(). - * - * @return string The procmail script. - */ - function toCode() - { - $code = ''; - foreach ($this->_recipes as $item) { - $code .= $item->generate() . "\n"; - } - - // If an external delivery program is used, add final rule - // to deliver to $DEFAULT - if (isset($this->_params['delivery_agent'])) { - $code .= ":0 w\n"; - $code .= isset($this->_params['delivery_mailbox_prefix']) ? - '| ' . $this->_params['delivery_agent'] . ' ' . $this->_params['delivery_mailbox_prefix'] . '$DEFAULT' : - '| ' . $this->_params['delivery_agent'] . ' $DEFAULT'; - } - - return rtrim($code) . "\n"; - } - - /** - * Generates the procmail script to do the filtering specified in the - * rules. - * - * @return string The procmail script. - */ - function generate() - { - $filters = &$GLOBALS['ingo_storage']->retrieve(Ingo_Storage::ACTION_FILTERS); - - $this->addItem(new Procmail_Comment(_("procmail script generated by Ingo") . ' (' . date('F j, Y, g:i a') . ')')); - - /* Add variable information, if present. */ - if (!empty($this->_params['variables']) && - is_array($this->_params['variables'])) { - foreach ($this->_params['variables'] as $key => $val) { - $this->addItem(new Procmail_Variable(array('name' => $key, 'value' => $val))); - } - } - - foreach ($filters->getFilterList() as $filter) { - switch ($filter['action']) { - case Ingo_Storage::ACTION_BLACKLIST: - $this->generateBlacklist(!empty($filter['disable'])); - break; - - case Ingo_Storage::ACTION_WHITELIST: - $this->generateWhitelist(!empty($filter['disable'])); - break; - - case Ingo_Storage::ACTION_VACATION: - $this->generateVacation(!empty($filter['disable'])); - break; - - case Ingo_Storage::ACTION_FORWARD: - $this->generateForward(!empty($filter['disable'])); - break; - - default: - if (in_array($filter['action'], $this->_actions)) { - /* Create filter if using AND. */ - if ($filter['combine'] == Ingo_Storage::COMBINE_ALL) { - $recipe = new Procmail_Recipe($filter, $this->_params); - if (!$filter['stop']) { - $recipe->addFlag('c'); - } - foreach ($filter['conditions'] as $condition) { - $recipe->addCondition($condition); - } - $this->addItem(new Procmail_Comment($filter['name'], !empty($filter['disable']), true)); - $this->addItem($recipe); - } else { - /* Create filter if using OR */ - $this->addItem(new Procmail_Comment($filter['name'], !empty($filter['disable']), true)); - $loop = 0; - foreach ($filter['conditions'] as $condition) { - $recipe = new Procmail_Recipe($filter, $this->_params); - if ($loop++) { - $recipe->addFlag('E'); - } - if (!$filter['stop']) { - $recipe->addFlag('c'); - } - $recipe->addCondition($condition); - $this->addItem($recipe); - } - } - } - } - } - - return $this->toCode(); - } - - /** - * Generates the procmail script to handle the blacklist specified in - * the rules. - * - * @param boolean $disable Disable the blacklist? - */ - function generateBlacklist($disable = false) - { - $blacklist = &$GLOBALS['ingo_storage']->retrieve(Ingo_Storage::ACTION_BLACKLIST); - $bl_addr = $blacklist->getBlacklist(); - $bl_folder = $blacklist->getBlacklistFolder(); - - $bl_type = (empty($bl_folder)) ? Ingo_Storage::ACTION_DISCARD : Ingo_Storage::ACTION_MOVE; - - if (!empty($bl_addr)) { - $this->addItem(new Procmail_Comment(_("Blacklisted Addresses"), $disable, true)); - $params = array('action-value' => $bl_folder, - 'action' => $bl_type, - 'disable' => $disable); - - foreach ($bl_addr as $address) { - if (!empty($address)) { - $recipe = new Procmail_Recipe($params, $this->_params); - $recipe->addCondition(array('field' => 'From', 'value' => $address, 'match' => 'address')); - $this->addItem($recipe); - } - } - } - } - - /** - * Generates the procmail script to handle the whitelist specified in - * the rules. - * - * @param boolean $disable Disable the whitelist? - */ - function generateWhitelist($disable = false) - { - $whitelist = &$GLOBALS['ingo_storage']->retrieve(Ingo_Storage::ACTION_WHITELIST); - $wl_addr = $whitelist->getWhitelist(); - - if (!empty($wl_addr)) { - $this->addItem(new Procmail_Comment(_("Whitelisted Addresses"), $disable, true)); - foreach ($wl_addr as $address) { - if (!empty($address)) { - $recipe = new Procmail_Recipe(array('action' => Ingo_Storage::ACTION_KEEP, 'disable' => $disable), $this->_params); - $recipe->addCondition(array('field' => 'From', 'value' => $address, 'match' => 'address')); - $this->addItem($recipe); - } - } - } - } - - /** - * Generates the procmail script to handle vacation. - * - * @param boolean $disable Disable vacation? - */ - function generateVacation($disable = false) - { - $vacation = &$GLOBALS['ingo_storage']->retrieve(Ingo_Storage::ACTION_VACATION); - $addresses = $vacation->getVacationAddresses(); - $actionval = array( - 'addresses' => $addresses, - 'subject' => $vacation->getVacationSubject(), - 'days' => $vacation->getVacationDays(), - 'reason' => $vacation->getVacationReason(), - 'ignorelist' => $vacation->getVacationIgnorelist(), - 'excludes' => $vacation->getVacationExcludes(), - 'start' => $vacation->getVacationStart(), - 'end' => $vacation->getVacationEnd(), - ); - - if (!empty($addresses)) { - $this->addItem(new Procmail_Comment(_("Vacation"), $disable, true)); - $params = array('action' => Ingo_Storage::ACTION_VACATION, - 'action-value' => $actionval, - 'disable' => $disable); - $recipe = new Procmail_Recipe($params, $this->_params); - $this->addItem($recipe); - } - } - - /** - * Generates the procmail script to handle mail forwards. - * - * @param boolean $disable Disable forwarding? - */ - function generateForward($disable = false) - { - $forward = &$GLOBALS['ingo_storage']->retrieve(Ingo_Storage::ACTION_FORWARD); - $addresses = $forward->getForwardAddresses(); - - if (!empty($addresses)) { - $this->addItem(new Procmail_Comment(_("Forwards"), $disable, true)); - $params = array('action' => Ingo_Storage::ACTION_FORWARD, - 'action-value' => $addresses, - 'disable' => $disable); - $recipe = new Procmail_Recipe($params, $this->_params); - if ($forward->getForwardKeep()) { - $recipe->addFlag('c'); - } - $this->addItem($recipe); - } - } - - /** - * Adds an item to the recipe list. - * - * @param object $item The item to add to the recipe list. - * The object should have a generate() function. - */ - function addItem($item) - { - $this->_recipes[] = $item; - } - -} - -/** - * The Procmail_Comment:: class represents a Procmail comment. This is - * a pretty simple class, but it makes the code in Ingo_Script_procmail:: - * cleaner as it provides a generate() function and can be added to the - * recipe list the same way as a recipe can be. - * - * @author Ben Chavet - * @since Ingo 1.0 - * @package Ingo - */ -class Procmail_Comment { - - /** - * The comment text. - * - * @var string - */ - var $_comment = ''; - - /** - * Constructs a new procmail comment. - * - * @param string $comment Comment to be generated. - * @param boolean $disable Output 'DISABLED' comment? - * @param boolean $header Output a 'header' comment? - */ - function Procmail_Comment($comment, $disable = false, $header = false) - { - if ($disable) { - $comment = _("DISABLED: ") . $comment; - } - - if ($header) { - $this->_comment .= "##### $comment #####"; - } else { - $this->_comment .= "# $comment"; - } - } - - /** - * Returns the comment stored by this object. - * - * @return string The comment stored by this object. - */ - function generate() - { - return $this->_comment; - } - -} - -/** - * The Procmail_Recipe:: class represents a Procmail recipe. - * - * @author Ben Chavet - * @since Ingo 1.0 - * @package Ingo - */ -class Procmail_Recipe { - - var $_action = array(); - var $_conditions = array(); - var $_disable = ''; - var $_flags = ''; - var $_params = array( - 'date' => 'date', - 'echo' => 'echo', - 'ls' => 'ls' - ); - var $_valid = true; - - /** - * Constructs a new procmail recipe. - * - * @param array $params Array of parameters. - * REQUIRED FIELDS: - * 'action' - * OPTIONAL FIELDS: - * 'action-value' (only used if the - * 'action' requires it) - * @param array $scriptparams Array of parameters passed to - * Ingo_Script_procmail::. - */ - function Procmail_Recipe($params = array(), $scriptparams = array()) - { - $this->_disable = !empty($params['disable']); - $this->_params = array_merge($this->_params, $scriptparams); - - switch ($params['action']) { - case Ingo_Storage::ACTION_KEEP: - // Note: you may have to set the DEFAULT variable in your - // backend configuration. - if (isset($this->_params['delivery_agent']) && isset($this->_params['delivery_mailbox_prefix'])) { - $this->_action[] = '| ' . $this->_params['delivery_agent'] . ' ' . $this->_params['delivery_mailbox_prefix'] . '$DEFAULT'; - } elseif (isset($this->_params['delivery_agent'])) { - $this->_action[] = '| ' . $this->_params['delivery_agent'] . ' $DEFAULT'; - } else { - $this->_action[] = '$DEFAULT'; - } - break; - - case Ingo_Storage::ACTION_MOVE: - if (isset($this->_params['delivery_agent']) && isset($this->_params['delivery_mailbox_prefix'])) { - $this->_action[] = '| ' . $this->_params['delivery_agent'] . ' ' . $this->_params['delivery_mailbox_prefix'] . $this->procmailPath($params['action-value']); - } elseif (isset($this->_params['delivery_agent'])) { - $this->_action[] = '| ' . $this->_params['delivery_agent'] . ' ' . $this->procmailPath($params['action-value']); - } else { - $this->_action[] = $this->procmailPath($params['action-value']); - } - break; - - case Ingo_Storage::ACTION_DISCARD: - $this->_action[] = '/dev/null'; - break; - - case Ingo_Storage::ACTION_REDIRECT: - $this->_action[] = '! ' . $params['action-value']; - break; - - case Ingo_Storage::ACTION_REDIRECTKEEP: - $this->_action[] = '{'; - $this->_action[] = ' :0 c'; - $this->_action[] = ' ! ' . $params['action-value']; - $this->_action[] = ''; - $this->_action[] = ' :0' . (isset($this->_params['delivery_agent']) ? ' w' : ''); - if (isset($this->_params['delivery_agent']) && isset($this->_params['delivery_mailbox_prefix'])) { - $this->_action[] = ' | ' . $this->_params['delivery_agent'] . ' ' . $this->_params['delivery_mailbox_prefix'] . '$DEFAULT'; - } elseif (isset($this->_params['delivery_agent'])) { - $this->_action[] = ' | ' . $this->_params['delivery_agent'] . ' $DEFAULT'; - } else { - $this->_action[] = ' $DEFAULT'; - } - $this->_action[] = '}'; - break; - - case Ingo_Storage::ACTION_REJECT: - $this->_action[] = '{'; - $this->_action[] = ' EXITCODE=' . $params['action-value']; - $this->_action[] = ' HOST="no.address.here"'; - $this->_action[] = '}'; - break; - - case Ingo_Storage::ACTION_VACATION: - $days = $params['action-value']['days']; - $timed = !empty($params['action-value']['start']) && - !empty($params['action-value']['end']); - $this->_action[] = '{'; - foreach ($params['action-value']['addresses'] as $address) { - if (!empty($address)) { - $this->_action[] = ' FILEDATE=`test -f ${VACATION_DIR:-.}/\'.vacation.' . $address . '\' && ' - . $this->_params['ls'] . ' -lcn --time-style=+%s ${VACATION_DIR:-.}/\'.vacation.' . $address . '\' | ' - . 'awk \'{ print $6 + (' . $days * 86400 . ') }\'`'; - $this->_action[] = ' DATE=`' . $this->_params['date'] . ' +%s`'; - $this->_action[] = ' DUMMY=`test -f ${VACATION_DIR:-.}/\'.vacation.' . $address . '\' && ' - . 'test $FILEDATE -le $DATE && ' - . 'rm ${VACATION_DIR:-.}/\'.vacation.' . $address . '\'`'; - if ($timed) { - $this->_action[] = ' START=' . $params['action-value']['start']; - $this->_action[] = ' END=' . $params['action-value']['end']; - } - $this->_action[] = ''; - $this->_action[] = ' :0 h'; - $this->_action[] = ' SUBJECT=| formail -xSubject:'; - $this->_action[] = ''; - $this->_action[] = ' :0 Whc: ${VACATION_DIR:-.}/vacation.lock'; - if ($timed) { - $this->_action[] = ' * ? test $DATE -gt $START && test $END -gt $DATE'; - $this->_action[] = ' {'; - $this->_action[] = ' :0 Whc'; - } - $this->_action[] = ' * ^TO_' . $address; - $this->_action[] = ' * !^X-Loop: ' . $address; - $this->_action[] = ' * !^X-Spam-Flag: YES'; - if (count($params['action-value']['excludes']) > 0) { - foreach ($params['action-value']['excludes'] as $exclude) { - if (!empty($exclude)) { - $this->_action[] = ' * !^From.*' . $exclude; - } - } - } - if ($params['action-value']['ignorelist']) { - $this->_action[] = ' * !^FROM_DAEMON'; - } - $this->_action[] = ' | formail -rD 8192 ${VACATION_DIR:-.}/.vacation.' . $address; - $this->_action[] = ' :0 ehc'; - $this->_action[] = ' | (formail -rI"Precedence: junk" \\'; - $this->_action[] = ' -a"From: <' . $address . '>" \\'; - $this->_action[] = ' -A"X-Loop: ' . $address . '" \\'; - if (Horde_Mime::is8bit($params['action-value']['reason'])) { - $this->_action[] = ' -i"Subject: ' . Horde_Mime::encode($params['action-value']['subject'] . ' (Re: $SUBJECT)', Horde_Nls::getCharset()) . '" \\'; - $this->_action[] = ' -i"Content-Transfer-Encoding: quoted-printable" \\'; - $this->_action[] = ' -i"Content-Type: text/plain; charset=' . Horde_Nls::getCharset() . '" ; \\'; - $reason = Horde_Mime::quotedPrintableEncode($params['action-value']['reason'], "\n"); - } else { - $this->_action[] = ' -i"Subject: ' . Horde_Mime::encode($params['action-value']['subject'] . ' (Re: $SUBJECT)', Horde_Nls::getCharset()) . '" ; \\'; - $reason = $params['action-value']['reason']; - } - $reason = addcslashes($reason, "\\\n\r\t\"`"); - $this->_action[] = ' ' . $this->_params['echo'] . ' -e "' . $reason . '" \\'; - $this->_action[] = ' ) | $SENDMAIL -f' . $address . ' -oi -t'; - $this->_action[] = ''; - $this->_action[] = ' :0'; - $this->_action[] = ' /dev/null'; - if ($timed) { - $this->_action[] = ' }'; - } - } - } - $this->_action[] = '}'; - break; - - case Ingo_Storage::ACTION_FORWARD: - /* Make sure that we prevent mail loops using 3 methods. - * - * First, we call sendmail -f to set the envelope sender to be the - * same as the original sender, so bounces will go to the original - * sender rather than to us. This unfortunately triggers lots of - * Authentication-Warning: messages in sendmail's logs. - * - * Second, add an X-Loop header, to handle the case where the - * address we forward to forwards back to us. - * - * Third, don't forward mailer daemon messages (i.e., bounces). - * Method 1 above should make this redundant, unless we're sending - * mail from this account and have a bad forward-to account. - * - * Get the from address, saving a call to formail if possible. - * The procmail code for doing this is borrowed from the - * Procmail Library Project, http://pm-lib.sourceforge.net/. - * The Ingo project has the permission to use Procmail Library code - * under Apache licence v 1.x or any later version. - * Permission obtained 2006-04-04 from Author Jari Aalto. */ - $this->_action[] = '{'; - $this->_action[] = ' :0 '; - $this->_action[] = ' *$ ! ^From *\/[^ ]+'; - $this->_action[] = ' *$ ! ^Sender: *\/[^ ]+'; - $this->_action[] = ' *$ ! ^From: *\/[^ ]+'; - $this->_action[] = ' *$ ! ^Reply-to: *\/[^ ]+'; - $this->_action[] = ' {'; - $this->_action[] = ' OUTPUT = `formail -zxFrom:`'; - $this->_action[] = ' }'; - $this->_action[] = ' :0 E'; - $this->_action[] = ' {'; - $this->_action[] = ' OUTPUT = $MATCH'; - $this->_action[] = ' }'; - $this->_action[] = ''; - - /* Forward to each address on our list. */ - foreach ($params['action-value'] as $address) { - if (!empty($address)) { - $this->_action[] = ' :0 c'; - $this->_action[] = ' * !^FROM_MAILER'; - $this->_action[] = ' * !^X-Loop: to-' . $address; - $this->_action[] = ' | formail -A"X-Loop: to-' . $address . '" | $SENDMAIL -oi -f $OUTPUT ' . $address; - } - } - - /* In case of mail loop or bounce, store a copy locally. Note - * that if we forward to more than one address, only a mail loop - * on the last address will cause a local copy to be saved. TODO: - * The next two lines are redundant (and create an extra copy of - * the message) if "Keep a copy of messages in this account" is - * checked. */ - $this->_action[] = ' :0 E' . (isset($this->_params['delivery_agent']) ? 'w' : ''); - if (isset($this->_params['delivery_agent'])) { - $this->_action[] = isset($this->_params['delivery_mailbox_prefix']) ? - ' | ' . $this->_params['delivery_agent'] . ' ' . $this->_params['delivery_mailbox_prefix'] . '$DEFAULT' : - ' | ' . $this->_params['delivery_agent'] . ' $DEFAULT'; - } else { - $this->_action[] = ' $DEFAULT'; - } - $this->_action[] = ' :0 '; - $this->_action[] = ' /dev/null'; - $this->_action[] = '}'; - break; - - default: - $this->_valid = false; - break; - } - } - - /** - * Adds a flag to the recipe. - * - * @param string $flag String of flags to append to the current flags. - */ - function addFlag($flag) - { - $this->_flags .= $flag; - } - - /** - * Adds a condition to the recipe. - * - * @param array $condition Array of parameters. Required keys are 'field' - * and 'value'. 'case' is an optional key. - */ - function addCondition($condition = array()) - { - $flag = !empty($condition['case']) ? 'D' : ''; - $match = isset($condition['match']) ? $condition['match'] : null; - $string = ''; - $prefix = ''; - - switch ($condition['field']) { - case 'Destination': - $string = '^TO_'; - break; - - case 'Body': - $flag .= 'B'; - break; - - default: - // convert 'field' to PCRE pattern matching - if (strpos($condition['field'], ',') == false) { - $string = '^' . $condition['field'] . ':'; - } else { - $string .= '/^(' . str_replace(',', '|', $condition['field']) . '):'; - } - $prefix = ' '; - } - - $reverseCondition = false; - switch ($match) { - case 'regex': - $string .= $prefix . $condition['value']; - break; - - case 'address': - $string .= '(.*\<)?' . quotemeta($condition['value']); - break; - - case 'not begins with': - $reverseCondition = true; - // fall through - case 'begins with': - $string .= $prefix . quotemeta($condition['value']); - break; - - case 'not ends with': - $reverseCondition = true; - // fall through - case 'ends with': - $string .= '.*' . quotemeta($condition['value']) . '$'; - break; - - case 'not contain': - $reverseCondition = true; - // fall through - case 'contains': - default: - $string .= '.*' . quotemeta($condition['value']); - break; - } - - $this->_conditions[] = array('condition' => ($reverseCondition ? '* !' : '* ') . $string, - 'flags' => $flag); - } - - /** - * Generates procmail code to represent the recipe. - * - * @return string Procmail code to represent the recipe. - */ - function generate() - { - $nest = 0; - $prefix = ''; - $text = array(); - - if (!$this->_valid) { - return ''; - } - - // Set the global flags for the whole rule, each condition - // will add its own (such as Body or Case Sensitive) - $global = $this->_flags; - if (isset($this->_conditions[0])) { - $global .= $this->_conditions[0]['flags']; - } - $text[] = ':0 ' . $global . (isset($this->_params['delivery_agent']) ? 'w' : ''); - foreach ($this->_conditions as $condition) { - if ($nest > 0) { - $text[] = str_repeat(' ', $nest - 1) . '{'; - $text[] = str_repeat(' ', $nest) . ':0 ' . $condition['flags']; - $text[] = str_repeat(' ', $nest) . $condition['condition']; - } else { - $text[] = $condition['condition']; - } - $nest++; - } - - if (--$nest > 0) { - $prefix = str_repeat(' ', $nest); - } - foreach ($this->_action as $val) { - $text[] = $prefix . $val; - } - - for ($i = $nest; $i > 0; $i--) { - $text[] = str_repeat(' ', $i - 1) . '}'; - } - - if ($this->_disable) { - $code = ''; - foreach ($text as $val) { - $comment = new Procmail_Comment($val); - $code .= $comment->generate() . "\n"; - } - return $code . "\n"; - } else { - return implode("\n", $text) . "\n"; - } - } - - /** - * Returns a procmail-ready mailbox path, converting IMAP folder - * pathname conventions as necessary. - * - * @param string $folder The IMAP folder name. - * - * @return string The procmail mailbox path. - */ - function procmailPath($folder) - { - /* NOTE: '$DEFAULT' here is a literal, not a PHP variable. */ - if (isset($this->_params) && - ($this->_params['path_style'] == 'maildir')) { - if (empty($folder) || ($folder == 'INBOX')) { - return '$DEFAULT'; - } - if (substr($folder, 0, 6) == 'INBOX.') { - $folder = substr($folder, 6); - } - return '"$DEFAULT/.' . escapeshellcmd($folder) . '/"'; - } else { - if (empty($folder) || ($folder == 'INBOX')) { - return '$DEFAULT'; - } - return str_replace(' ', '\ ', escapeshellcmd($folder)); - } - } - -} - -/** - * The Procmail_Variable:: class represents a Procmail variable. - * - * @author Michael Slusarz - * @since Ingo 1.0 - * @package Ingo - */ -class Procmail_Variable { - - var $_name; - var $_value; - - /** - * Constructs a new procmail variable. - * - * @param array $params Array of parameters. Expected fields are 'name' - * and 'value'. - */ - function Procmail_Variable($params = array()) - { - $this->_name = $params['name']; - $this->_value = $params['value']; - } - - /** - * Generates procmail code to represent the variable. - * - * @return string Procmail code to represent the variable. - */ - function generate() - { - return $this->_name . '=' . $this->_value . "\n"; - } - -} diff --git a/ingo/lib/Script/sieve.php b/ingo/lib/Script/sieve.php deleted file mode 100644 index 75395424d..000000000 --- a/ingo/lib/Script/sieve.php +++ /dev/null @@ -1,2976 +0,0 @@ - - * @package Ingo - */ -class Ingo_Script_sieve extends Ingo_Script { - - /** - * The list of actions allowed (implemented) for this driver. - * - * @var array - */ - var $_actions = array( - Ingo_Storage::ACTION_KEEP, - Ingo_Storage::ACTION_MOVE, - Ingo_Storage::ACTION_DISCARD, - Ingo_Storage::ACTION_REDIRECT, - Ingo_Storage::ACTION_REDIRECTKEEP, - Ingo_Storage::ACTION_MOVEKEEP, - Ingo_Storage::ACTION_REJECT, - Ingo_Storage::ACTION_FLAGONLY, - Ingo_Storage::ACTION_NOTIFY - ); - - /** - * The categories of filtering allowed. - * - * @var array - */ - var $_categories = array( - Ingo_Storage::ACTION_BLACKLIST, - Ingo_Storage::ACTION_WHITELIST, - Ingo_Storage::ACTION_VACATION, - Ingo_Storage::ACTION_FORWARD, - Ingo_Storage::ACTION_SPAM - ); - - /** - * The list of tests allowed (implemented) for this driver. - * - * @var array - */ - var $_tests = array( - 'contains', 'not contain', 'is', 'not is', 'begins with', - 'not begins with', 'ends with', 'not ends with', 'exists', 'not exist', - 'less than', 'less than or equal to', 'equal', 'not equal', - 'greater than', 'greater than or equal to', 'regex', 'matches', - 'not matches' - ); - - /** - * The types of tests allowed (implemented) for this driver. - * - * @var array - */ - var $_types = array( - Ingo_Storage::TYPE_HEADER, - Ingo_Storage::TYPE_SIZE, - Ingo_Storage::TYPE_BODY - ); - - /** - * Can tests be case sensitive? - * - * @var boolean - */ - var $_casesensitive = true; - - /** - * Does the driver support setting IMAP flags? - * - * @var boolean - */ - var $_supportIMAPFlags = true; - - /** - * Does the driver support the stop-script option? - * - * @var boolean - */ - var $_supportStopScript = true; - - /** - * Does the driver require a script file to be generated? - * - * @var boolean - */ - var $_scriptfile = true; - - /** - * The blocks that make up the code. - * - * @var array - */ - var $_blocks = array(); - - /** - * The blocks that have to appear at the end of the code. - * - * @var array - */ - var $_endBlocks = array(); - - /** - * Returns a script previously generated with generate(). - * - * @return string The Sieve script. - */ - function toCode() - { - $date_format = $GLOBALS['prefs']->getValue('date_format'); - // %R and %r don't work on Windows, but who runs a Sieve backend on a - // Windows server? - $time_format = $GLOBALS['prefs']->getValue('twentyFour') ? '%R' : '%r'; - $code = "# Sieve Filter\n# " - . _("Generated by Ingo (http://www.horde.org/ingo/)") . ' (' - . trim(strftime($date_format . ', ' . $time_format)) - . ")\n\n"; - $code = Horde_String::convertCharset($code, Horde_Nls::getCharset(), 'UTF-8'); - $requires = $this->requires(); - - if (count($requires) > 1) { - $stringlist = ''; - foreach ($this->requires() as $require) { - $stringlist .= (empty($stringlist)) ? '"' : ', "'; - $stringlist .= $require . '"'; - } - $code .= 'require [' . $stringlist . '];' . "\n\n"; - } elseif (count($requires) == 1) { - foreach ($this->requires() as $require) { - $code .= 'require "' . $require . '";' . "\n\n"; - } - } - - foreach ($this->_blocks as $block) { - $code .= $block->toCode() . "\n"; - } - - return rtrim($code) . "\n"; - } - - /** - * Escape a string according to Sieve RFC 3028 [2.4.2]. - * - * @param string $string The string to escape. - * @param boolean $regexmode Is the escaped string a regex value? - * Defaults to no. - * - * @return string The escaped string. - */ - function escapeString($string, $regexmode = false) - { - /* Remove any backslashes in front of commas. */ - $string = str_replace('\,', ',', $string); - - if ($regexmode) { - return str_replace('"', addslashes('"'), $string); - } else { - return str_replace(array('\\', '"'), array(addslashes('\\'), addslashes('"')), $string); - } - } - - /** - * Checks if all rules are valid. - * - * @return boolean|string True if all rules are valid, an error message - * otherwise. - */ - function check() - { - foreach ($this->_blocks as $block) { - $res = $block->check(); - if ($res !== true) { - return $res; - } - } - - return true; - } - - /** - * Returns a list of sieve extensions required for this rule and any - * sub-rules. - * - * @return array A Sieve extension list. - */ - function requires() - { - $requires = array(); - foreach ($this->_blocks as $block) { - $requires = array_merge($requires, $block->requires()); - } - - return array_unique($requires); - } - - /** - * Adds all blocks necessary for the forward rule. - */ - function _addForwardBlocks() - { - if (!$this->_validRule(Ingo_Storage::ACTION_FORWARD)) { - return; - } - - $forward = &$GLOBALS['ingo_storage']->retrieve(Ingo_Storage::ACTION_FORWARD); - $fwd_addr = $forward->getForwardAddresses(); - if (empty($fwd_addr)) { - return; - } - - $action = array(); - foreach ($fwd_addr as $addr) { - $addr = trim($addr); - if (!empty($addr)) { - $action[] = new Sieve_Action_Redirect(array('address' => $addr)); - } - } - - if (count($action)) { - if($forward->getForwardKeep()) { - $this->_endBlocks[] = new Sieve_Comment(_("Forward Keep Action")); - $if = new Sieve_If(new Sieve_Test_True()); - $if->setActions(array(new Sieve_Action_Keep(), - new Sieve_Action_Stop())); - $this->_endBlocks[] = $if; - } else { - $action[] = new Sieve_Action_Stop(); - } - } - - $this->_blocks[] = new Sieve_Comment(_("Forwards")); - - $test = new Sieve_Test_True(); - $if = new Sieve_If($test); - $if->setActions($action); - $this->_blocks[] = $if; - } - - /** - * Adds all blocks necessary for the blacklist rule. - */ - function _addBlacklistBlocks() - { - if (!$this->_validRule(Ingo_Storage::ACTION_BLACKLIST)) { - return; - } - - $blacklist = &$GLOBALS['ingo_storage']->retrieve(Ingo_Storage::ACTION_BLACKLIST); - $bl_addr = $blacklist->getBlacklist(); - $folder = $blacklist->getBlacklistFolder(); - if (empty($bl_addr)) { - return; - } - - $action = array(); - if (empty($folder)) { - $action[] = new Sieve_Action_Discard(); - } elseif ($folder == INGO_BLACKLIST_MARKER) { - $action[] = new Sieve_Action_Addflag(array('flags' => Ingo_Storage::FLAG_DELETED)); - $action[] = new Sieve_Action_Keep(); - $action[] = new Sieve_Action_Removeflag(array('flags' => Ingo_Storage::FLAG_DELETED)); - } else { - $action[] = new Sieve_Action_Fileinto(array('folder' => $folder)); - } - - $action[] = new Sieve_Action_Stop(); - - $this->_blocks[] = new Sieve_Comment(_("Blacklisted Addresses")); - - /* Split the test up to only do 5 addresses at a time. */ - $temp = array(); - $wildcards = array(); - foreach ($bl_addr as $address) { - if (!empty($address)) { - if ((strstr($address, '*') !== false) || - (strstr($address, '?') !== false)) { - $wildcards[] = $address; - } else { - $temp[] = $address; - } - } - if (count($temp) == 5) { - $test = new Sieve_Test_Address(array('headers' => "From\nSender\nResent-From", 'addresses' => implode("\n", $temp))); - $if = new Sieve_If($test); - $if->setActions($action); - $this->_blocks[] = $if; - $temp = array(); - } - if (count($wildcards) == 5) { - $test = new Sieve_Test_Address(array('headers' => "From\nSender\nResent-From", 'match-type' => ':matches', 'addresses' => implode("\n", $wildcards))); - $if = new Sieve_If($test); - $if->setActions($action); - $this->_blocks[] = $if; - $wildcards = array(); - } - } - - if ($temp) { - $test = new Sieve_Test_Address(array('headers' => "From\nSender\nResent-From", 'addresses' => implode("\n", $temp))); - $if = new Sieve_If($test); - $if->setActions($action); - $this->_blocks[] = $if; - } - - if ($wildcards) { - $test = new Sieve_Test_Address(array('headers' => "From\nSender\nResent-From", 'match-type' => ':matches', 'addresses' => implode("\n", $wildcards))); - $if = new Sieve_If($test); - $if->setActions($action); - $this->_blocks[] = $if; - } - } - - /** - * Adds all blocks necessary for the whitelist rule. - */ - function _addWhitelistBlocks() - { - if (!$this->_validRule(Ingo_Storage::ACTION_WHITELIST)) { - return; - } - - $whitelist = &$GLOBALS['ingo_storage']->retrieve(Ingo_Storage::ACTION_WHITELIST); - $wl_addr = $whitelist->getWhitelist(); - if (empty($wl_addr)) { - return; - } - - $this->_blocks[] = new Sieve_Comment(_("Whitelisted Addresses")); - - $action = array(new Sieve_Action_Keep(), new Sieve_Action_Stop()); - $test = new Sieve_Test_Address(array('headers' => "From\nSender\nResent-From", 'addresses' => implode("\n", $wl_addr))); - $if = new Sieve_If($test); - $if->setActions($action); - $this->_blocks[] = $if; - } - - /** - * Adds all blocks necessary for the vacation rule. - */ - function _addVacationBlocks() - { - if (!$this->_validRule(Ingo_Storage::ACTION_VACATION)) { - return; - } - - $vacation = &$GLOBALS['ingo_storage']->retrieve(Ingo_Storage::ACTION_VACATION); - $vacation_addr = $vacation->getVacationAddresses(); - if (!count($vacation_addr)) { - return; - } - - $vals = array( - 'subject' => Horde_String::convertCharset($vacation->getVacationSubject(), Horde_Nls::getCharset(), 'UTF-8'), - 'days' => $vacation->getVacationDays(), - 'addresses' => $vacation_addr, - 'start' => $vacation->getVacationStart(), - 'start_year' => $vacation->getVacationStartYear(), - 'start_month' => $vacation->getVacationStartMonth(), - 'start_day' => $vacation->getVacationStartDay(), - 'end' => $vacation->getVacationEnd(), - 'end_year' => $vacation->getVacationEndYear(), - 'end_month' => $vacation->getVacationEndMonth(), - 'end_day' => $vacation->getVacationEndDay(), - 'reason' => Horde_String::convertCharset($vacation->getVacationReason(), Horde_Nls::getCharset(), 'UTF-8'), - ); - - $action = $tests = array(); - $action[] = new Sieve_Action_Vacation($vals); - - if ($vacation->getVacationIgnorelist()) { - $mime_headers = new Horde_Mime_Headers(); - $headers = $mime_headers->listHeaders(); - $headers['Mailing-List'] = null; - $tmp = new Sieve_Test_Exists(array('headers' => implode("\n", array_keys($headers)))); - $tests[] = new Sieve_Test_Not($tmp); - $vals = array('headers' => 'Precedence', - 'match-type' => ':is', - 'strings' => "list\nbulk\njunk", - 'comparator' => 'i;ascii-casemap'); - $tmp = new Sieve_Test_Header($vals); - $tests[] = new Sieve_Test_Not($tmp); - $vals = array('headers' => 'To', - 'match-type' => ':matches', - 'strings' => 'Multiple recipients of*', - 'comparator' => 'i;ascii-casemap'); - $tmp = new Sieve_Test_Header($vals); - $tests[] = new Sieve_Test_Not($tmp); - } - - $addrs = array(); - foreach ($vacation->getVacationExcludes() as $addr) { - $addr = trim($addr); - if (!empty($addr)) { - $addrs[] = $addr; - } - } - - if ($addrs) { - $tmp = new Sieve_Test_Address(array('headers' => "From\nSender\nResent-From", 'addresses' => implode("\n", $addrs))); - $tests[] = new Sieve_Test_Not($tmp); - } - - $this->_blocks[] = new Sieve_Comment(_("Vacation")); - - if ($tests) { - $test = new Sieve_Test_Allof($tests); - $if = new Sieve_If($test); - $if->setActions($action); - $this->_blocks[] = $if; - } else { - $this->_blocks[] = $action[0]; - } - } - - /** - * Adds all blocks necessary for the spam rule. - */ - function _addSpamBlocks() - { - if (!$this->_validRule(Ingo_Storage::ACTION_SPAM)) { - return; - } - - $spam = &$GLOBALS['ingo_storage']->retrieve(Ingo_Storage::ACTION_SPAM); - if ($spam === false) { - return; - } - - $this->_blocks[] = new Sieve_Comment(_("Spam Filter")); - - $actions = array(); - $actions[] = new Sieve_Action_Fileinto(array( - 'folder' => $spam->getSpamFolder() - )); - - if ($GLOBALS['conf']['spam']['compare'] == 'numeric') { - $vals = array( - 'headers' => $GLOBALS['conf']['spam']['header'], - 'comparison' => 'ge', - 'value' => $spam->getSpamLevel(), - ); - $test = new Sieve_Test_Relational($vals); - } elseif ($GLOBALS['conf']['spam']['compare'] == 'string') { - $vals = array( - 'headers' => $GLOBALS['conf']['spam']['header'], - 'match-type' => ':contains', - 'strings' => str_repeat($GLOBALS['conf']['spam']['char'], - $spam->getSpamLevel()), - 'comparator' => 'i;ascii-casemap', - ); - $test = new Sieve_Test_Header($vals); - } - - $actions[] = new Sieve_Action_Stop(); - - $if = new Sieve_If($test); - $if->setActions($actions); - $this->_blocks[] = $if; - } - - /** - * Generates the Sieve script to do the filtering specified in - * the rules. - * - * @return string The Sieve script. - */ - function generate() - { - global $ingo_storage; - - $filters = &$ingo_storage->retrieve(Ingo_Storage::ACTION_FILTERS); - foreach ($filters->getFilterList() as $filter) { - /* Check to make sure this is a valid rule and that the rule - is not disabled. */ - if (!$this->_validRule($filter['action']) || - !empty($filter['disable'])) { - continue; - } - - $action = array(); - switch ($filter['action']) { - case Ingo_Storage::ACTION_KEEP: - if (!empty($filter['flags'])) { - $action[] = new Sieve_Action_Addflag(array('flags' => $filter['flags'])); - } - - $action[] = new Sieve_Action_Keep(); - - if (!empty($filter['flags'])) { - $action[] = new Sieve_Action_RemoveFlag(array('flags' => $filter['flags'])); - } - break; - - case Ingo_Storage::ACTION_DISCARD: - $action[] = new Sieve_Action_Discard(); - break; - - case Ingo_Storage::ACTION_MOVE: - if (!empty($filter['flags'])) { - $action[] = new Sieve_Action_Addflag(array('flags' => $filter['flags'])); - } - - $action[] = new Sieve_Action_Fileinto(array('folder' => $filter['action-value'])); - - if (!empty($filter['flags'])) { - $action[] = new Sieve_Action_RemoveFlag(array('flags' => $filter['flags'])); - } - break; - - case Ingo_Storage::ACTION_REJECT: - $action[] = new Sieve_Action_Reject(array('reason' => $filter['action-value'])); - break; - - case Ingo_Storage::ACTION_REDIRECT: - $action[] = new Sieve_Action_Redirect(array('address' => $filter['action-value'])); - break; - - case Ingo_Storage::ACTION_REDIRECTKEEP: - if (!empty($filter['flags'])) { - $action[] = new Sieve_Action_Addflag(array('flags' => $filter['flags'])); - } - - $action[] = new Sieve_Action_Redirect(array('address' => $filter['action-value'])); - $action[] = new Sieve_Action_Keep(); - - if (!empty($filter['flags'])) { - $action[] = new Sieve_Action_RemoveFlag(array('flags' => $filter['flags'])); - } - break; - - case Ingo_Storage::ACTION_MOVEKEEP: - if (!empty($filter['flags'])) { - $action[] = new Sieve_Action_Addflag(array('flags' => $filter['flags'])); - } - - $action[] = new Sieve_Action_Keep(); - $action[] = new Sieve_Action_Fileinto(array('folder' => $filter['action-value'])); - - if (!empty($filter['flags'])) { - $action[] = new Sieve_Action_RemoveFlag(array('flags' => $filter['flags'])); - } - break; - - case Ingo_Storage::ACTION_FLAGONLY: - if (!empty($filter['flags'])) { - $action[] = new Sieve_Action_Addflag(array('flags' => $filter['flags'])); - } - break; - - case Ingo_Storage::ACTION_NOTIFY: - $action[] = new Sieve_Action_Notify(array('address' => $filter['action-value'], 'name' => $filter['name'])); - break; - - case Ingo_Storage::ACTION_WHITELIST: - $this->_addWhitelistBlocks(); - continue 2; - - case Ingo_Storage::ACTION_BLACKLIST: - $this->_addBlacklistBlocks(); - continue 2; - - case Ingo_Storage::ACTION_VACATION: - $this->_addVacationBlocks(); - continue 2; - - case Ingo_Storage::ACTION_FORWARD: - $this->_addForwardBlocks(); - continue 2; - - case Ingo_Storage::ACTION_SPAM: - $this->_addSpamBlocks(); - continue 2; - } - - $this->_blocks[] = new Sieve_Comment($filter['name']); - - if ($filter['stop']) { - $action[] = new Sieve_Action_Stop(); - } - - $test = new Sieve_Test(); - if ($filter['combine'] == Ingo_Storage::COMBINE_ANY) { - $test = new Sieve_Test_Anyof(); - } else { - $test = new Sieve_Test_Allof(); - } - - foreach ($filter['conditions'] as $condition) { - $tmp = ''; - switch ($condition['match']) { - case 'equal': - $tmp = new Sieve_Test_Relational(array('comparison' => 'eq', 'headers' => $condition['field'], 'value' => $condition['value'])); - $test->addTest($tmp); - break; - - case 'not equal': - $tmp = new Sieve_Test_Relational(array('comparison' => 'ne', 'headers' => $condition['field'], 'value' => $condition['value'])); - $test->addTest($tmp); - break; - - case 'less than': - if ($condition['field'] == 'Size') { - /* Message Size Test. */ - $tmp = new Sieve_Test_Size(array('comparison' => ':under', 'size' => $condition['value'])); - } else { - /* Relational Test. */ - $tmp = new Sieve_Test_Relational(array('comparison' => 'lt', 'headers' => $condition['field'], 'value' => $condition['value'])); - } - $test->addTest($tmp); - break; - - case 'less than or equal to': - $tmp = new Sieve_Test_Relational(array('comparison' => 'le', 'headers' => $condition['field'], 'value' => $condition['value'])); - $test->addTest($tmp); - break; - - case 'greater than': - if ($condition['field'] == 'Size') { - /* Message Size Test. */ - $tmp = new Sieve_Test_Size(array('comparison' => ':over', 'size' => $condition['value'])); - } else { - /* Relational Test. */ - $tmp = new Sieve_Test_Relational(array('comparison' => 'gt', 'headers' => $condition['field'], 'value' => $condition['value'])); - } - $test->addTest($tmp); - break; - - case 'greater than or equal to': - $tmp = new Sieve_Test_Relational(array('comparison' => 'ge', 'headers' => $condition['field'], 'value' => $condition['value'])); - $test->addTest($tmp); - break; - - case 'exists': - $tmp = new Sieve_Test_Exists(array('headers' => $condition['field'])); - $test->addTest($tmp); - break; - - case 'not exist': - $tmp = new Sieve_Test_Exists(array('headers' => $condition['field'])); - $test->addTest(new Sieve_Test_Not($tmp)); - break; - - case 'contains': - case 'not contain': - case 'is': - case 'not is': - case 'begins with': - case 'not begins with': - case 'ends with': - case 'not ends with': - case 'regex': - case 'matches': - case 'not matches': - $comparator = (isset($condition['case']) && - $condition['case']) - ? 'i;octet' - : 'i;ascii-casemap'; - $vals = array('headers' => preg_replace('/(.)(? $comparator); - $use_address_test = false; - - if ($condition['match'] != 'regex') { - $condition['value'] = preg_replace('/(.)(?addTest($tmp); - break; - - case 'not contain': - $vals['match-type'] = ':contains'; - if ($use_address_test) { - $tmp = new Sieve_Test_Address($vals); - } elseif ($condition['field'] == 'Body') { - $tmp = new Sieve_Test_Body($vals); - } else { - $tmp = new Sieve_Test_Header($vals); - } - $test->addTest(new Sieve_Test_Not($tmp)); - break; - - case 'is': - $vals['match-type'] = ':is'; - if ($use_address_test) { - $tmp = new Sieve_Test_Address($vals); - } elseif ($condition['field'] == 'Body') { - $tmp = new Sieve_Test_Body($vals); - } else { - $tmp = new Sieve_Test_Header($vals); - } - $test->addTest($tmp); - break; - - case 'not is': - $vals['match-type'] = ':is'; - if ($use_address_test) { - $tmp = new Sieve_Test_Address($vals); - } elseif ($condition['field'] == 'Body') { - $tmp = new Sieve_Test_Body($vals); - } else { - $tmp = new Sieve_Test_Header($vals); - } - $test->addTest(new Sieve_Test_Not($tmp)); - break; - - case 'begins with': - $vals['match-type'] = ':matches'; - if ($use_address_test) { - $add_arr = preg_split('(\r\n|\n|\r)', $vals['addresses']); - if (count($add_arr) > 1) { - foreach ($add_arr as $k => $v) { - $add_arr[$k] = $v . '*'; - } - $vals['addresses'] = implode("\r\n", $add_arr); - } else { - $vals['addresses'] .= '*'; - } - $tmp = new Sieve_Test_Address($vals); - } else { - $add_arr = preg_split('(\r\n|\n|\r)', $vals['strings']); - if (count($add_arr) > 1) { - foreach ($add_arr as $k => $v) { - $add_arr[$k] = $v . '*'; - } - $vals['strings'] = implode("\r\n", $add_arr); - } else { - $vals['strings'] .= '*'; - } - if ($condition['field'] == 'Body') { - $tmp = new Sieve_Test_Body($vals); - } else { - $tmp = new Sieve_Test_Header($vals); - } - } - $test->addTest($tmp); - break; - - case 'not begins with': - $vals['match-type'] = ':matches'; - if ($use_address_test) { - $add_arr = preg_split('(\r\n|\n|\r)', $vals['addresses']); - if (count($add_arr) > 1) { - foreach ($add_arr as $k => $v) { - $add_arr[$k] = $v . '*'; - } - $vals['addresses'] = implode("\r\n", $add_arr); - } else { - $vals['addresses'] .= '*'; - } - $tmp = new Sieve_Test_Address($vals); - } else { - $add_arr = preg_split('(\r\n|\n|\r)', $vals['strings']); - if (count($add_arr) > 1) { - foreach ($add_arr as $k => $v) { - $add_arr[$k] = $v . '*'; - } - $vals['strings'] = implode("\r\n", $add_arr); - } else { - $vals['strings'] .= '*'; - } - if ($condition['field'] == 'Body') { - $tmp = new Sieve_Test_Body($vals); - } else { - $tmp = new Sieve_Test_Header($vals); - } - } - $test->addTest(new Sieve_Test_Not($tmp)); - break; - - case 'ends with': - $vals['match-type'] = ':matches'; - if ($use_address_test) { - $add_arr = preg_split('(\r\n|\n|\r)', $vals['addresses']); - if (count($add_arr) > 1) { - foreach ($add_arr as $k => $v) { - $add_arr[$k] = '*' . $v; - } - $vals['addresses'] = implode("\r\n", $add_arr); - } else { - $vals['addresses'] = '*' . $vals['addresses']; - } - $tmp = new Sieve_Test_Address($vals); - } else { - $add_arr = preg_split('(\r\n|\n|\r)', $vals['strings']); - if (count($add_arr) > 1) { - foreach ($add_arr as $k => $v) { - $add_arr[$k] = '*' . $v; - } - $vals['strings'] = implode("\r\n", $add_arr); - } else { - $vals['strings'] = '*' . $vals['strings']; - } - if ($condition['field'] == 'Body') { - $tmp = new Sieve_Test_Body($vals); - } else { - $tmp = new Sieve_Test_Header($vals); - } - } - $test->addTest($tmp); - break; - - case 'not ends with': - $vals['match-type'] = ':matches'; - if ($use_address_test) { - $add_arr = preg_split('(\r\n|\n|\r)', $vals['addresses']); - if (count($add_arr) > 1) { - foreach ($add_arr as $k => $v) { - $add_arr[$k] = '*' . $v; - } - $vals['addresses'] = implode("\r\n", $add_arr); - } else { - $vals['addresses'] = '*' . $vals['addresses']; - } - $tmp = new Sieve_Test_Address($vals); - } else { - $add_arr = preg_split('(\r\n|\n|\r)', $vals['strings']); - if (count($add_arr) > 1) { - foreach ($add_arr as $k => $v) { - $add_arr[$k] = '*' . $v; - } - $vals['strings'] = implode("\r\n", $add_arr); - } else { - $vals['strings'] = '*' . $vals['strings']; - } - if ($condition['field'] == 'Body') { - $tmp = new Sieve_Test_Body($vals); - } else { - $tmp = new Sieve_Test_Header($vals); - } - } - $test->addTest(new Sieve_Test_Not($tmp)); - break; - - case 'regex': - $vals['match-type'] = ':regex'; - if ($use_address_test) { - $tmp = new Sieve_Test_Address($vals); - } elseif ($condition['field'] == 'Body') { - $tmp = new Sieve_Test_Body($vals); - } else { - $tmp = new Sieve_Test_Header($vals); - } - $test->addTest($tmp); - break; - - case 'matches': - $vals['match-type'] = ':matches'; - if ($use_address_test) { - $tmp = new Sieve_Test_Address($vals); - } elseif ($condition['field'] == 'Body') { - $tmp = new Sieve_Test_Body($vals); - } else { - $tmp = new Sieve_Test_Header($vals); - } - $test->addTest($tmp); - break; - - case 'not matches': - $vals['match-type'] = ':matches'; - if ($use_address_test) { - $tmp = new Sieve_Test_Address($vals); - } elseif ($condition['field'] == 'Body') { - $tmp = new Sieve_Test_Body($vals); - } else { - $tmp = new Sieve_Test_Header($vals); - } - $test->addTest(new Sieve_Test_Not($tmp)); - break; - } - } - } - - $if = new Sieve_If($test); - $if->setActions($action); - $this->_blocks[] = $if; - } - - /* Add blocks that have to go to the end. */ - foreach ($this->_endBlocks as $block) { - $this->_blocks[] = $block; - } - - return $this->toCode(); - } - -} - -/** - * The Sieve_If class represents a Sieve If Statement - * - * @author Mike Cochrane - * @since Ingo 0.1 - * @package Ingo - */ -class Sieve_If { - - /** - * The Sieve_Test object for the if test. - * - * @var Sieve_Test - */ - var $_test; - - /** - * A list of Sieve_Action objects that go into the if clause. - * - * @var array - */ - var $_actions = array(); - - /** - * A list of Sieve_Elseif objects that create optional elsif clauses. - * - * @var array - */ - var $_elsifs = array(); - - /** - * A Sieve_Else object that creates an optional else clause. - * - * @var Sieve_Else - */ - var $_else; - - /** - * Constructor. - * - * @params Sieve_Test $test A Sieve_Test object. - */ - function Sieve_If($test = null) - { - if (is_null($test)) { - $this->_test = new Sieve_Test_False(); - } else { - $this->_test = $test; - } - - $this->_actions[] = new Sieve_Action_Keep(); - $this->_else = new Sieve_Else(); - } - - function getTest() - { - return $this->_test; - } - - function setTest($test) - { - $this->_test = $test; - } - - function getActions() - { - return $this->_actions; - } - - function setActions($actions) - { - $this->_actions = $actions; - } - - function getElsifs() - { - return $this->_elsifs; - } - - function setElsifs($elsifs) - { - $this->_elsifs = $elsifs; - } - - function addElsif($elsif) - { - $this->_elsifs[] = $elsif; - } - - function getElse() - { - return $this->_else; - } - - function setElse($else) - { - $this->_else = $else; - } - - /** - * Returns a script snippet representing this rule and any sub-rules. - * - * @return string A Sieve script snippet. - */ - function toCode() - { - $code = 'if ' . $this->_test->toCode() . " { \n"; - foreach ($this->_actions as $action) { - $code .= ' ' . $action->toCode() . "\n"; - } - $code .= "} "; - - foreach ($this->_elsifs as $elsif) { - $code .= $elsif->toCode(); - } - - $code .= $this->_else->toCode(); - - return $code . "\n"; - } - - /** - * Checks if all sub-rules are valid. - * - * @return boolean|string True if all rules are valid, an error message - * otherwise. - */ - function check() - { - $res = $this->_test->check(); - if ($res !== true) { - return $res; - } - - foreach ($this->_elsifs as $elsif) { - $res = $elsif->check(); - if ($res !== true) { - return $res; - } - } - - $res = $this->_else->check(); - if ($res !== true) { - return $res; - } - - foreach ($this->_actions as $action) { - $res = $action->check(); - if ($res !== true) { - return $res; - } - } - - return true; - } - - /** - * Returns a list of sieve extensions required for this rule and any - * sub-rules. - * - * @return array A Sieve extension list. - */ - function requires() - { - $requires = array(); - - foreach ($this->_actions as $action) { - $requires = array_merge($requires, $action->requires()); - } - - foreach ($this->_elsifs as $elsif) { - $requires = array_merge($requires, $elsif->requires()); - } - - $requires = array_merge($requires, $this->_test->requires(), $this->_else->requires()); - - return $requires; - } - -} - -/** - * The Sieve_Else class represents a Sieve Else Statement - * - * @author Mike Cochrane - * @since Ingo 0.1 - * @package Ingo - */ -class Sieve_Else { - - /** - * A list of Sieve_Action objects that go into the else clause. - * - * @var array - */ - var $_actions = array(); - - /** - * Constructor. - * - * @params Sieve_Action|array $actions A Sieve_Action object or a list of - * Sieve_Action objects. - */ - function Sieve_Else($actions = null) - { - if (is_array($actions)) { - $this->_actions = $actions; - } elseif (!is_null($actions)) { - $this->_actions[] = $actions; - } - } - - /** - * Returns a script snippet representing this rule and any sub-rules. - * - * @return string A Sieve script snippet. - */ - function toCode() - { - if (count($this->_actions) == 0) { - return ''; - } - - $code = 'else' . " { \n"; - foreach ($this->_actions as $action) { - $code .= ' ' . $action->toCode() . "\n"; - } - $code .= "} "; - - return $code; - } - - function setActions($actions) - { - $this->_actions = $actions; - } - - function getActions() - { - return $this->_actions; - } - - /** - * Checks if all sub-rules are valid. - * - * @return boolean|string True if all rules are valid, an error message - * otherwise. - */ - function check() - { - foreach ($this->_actions as $action) { - $res = $action->check(); - if ($res !== true) { - return $res; - } - } - - return true; - } - - /** - * Returns a list of sieve extensions required for this rule and any - * sub-rules. - * - * @return array A Sieve extension list. - */ - function requires() - { - $requires = array(); - - foreach ($this->_actions as $action) { - $requires = array_merge($requires, $action->requires()); - } - - return $requires; - } - -} - -/** - * The Sieve_Elsif class represents a Sieve Elsif Statement - * - * @author Mike Cochrane - * @since Ingo 0.1 - * @package Ingo - */ -class Sieve_Elsif { - - /** - * The Sieve_Test object for the if test. - * - * @var Sieve_Test - */ - var $_test; - - /** - * A list of Sieve_Action objects that go into the if clause. - * - * @var array - */ - var $_actions = array(); - - /** - * Constructor. - * - * @params Sieve_Test $test A Sieve_Test object. - */ - function Sieve_Elsif($test = null) - { - if (is_null($test)) { - $this->_test = new Sieve_Test_False(); - } else { - $this->_test = $test; - } - $this->_actions[] = new Sieve_Action_Keep(); - } - - function getTest() - { - return $this->_test; - } - - function setTest($test) - { - $this->_test = $test; - } - - function getActions() - { - return $this->_actions; - } - - function setActions($actions) - { - $this->_actions = $actions; - } - - /** - * Returns a script snippet representing this rule and any sub-rules. - * - * @return string A Sieve script snippet. - */ - function toCode() - { - $code = 'elsif ' . $this->_test->toCode() . " { \n"; - foreach ($this->_actions as $action) { - $code .= ' ' . $action->toCode() . "\n"; - } - $code .= "} "; - - return $code; - } - - /** - * Checks if all sub-rules are valid. - * - * @return boolean|string True if all rules are valid, an error message - * otherwise. - */ - function check() - { - $res = $this->_test->check(); - if ($res !== true) { - return $res; - } - - foreach ($this->_actions as $action) { - $res = $action->check(); - if ($res !== true) { - return $res; - } - } - - return true; - } - - /** - * Returns a list of sieve extensions required for this rule and any - * sub-rules. - * - * @return array A Sieve extension list. - */ - function requires() - { - $requires = array(); - - foreach ($this->_actions as $action) { - $requires = array_merge($requires, $action->requires()); - } - - $requires = array_merge($requires, $this->_test->requires()); - - return $requires; - } - -} - -/** - * The Sieve_Test class represents a Sieve Test. - * - * A test is a piece of code that evaluates to true or false. - * - * @author Mike Cochrane - * @since Ingo 0.1 - * @package Ingo - */ -class Sieve_Test { - - /** - * Any necessary test parameters. - * - * @var array - */ - var $_vars = array(); - - /** - * Returns a script snippet representing this rule and any sub-rules. - * - * @return string A Sieve script snippet. - */ - function toCode() - { - return 'toCode() Function Not Implemented in class ' . get_class($this); - } - - /** - * Checks if the rule parameters are valid. - * - * @return boolean|string True if this rule is valid, an error message - * otherwise. - */ - function check() - { - return 'check() Function Not Implemented in class ' . get_class($this); - } - - /** - * Returns a list of sieve extensions required for this rule and any - * sub-rules. - * - * @return array A Sieve extension list. - */ - function requires() - { - return array(); - } - -} - -/** - * The Sieve_Test_True class represents a test that always evaluates to true. - * - * @author Mike Cochrane - * @since Ingo 0.1 - * @package Ingo - */ -class Sieve_Test_True extends Sieve_Test { - - /** - * Returns a script snippet representing this rule and any sub-rules. - * - * @return string A Sieve script snippet. - */ - function toCode() - { - return 'true'; - } - - /** - * Checks if the rule parameters are valid. - * - * @return boolean|string True if this rule is valid, an error message - * otherwise. - */ - function check() - { - return true; - } - -} - -/** - * The Sieve_Test_False class represents a test that always evaluates to - * false. - * - * @author Mike Cochrane - * @since Ingo 0.1 - * @package Ingo - */ -class Sieve_Test_False extends Sieve_Test { - - /** - * Returns a script snippet representing this rule and any sub-rules. - * - * @return string A Sieve script snippet. - */ - function toCode() - { - return 'false'; - } - - /** - * Checks if the rule parameters are valid. - * - * @return boolean|string True if this rule is valid, an error message - * otherwise. - */ - function check() - { - return true; - } - -} - -/** - * The Sieve_Test_Allof class represents a Allof test structure. - * - * Equivalent to a logical AND of all the tests it contains. - * - * @author Mike Cochrane - * @since Ingo 0.1 - * @package Ingo - */ -class Sieve_Test_Allof extends Sieve_Test { - - var $_tests = array(); - - /** - * Constructor. - * - * @params Sieve_Test|array $test A Sieve_Test object or a list of - * Sieve_Test objects. - */ - function Sieve_Test_Allof($test = null) - { - if (is_array($test)) { - $this->_tests = $test; - } elseif (!is_null($test)) { - $this->_tests[] = $test; - } - } - - /** - * Returns a script snippet representing this rule and any sub-rules. - * - * @return string A Sieve script snippet. - */ - function toCode() - { - $code = ''; - if (count($this->_tests) > 1) { - $testlist = ''; - foreach ($this->_tests as $test) { - $testlist .= (empty($testlist)) ? '' : ', '; - $testlist .= trim($test->toCode()); - } - - $code = "allof ( $testlist )"; - } elseif (count($this->_tests) == 1) { - $code = $this->_tests[0]->toCode(); - } else { - return 'true'; - } - return $code; - } - - /** - * Checks if all sub-rules are valid. - * - * @return boolean|string True if all rules are valid, an error message - * otherwise. - */ - function check() - { - foreach ($this->_tests as $test) { - $res = $test->check(); - if ($res !== true) { - return $res; - } - } - - return true; - } - - function addTest($test) - { - $this->_tests[] = $test; - } - - function getTests() - { - return $this->_tests; - } - - /** - * Returns a list of sieve extensions required for this rule and any - * sub-rules. - * - * @return array A Sieve extension list. - */ - function requires() - { - $requires = array(); - - foreach ($this->_tests as $test) { - $requires = array_merge($requires, $test->requires()); - } - - return $requires; - } - -} - -/** - * The Sieve_Test_Anyof class represents a Anyof test structure. - * - * Equivalent to a logical OR of all the tests it contains. - * - * @author Mike Cochrane - * @since Ingo 0.1 - * @package Ingo - */ -class Sieve_Test_Anyof extends Sieve_Test { - - var $_tests = array(); - - /** - * Constructor. - * - * @params Sieve_Test|array $test A Sieve_Test object or a list of - * Sieve_Test objects. - */ - function Sieve_Test_Anyof($test = null) - { - if (is_array($test)) { - $this->_tests = $test; - } elseif (!is_null($test)) { - $this->_tests[] = $test; - } - } - - /** - * Returns a script snippet representing this rule and any sub-rules. - * - * @return string A Sieve script snippet. - */ - function toCode() - { - $testlist = ''; - if (count($this->_tests) > 1) { - $testlist = ''; - foreach ($this->_tests as $test) { - $testlist .= (empty($testlist)) ? '' : ', '; - $testlist .= trim($test->toCode()); - } - - $code = "anyof ( $testlist )"; - } elseif (count($this->_tests) == 1) { - $code = $this->_tests[0]->toCode(); - } else { - return 'true'; - } - return $code; - } - - function addTest($test) - { - $this->_tests[] = $test; - } - - function getTests() - { - return $this->_tests; - } - - /** - * Checks if all sub-rules are valid. - * - * @return boolean|string True if all rules are valid, an error message - * otherwise. - */ - function check() - { - foreach ($this->_tests as $test) { - $res = $test->check(); - if ($res !== true) { - return $res; - } - } - - return true; - } - - /** - * Returns a list of sieve extensions required for this rule and any - * sub-rules. - * - * @return array A Sieve extension list. - */ - function requires() - { - $requires = array(); - - foreach ($this->_tests as $test) { - $requires = array_merge($requires, $test->requires()); - } - - return $requires; - } - -} - -/** - * The Sieve_Test_Relational class represents a relational test. - * - * @author Todd Merritt - * @since Ingo 1.0 - * @package Ingo - */ -class Sieve_Test_Relational extends Sieve_Test { - - /** - * Constructor. - * - * @params array $vars Any required parameters. - */ - function Sieve_Test_Relational($vars = array()) - { - $this->_vars['comparison'] = (isset($vars['comparison'])) ? $vars['comparison'] : ''; - $this->_vars['headers'] = (isset($vars['headers'])) ? $vars['headers'] : ''; - $this->_vars['value'] = (isset($vars['value'])) ? $vars['value'] : 0; - } - - /** - * Returns a script snippet representing this rule and any sub-rules. - * - * @return string A Sieve script snippet. - */ - function toCode() - { - $code = 'header :value "' . - $this->_vars['comparison'] . '" ' . - ':comparator "i;ascii-numeric" '; - - $headers = preg_split('(\r\n|\n|\r)', $this->_vars['headers']); - $header_count = count($headers); - - if ($header_count > 1) { - $code .= "["; - $headerstr = ''; - - foreach ($headers as $val) { - $headerstr .= (empty($headerstr) ? '"' : ', "') . - Ingo_Script_sieve::escapeString($val) . '"'; - } - - $code .= $headerstr . "] "; - } elseif ($header_count == 1) { - $code .= '"' . Ingo_Script_sieve::escapeString($headers[0]) . '" '; - } - - return $code . '["' . $this->_vars['value'] . '"]'; - } - - /** - * Checks if the rule parameters are valid. - * - * @return boolean|string True if this rule is valid, an error message - * otherwise. - */ - function check() - { - $headers = preg_split('(\r\n|\n|\r)', $this->_vars['headers']); - return $headers ? true : _("No headers specified"); - } - - /** - * Returns a list of sieve extensions required for this rule and any - * sub-rules. - * - * @return array A Sieve extension list. - */ - function requires() - { - return array('relational', 'comparator-i;ascii-numeric'); - } - -} - -/** - * The Sieve_Test_Size class represents a message size test. - * - * @author Mike Cochrane - * @since Ingo 0.1 - * @package Ingo - */ -class Sieve_Test_Size extends Sieve_Test { - - /** - * Constructor. - * - * @params array $vars Any required parameters. - */ - function Sieve_Test_Size($vars = array()) - { - $this->_vars['comparison'] = (isset($vars['comparison'])) ? $vars['comparison'] : ''; - $this->_vars['size'] = (isset($vars['size'])) ? $vars['size'] : ''; - } - - /** - * Returns a script snippet representing this rule and any sub-rules. - * - * @return string A Sieve script snippet. - */ - function toCode() - { - return 'size ' . $this->_vars['comparison'] . ' ' . $this->_vars['size']; - } - - /** - * Checks if the rule parameters are valid. - * - * @return boolean|string True if this rule is valid, an error message - * otherwise. - */ - function check() - { - if (!(isset($this->_vars['comparison']) && - isset($this->_vars['size']))) { - return false; - } - - return true; - } - -} - -/** - * The Sieve_Test_Not class represents the inverse of a given test. - * - * @author Mike Cochrane - * @since Ingo 0.1 - * @package Ingo - */ -class Sieve_Test_Not extends Sieve_Test { - - var $_test = array(); - - /** - * Constructor. - * - * @params Sieve_Test $test A Sieve_Test object. - */ - function Sieve_Test_Not($test) - { - $this->_test = $test; - } - - /** - * Checks if the sub-rule is valid. - * - * @return boolean|string True if this rule is valid, an error message - * otherwise. - */ - function check() - { - return $this->_test->check(); - } - - /** - * Returns a script snippet representing this rule and any sub-rules. - * - * @return string A Sieve script snippet. - */ - function toCode() - { - return 'not ' . $this->_test->toCode(); - } - - /** - * Returns a list of sieve extensions required for this rule and any - * sub-rules. - * - * @return array A Sieve extension list. - */ - function requires() - { - return $this->_test->requires(); - } - -} - -/** - * The Sieve_Test_Exists class represents a test for the existsance of one or - * more headers in a message. - * - * @author Mike Cochrane - * @since Ingo 0.1 - * @package Ingo - */ -class Sieve_Test_Exists extends Sieve_Test { - - /** - * Constructor. - * - * @params array $vars Any required parameters. - */ - function Sieve_Test_Exists($vars = array()) - { - $this->_vars['headers'] = (isset($vars['headers'])) ? $vars['headers'] : ''; - } - - /** - * Checks if the rule parameters are valid. - * - * @return boolean|string True if this rule is valid, an error message - * otherwise. - */ - function check() - { - $headers = preg_split('(\r\n|\n|\r)', $this->_vars['headers']); - if (!$headers) { - return _("No headers specified"); - } - - return true; - } - - /** - * Returns a script snippet representing this rule and any sub-rules. - * - * @return string A Sieve script snippet. - */ - function toCode() - { - $code = 'exists '; - $headers = preg_split('(\r\n|\n|\r)', $this->_vars['headers']); - if (count($headers) > 1) { - $code .= "["; - $headerstr = ''; - foreach ($headers as $header) { - $headerstr .= (empty($headerstr) ? '"' : ', "') . - Ingo_Script_sieve::escapeString($header) . '"'; - } - $code .= $headerstr . "] "; - } elseif (count($headers) == 1) { - $code .= '"' . Ingo_Script_sieve::escapeString($headers[0]) . '" '; - } else { - return "**error** No Headers Specified"; - } - - return $code; - } - -} - -/** - * The Sieve_Test_Address class represents a test on parts or all of the - * addresses in the given fields. - * - * @author Mike Cochrane - * @since Ingo 0.1 - * @package Ingo - */ -class Sieve_Test_Address extends Sieve_Test { - - /** - * Constructor. - * - * @params array $vars Any required parameters. - */ - function Sieve_Test_Address($vars) - { - $this->_vars['headers'] = (isset($vars['headers'])) ? $vars['headers'] : ''; - $this->_vars['comparator'] = (isset($vars['comparator'])) ? $vars['comparator'] : 'i;ascii-casemap'; - $this->_vars['match-type'] = (isset($vars['match-type'])) ? $vars['match-type'] : ':is'; - $this->_vars['address-part'] = (isset($vars['address-part'])) ? $vars['address-part'] : ':all'; - $this->_vars['addresses'] = (isset($vars['addresses'])) ? $vars['addresses'] : ''; - } - - /** - * Checks if the rule parameters are valid. - * - * @return boolean|string True if this rule is valid, an error message - * otherwise. - */ - function check() - { - $headers = preg_split('(\r\n|\n|\r)', $this->_vars['headers']); - if (!$headers) { - return false; - } - - $addresses = preg_split('(\r\n|\n|\r)', $this->_vars['addresses']); - if (!$addresses) { - return false; - } - - return true; - } - - /** - * Returns a script snippet representing this rule and any sub-rules. - * - * @return string A Sieve script snippet. - */ - function toCode() - { - $code = 'address ' . - $this->_vars['address-part'] . ' ' . - ':comparator "' . $this->_vars['comparator'] . '" ' . - $this->_vars['match-type'] . ' '; - - $headers = preg_split('(\r\n|\n|\r|,)', $this->_vars['headers']); - $headers = array_filter($headers); - if (count($headers) > 1) { - $code .= "["; - $headerstr = ''; - foreach ($headers as $header) { - $header = trim($header); - if (!empty($header)) { - $headerstr .= empty($headerstr) ? '"' : ', "'; - $headerstr .= Ingo_Script_sieve::escapeString($header, $this->_vars['match-type'] == ':regex') . '"'; - } - } - $code .= $headerstr . "] "; - } elseif (count($headers) == 1) { - $code .= '"' . Ingo_Script_sieve::escapeString($headers[0], $this->_vars['match-type'] == ':regex') . '" '; - } else { - return "No Headers Specified"; - } - - $addresses = preg_split('(\r\n|\n|\r)', $this->_vars['addresses']); - $addresses = array_filter($addresses); - if (count($addresses) > 1) { - $code .= "["; - $addressstr = ''; - foreach ($addresses as $addr) { - $addr = trim($addr); - if (!empty($addr)) { - $addressstr .= empty($addressstr) ? '"' : ', "'; - $addressstr .= Ingo_Script_sieve::escapeString($addr, $this->_vars['match-type'] == ':regex') . '"'; - } - } - $code .= $addressstr . "] "; - } elseif (count($addresses) == 1) { - $code .= '"' . Ingo_Script_sieve::escapeString($addresses[0], $this->_vars['match-type'] == ':regex') . '" '; - } else { - return "No Addresses Specified"; - } - - return $code; - } - - /** - * Returns a list of sieve extensions required for this rule and any - * sub-rules. - * - * @return array A Sieve extension list. - */ - function requires() - { - if ($this->_vars['match-type'] == ':regex') { - return array('regex'); - } - return array(); - } - -} - -/** - * The Sieve_Test_Header class represents a test on the contents of one or - * more headers in a message. - * - * @author Mike Cochrane - * @since Ingo 0.1 - * @package Ingo - */ -class Sieve_Test_Header extends Sieve_Test { - - /** - * Constructor. - * - * @params array $vars Any required parameters. - */ - function Sieve_Test_Header($vars = array()) - { - $this->_vars['headers'] = isset($vars['headers']) - ? $vars['headers'] - : 'Subject'; - $this->_vars['comparator'] = isset($vars['comparator']) - ? $vars['comparator'] - : 'i;ascii-casemap'; - $this->_vars['match-type'] = isset($vars['match-type']) - ? $vars['match-type'] - : ':is'; - $this->_vars['strings'] = isset($vars['strings']) - ? $vars['strings'] - : ''; - } - - /** - * Checks if the rule parameters are valid. - * - * @return boolean|string True if this rule is valid, an error message - * otherwise. - */ - function check() - { - $headers = preg_split('((?_vars['headers']); - if (!$headers) { - return false; - } - - $strings = preg_split('((?_vars['strings']); - if (!$strings) { - return false; - } - - return true; - } - - /** - * Returns a script snippet representing this rule and any sub-rules. - * - * @return string A Sieve script snippet. - */ - function toCode() - { - $code = 'header ' . - ':comparator "' . $this->_vars['comparator'] . '" ' . - $this->_vars['match-type'] . ' '; - - $headers = preg_split('(\r\n|\n|\r)', $this->_vars['headers']); - $headers = array_filter($headers); - if (count($headers) > 1) { - $code .= "["; - $headerstr = ''; - foreach ($headers as $header) { - $headerstr .= empty($headerstr) ? '"' : ', "'; - $headerstr .= Ingo_Script_sieve::escapeString($header, $this->_vars['match-type'] == ':regex') . '"'; - } - $code .= $headerstr . "] "; - } elseif (count($headers) == 1) { - $code .= '"' . $headers[0] . '" '; - } else { - return _("No headers specified"); - } - - $strings = preg_split('(\r\n|\n|\r)', $this->_vars['strings']); - $strings = array_filter($strings); - if (count($strings) > 1) { - $code .= "["; - $stringlist = ''; - foreach ($strings as $str) { - $stringlist .= empty($stringlist) ? '"' : ', "'; - $stringlist .= Ingo_Script_sieve::escapeString($str, $this->_vars['match-type'] == ':regex') . '"'; - } - $code .= $stringlist . "] "; - } elseif (count($strings) == 1) { - $code .= '"' . Ingo_Script_sieve::escapeString(reset($strings), $this->_vars['match-type'] == ':regex') . '" '; - } else { - return _("No strings specified"); - } - - return $code; - } - - /** - * Returns a list of sieve extensions required for this rule and any - * sub-rules. - * - * @return array A Sieve extension list. - */ - function requires() - { - if ($this->_vars['match-type'] == ':regex') { - return array('regex'); - } - return array(); - } - -} - -/** - * The Sieve_Test_Body class represents a test on the contents of the body in - * a message. - * - * @author Michael Menge - * @since Ingo 1.2 - * @package Ingo - */ -class Sieve_Test_Body extends Sieve_Test { - - /** - * Constructor. - * - * @params array $vars Any required parameters. - */ - function Sieve_Test_Body($vars = array()) - { - $this->_vars['comparator'] = (isset($vars['comparator'])) ? $vars['comparator'] : 'i;ascii-casemap'; - $this->_vars['match-type'] = (isset($vars['match-type'])) ? $vars['match-type'] : ':is'; - $this->_vars['strings'] = (isset($vars['strings'])) ? $vars['strings'] : ''; - } - - /** - * Checks if the rule parameters are valid. - * - * @return boolean|string True if this rule is valid, an error message - * otherwise. - */ - function check() - { - $strings = preg_split('((?_vars['strings']); - if (!$strings) { - return false; - } - - return true; - } - - /** - * Returns a script snippet representing this rule and any sub-rules. - * - * @return string A Sieve script snippet. - */ - function toCode() - { - $code = 'body ' . - ':comparator "' . $this->_vars['comparator'] . '" ' . - $this->_vars['match-type'] . ' '; - - $strings = preg_split('(\r\n|\n|\r)', $this->_vars['strings']); - $strings = array_filter($strings); - if (count($strings) > 1) { - $code .= "["; - $stringlist = ''; - foreach ($strings as $str) { - $stringlist .= empty($stringlist) ? '"' : ', "'; - $stringlist .= Ingo_Script_sieve::escapeString($str, $this->_vars['match-type'] == ':regex') . '"'; - } - $code .= $stringlist . "] "; - } elseif (count($strings) == 1) { - $code .= '"' . Ingo_Script_sieve::escapeString($strings[0], $this->_vars['match-type'] == ':regex') . '" '; - } else { - return _("No strings specified"); - } - - return $code; - } - - /** - * Returns a list of sieve extensions required for this rule and any - * sub-rules. - * - * @return array A Sieve extension list. - */ - function requires() - { - if ($this->_vars['match-type'] == ':regex') { - return array('regex', 'body'); - } - - return array('body'); - } - -} - -/** - * A Comment. - * - * @author Mike Cochrane - * @since Ingo 0.1 - * @package Ingo - * @todo This and Sieve_If should really extends a Sieve_Block eventually. - */ -class Sieve_Comment { - - var $_comment; - - /** - * Constructor. - * - * @params string $comment The comment text. - */ - function Sieve_Comment($comment) - { - $this->_comment = $comment; - } - - /** - * Returns a script snippet representing this rule and any sub-rules. - * - * @return string A Sieve script snippet. - */ - function toCode() - { - $code = ''; - $lines = preg_split('(\r\n|\n|\r)', $this->_comment); - foreach ($lines as $line) { - $line = trim($line); - if (strlen($line)) { - $code .= (empty($code) ? '' : "\n") . '# ' . $line; - } - } - return Horde_String::convertCharset($code, Horde_Nls::getCharset(), 'UTF-8'); - } - - /** - * Checks if the rule parameters are valid. - * - * @return boolean|string True if this rule is valid, an error message - * otherwise. - */ - function check() - { - return true; - } - - /** - * Returns a list of sieve extensions required for this rule and any - * sub-rules. - * - * @return array A Sieve extension list. - */ - function requires() - { - return array(); - } - -} - -/** - * The Sieve_Action class represents an action in a Sieve script. - * - * An action is anything that has a side effect eg: discard, redirect. - * - * @author Mike Cochrane - * @since Ingo 0.1 - * @package Ingo - */ -class Sieve_Action { - - /** - * Any necessary action parameters. - * - * @var array - */ - var $_vars = array(); - - /** - * Returns a script snippet representing this rule and any sub-rules. - * - * @return string A Sieve script snippet. - */ - function toCode() - { - return 'toCode() Function Not Implemented in class ' . get_class($this) ; - } - - function toString() - { - return $this->toCode(); - } - - /** - * Checks if the rule parameters are valid. - * - * @return boolean|string True if this rule is valid, an error message - * otherwise. - */ - function check() - { - return 'check() Function Not Implemented in class ' . get_class($this) ; - } - - /** - * Returns a list of sieve extensions required for this rule and any - * sub-rules. - * - * @return array A Sieve extension list. - */ - function requires() - { - return array(); - } - -} - -/** - * The Sieve_Action_Redirect class represents a redirect action. - * - * @author Mike Cochrane - * @since Ingo 0.1 - * @package Ingo - */ -class Sieve_Action_Redirect extends Sieve_Action { - - /** - * Constructor. - * - * @params array $vars Any required parameters. - */ - function Sieve_Action_Redirect($vars = array()) - { - $this->_vars['address'] = (isset($vars['address'])) ? $vars['address'] : ''; - } - - function toCode($depth = 0) - { - return str_repeat(' ', $depth * 4) . 'redirect ' . - '"' . Ingo_Script_sieve::escapeString($this->_vars['address']) . '";'; - } - - /** - * Checks if the rule parameters are valid. - * - * @return boolean|string True if this rule is valid, an error message - * otherwise. - */ - function check() - { - if (empty($this->_vars['address'])) { - return _("Missing address to redirect message to"); - } - - return true; - } - -} - -/** - * The Sieve_Action_Reject class represents a reject action. - * - * @author Mike Cochrane - * @since Ingo 0.1 - * @package Ingo - */ -class Sieve_Action_Reject extends Sieve_Action { - - /** - * Constructor. - * - * @params array $vars Any required parameters. - */ - function Sieve_Action_Reject($vars = array()) - { - $this->_vars['reason'] = (isset($vars['reason'])) ? $vars['reason'] : ''; - } - - /** - * Returns a script snippet representing this rule and any sub-rules. - * - * @return string A Sieve script snippet. - */ - function toCode() - { - return 'reject "' . Ingo_Script_sieve::escapeString($this->_vars['reason']) . '";'; - } - - /** - * Checks if the rule parameters are valid. - * - * @return boolean|string True if this rule is valid, an error message - * otherwise. - */ - function check() - { - if (empty($this->_vars['reason'])) { - return _("Missing reason for reject"); - } - - return true; - } - - /** - * Returns a list of sieve extensions required for this rule and any - * sub-rules. - * - * @return array A Sieve extension list. - */ - function requires() - { - return array('reject'); - } - -} - -/** - * The Sieve_Action_Keep class represents a keep action. - * - * @author Mike Cochrane - * @since Ingo 0.1 - * @package Ingo - */ -class Sieve_Action_Keep extends Sieve_Action { - - /** - * Returns a script snippet representing this rule and any sub-rules. - * - * @return string A Sieve script snippet. - */ - function toCode() - { - return 'keep;'; - } - - /** - * Checks if the rule parameters are valid. - * - * @return boolean|string True if this rule is valid, an error message - * otherwise. - */ - function check() - { - return true; - } - -} - -/** - * The Sieve_Action_Discard class represents a discard action. - * - * @author Mike Cochrane - * @since Ingo 0.1 - * @package Ingo - */ -class Sieve_Action_Discard extends Sieve_Action { - - /** - * Returns a script snippet representing this rule and any sub-rules. - * - * @return string A Sieve script snippet. - */ - function toCode() - { - return 'discard;'; - } - - /** - * Checks if the rule parameters are valid. - * - * @return boolean|string True if this rule is valid, an error message - * otherwise. - */ - function check() - { - return true; - } - -} - -/** - * The Sieve_Action_Stop class represents a stop action. - * - * @author Mike Cochrane - * @since Ingo 0.1 - * @package Ingo - */ -class Sieve_Action_Stop extends Sieve_Action { - - /** - * Returns a script snippet representing this rule and any sub-rules. - * - * @return string A Sieve script snippet. - */ - function toCode() - { - return 'stop;'; - } - - /** - * Checks if the rule parameters are valid. - * - * @return boolean|string True if this rule is valid, an error message - * otherwise. - */ - function check() - { - return true; - } - -} - -/** - * The Sieve_Action_Fileinto class represents a fileinto action. - * - * @author Mike Cochrane - * @since Ingo 0.1 - * @package Ingo - */ -class Sieve_Action_Fileinto extends Sieve_Action { - - /** - * Constructor. - * - * @params array $vars Any required parameters. - */ - function Sieve_Action_Fileinto($vars = array()) - { - $this->_vars['folder'] = (isset($vars['folder'])) ? $vars['folder'] : ''; - } - - /** - * Returns a script snippet representing this rule and any sub-rules. - * - * @return string A Sieve script snippet. - */ - function toCode() - { - return 'fileinto "' . Ingo_Script_sieve::escapeString($this->_vars['folder']) . '";'; - } - - /** - * Checks if the rule parameters are valid. - * - * @return boolean|string True if this rule is valid, an error message - * otherwise. - */ - function check() - { - if (empty($this->_vars['folder'])) { - return _("Inexistant mailbox specified for message delivery."); - } - - return true; - } - - /** - * Returns a list of sieve extensions required for this rule and any - * sub-rules. - * - * @return array A Sieve extension list. - */ - function requires() - { - return array('fileinto'); - } - -} - -/** - * The Sieve_Action_Vacation class represents a vacation action. - * - * @author Mike Cochrane - * @since Ingo 0.1 - * @package Ingo - */ -class Sieve_Action_Vacation extends Sieve_Action { - - /** - * Constructor. - * - * @params array $vars Any required parameters. - */ - function Sieve_Action_Vacation($vars = array()) - { - $this->_vars['days'] = isset($vars['days']) ? intval($vars['days']) : ''; - $this->_vars['addresses'] = isset($vars['addresses']) ? $vars['addresses'] : ''; - $this->_vars['subject'] = isset($vars['subject']) ? $vars['subject'] : ''; - $this->_vars['reason'] = isset($vars['reason']) ? $vars['reason'] : ''; - $this->_vars['start'] = isset($vars['start']) ? $vars['start'] : ''; - $this->_vars['start_year'] = isset($vars['start_year']) ? $vars['start_year'] : ''; - $this->_vars['start_month'] = isset($vars['start_month']) ? $vars['start_month'] : ''; - $this->_vars['start_day'] = isset($vars['start_day']) ? $vars['start_day'] : ''; - $this->_vars['end'] = isset($vars['end']) ? $vars['end'] : ''; - $this->_vars['end_year'] = isset($vars['end_year']) ? $vars['end_year'] : ''; - $this->_vars['end_month'] = isset($vars['end_month']) ? $vars['end_month'] : ''; - $this->_vars['end_day'] = isset($vars['end_day']) ? $vars['end_day'] : ''; - } - - /** - * Returns a script snippet representing this rule and any sub-rules. - * - * @return string A Sieve script snippet. - */ - function toCode() - { - $start_year = $this->_vars['start_year']; - $start_month = $this->_vars['start_month']; - $start_day = $this->_vars['start_day']; - - $end_year = $this->_vars['end_year']; - $end_month = $this->_vars['end_month']; - $end_day = $this->_vars['end_day']; - - $code = ''; - - if (empty($this->_vars['start']) || empty($this->_vars['end'])) { - return $this->_vacationCode(); - } elseif ($end_year > $start_year + 1) { - $code .= $this->_yearCheck($start_year + 1, $end_year - 1) - . $this->_vacationCode() - . "\n}\n" - . $this->_yearCheck($start_year, $start_year); - if ($start_month < 12) { - $code .= $this->_monthCheck($start_month + 1, 12) - . $this->_vacationCode() - . "\n}\n"; - } - $code .= $this->_monthCheck($start_month, $start_month) - . $this->_dayCheck($start_day, 31) - . $this->_vacationCode() - . "\n}\n}\n}\n" - . $this->_yearCheck($end_year, $end_year); - if ($end_month > 1) { - $code .= $this->_monthCheck(1, $end_month - 1) - . $this->_vacationCode() - . "\n}\n"; - } - $code .= $this->_monthCheck($end_month, $end_month) - . $this->_dayCheck(1, $end_day) - . $this->_vacationCode() - . "\n}\n}\n}\n"; - } elseif ($end_year == $start_year + 1) { - $code .= $this->_yearCheck($start_year, $start_year); - if ($start_month < 12) { - $code .= $this->_monthCheck($start_month + 1, 12) - . $this->_vacationCode() - . "\n}\n"; - } - $code .= $this->_monthCheck($start_month, $start_month) - . $this->_dayCheck($start_day, 31) - . $this->_vacationCode() - . "\n}\n}\n}\n" - . $this->_yearCheck($end_year, $end_year); - if ($end_month > 1) { - $code .= $this->_monthCheck(1, $end_month - 1) - . $this->_vacationCode() - . "\n}\n"; - } - $code .= $this->_monthCheck($end_month, $end_month) - . $this->_dayCheck(1, $end_day) - . $this->_vacationCode() - . "\n}\n}\n}\n"; - } elseif ($end_year == $start_year) { - $code .= $this->_yearCheck($start_year, $start_year); - if ($end_month > $start_month) { - if ($end_month > $start_month + 1) { - $code .= $this->_monthCheck($start_month + 1, $end_month - 1) - . $this->_vacationCode() - . "\n}\n"; - } - $code .= $this->_monthCheck($start_month, $start_month) - . $this->_dayCheck($start_day, 31) - . $this->_vacationCode() - . "\n}\n}\n" - . $this->_monthCheck($end_month, $end_month) - . $this->_dayCheck(1, $end_day) - . $this->_vacationCode() - . "\n}\n}\n"; - } elseif ($end_month == $start_month) { - $code .= $this->_monthCheck($start_month, $start_month) - . $this->_dayCheck($start_day, $end_day) - . $this->_vacationCode() - . "\n}\n}\n"; - } - $code .= "}\n"; - } - - return $code; - } - - /** - * Checks if the rule parameters are valid. - * - * @return boolean|string True if this rule is valid, an error message - * otherwise. - */ - function check() - { - if (empty($this->_vars['reason'])) { - return _("Missing reason in vacation."); - } - - return true; - } - - /** - * Returns a list of sieve extensions required for this rule and any - * sub-rules. - * - * @return array A Sieve extension list. - */ - function requires() - { - return array('vacation', 'regex'); - } - - /** - */ - function _vacationCode() - { - $code = 'vacation :days ' . $this->_vars['days'] . ' '; - $addresses = $this->_vars['addresses']; - $stringlist = ''; - if (count($addresses) > 1) { - foreach ($addresses as $address) { - $address = trim($address); - if (!empty($address)) { - $stringlist .= empty($stringlist) ? '"' : ', "'; - $stringlist .= Ingo_Script_sieve::escapeString($address) . '"'; - } - } - $stringlist = "[" . $stringlist . "] "; - } elseif (count($addresses) == 1) { - $stringlist = '"' . Ingo_Script_sieve::escapeString($addresses[0]) . '" '; - } - - if (!empty($stringlist)) { - $code .= ':addresses ' . $stringlist; - } - - if (!empty($this->_vars['subject'])) { - include_once 'Horde/MIME.php'; - $code .= ':subject "' . MIME::encode(Ingo_Script_sieve::escapeString($this->_vars['subject']), 'UTF-8') . '" '; - } - return $code - . '"' . Ingo_Script_sieve::escapeString($this->_vars['reason']) - . '";'; - } - - /** - */ - function _yearCheck($begin, $end) - { - $code = 'if header :regex "Received" "^.*(' . $begin; - for ($i = $begin + 1; $i <= $end; $i++) { - $code .= '|' . $i; - } - return $code - . ') (\\\\(.*\\\\) )?..:..:.. (\\\\(.*\\\\) )?(\\\\+|\\\\-)....( \\\\(.*\\\\))?$" {' - . "\n "; - } - - /** - */ - function _monthCheck($begin, $end) - { - $months = array('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', - 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'); - $code = 'if header :regex "Received" "^.*(' . $months[$begin - 1]; - for ($i = $begin + 1; $i <= $end; $i++) { - $code .= '|' . $months[$i - 1]; - } - return $code - . ') (\\\\(.*\\\\) )?.... (\\\\(.*\\\\) )?..:..:.. (\\\\(.*\\\\) )?(\\\\+|\\\\-)....( \\\\(.*\\\\))?$" {' - . "\n "; - } - - /** - */ - function _dayCheck($begin, $end) - { - $code = 'if header :regex "Received" "^.*(' . str_repeat('[0 ]', 2 - strlen($begin)) . $begin; - for ($i = $begin + 1; $i <= $end; $i++) { - $code .= '|' . str_repeat('[0 ]', 2 - strlen($i)) . $i; - } - return $code - . ') (\\\\(.*\\\\) )?... (\\\\(.*\\\\) )?.... (\\\\(.*\\\\) )?..:..:.. (\\\\(.*\\\\) )?(\\\\+|\\\\-)....( \\\\(.*\\\\))?$" {' - . "\n "; - } - -} - -/** - * The Sieve_Action_Flag class is the base class for flag actions. - * - * @author Michael Slusarz - * @since Ingo 0.1 - * @package Ingo - */ -class Sieve_Action_Flag extends Sieve_Action { - - /** - * Constructor. - * - * @params array $vars Any required parameters. - */ - function Sieve_Action_Flag($vars = array()) - { - if (isset($vars['flags'])) { - if ($vars['flags'] & Ingo_Storage::FLAG_ANSWERED) { - $this->_vars['flags'][] = '\Answered'; - } - if ($vars['flags'] & Ingo_Storage::FLAG_DELETED) { - $this->_vars['flags'][] = '\Deleted'; - } - if ($vars['flags'] & Ingo_Storage::FLAG_FLAGGED) { - $this->_vars['flags'][] = '\Flagged'; - } - if ($vars['flags'] & Ingo_Storage::FLAG_SEEN) { - $this->_vars['flags'][] = '\Seen'; - } - } else { - $this->_vars['flags'] = ''; - } - } - - /** - * Returns a script snippet representing this rule and any sub-rules. - * - * @param string $mode The sieve flag command to use. Either 'removeflag' - * or 'addflag'. - * - * @return string A Sieve script snippet. - */ - function _toCode($mode) - { - $code = ''; - - if (is_array($this->_vars['flags']) && !empty($this->_vars['flags'])) { - $code .= $mode . ' '; - if (count($this->_vars['flags']) > 1) { - $stringlist = ''; - foreach ($this->_vars['flags'] as $flag) { - $flag = trim($flag); - if (!empty($flag)) { - $stringlist .= empty($stringlist) ? '"' : ', "'; - $stringlist .= Ingo_Script_sieve::escapeString($flag) . '"'; - } - } - $stringlist = '[' . $stringlist . ']'; - $code .= $stringlist . ';'; - } else { - $code .= '"' . Ingo_Script_sieve::escapeString($this->_vars['flags'][0]) . '";'; - } - } - return $code; - } - - /** - * Checks if the rule parameters are valid. - * - * @return boolean|string True if this rule is valid, an error message - * otherwise. - */ - function check() - { - return true; - } - - /** - * Returns a list of sieve extensions required for this rule and any - * sub-rules. - * - * @return array A Sieve extension list. - */ - function requires() - { - return array('imapflags'); - } - -} - -/** - * The Sieve_Action_Addflag class represents an add flag action. - * - * @author Mike Cochrane - * @since Ingo 0.1 - * @package Ingo - */ -class Sieve_Action_Addflag extends Sieve_Action_Flag { - - /** - * Returns a script snippet representing this rule and any sub-rules. - * - * @return string A Sieve script snippet. - */ - function toCode() - { - return $this->_toCode('addflag'); - } - -} - -/** - * The Sieve_Action_Removeflag class represents a remove flag action. - * - * @author Mike Cochrane - * @since Ingo 0.1 - * @package Ingo - */ -class Sieve_Action_Removeflag extends Sieve_Action_Flag { - - /** - * Returns a script snippet representing this rule and any sub-rules. - * - * @return string A Sieve script snippet. - */ - function toCode() - { - return $this->_toCode('removeflag'); - } - -} - -/** - * The Sieve_Action_Notify class represents a notify action. - * - * @author Paul Wolstenholme - * @since Ingo 1.1 - * @package Ingo - */ -class Sieve_Action_Notify extends Sieve_Action { - - /** - * Constructor. - * - * @params array $vars Any required parameters. - */ - function Sieve_Action_Notify($vars = array()) - { - $this->_vars['address'] = isset($vars['address']) ? $vars['address'] : ''; - $this->_vars['name'] = isset($vars['name']) ? $vars['name'] : ''; - } - - /** - * Returns a script snippet representing this rule and any sub-rules. - * - * @return string A Sieve script snippet. - */ - function toCode() - { - return 'notify :method "mailto" :options "' . - Ingo_Script_sieve::escapeString($this->_vars['address']) . - '" :message "' . - _("You have received a new message") . "\n" . - _("From:") . " \$from\$ \n" . - _("Subject:") . " \$subject\$ \n" . - _("Rule:") . ' ' . $this->_vars['name'] . '";'; - } - - /** - * Checks if the rule parameters are valid. - * - * @return boolean|string True if this rule is valid, an error message - * otherwise. - */ - function check() - { - if (empty($this->_vars['address'])) { - return _("Missing address to notify"); - } - - return true; - } - - /** - * Returns a list of sieve extensions required for this rule and any - * sub-rules. - * - * @return array A Sieve extension list. - */ - function requires() - { - return array('notify'); - } - -} diff --git a/ingo/lib/Storage.php b/ingo/lib/Storage.php index c6274827f..8e908a454 100644 --- a/ingo/lib/Storage.php +++ b/ingo/lib/Storage.php @@ -89,7 +89,7 @@ class Ingo_Storage $params = Horde::getDriverConfig('storage', $driver); } - $class = 'Ingo_Storage_' . $driver; + $class = 'Ingo_Storage_' . ucfirst($driver); return class_exists($class) ? new $class($params) : false; @@ -100,7 +100,7 @@ class Ingo_Storage */ public function __destruct() { - $cache = &Horde_SessionObjects::singleton(); + $cache = Horde_SessionObjects::singleton(); /* Store the current objects. */ foreach ($this->_cache as $key => $val) { @@ -122,7 +122,7 @@ class Ingo_Storage * @param boolean $cache Use the cached object? * @param boolean $readonly Whether to disable any write operations. * - * @return Ingo_Storage_rule|Ingo_Storage_filters The specified object. + * @return Ingo_Storage_Rule|Ingo_Storage_Filters The specified object. */ public function retrieve($field, $cache = true, $readonly = false) { @@ -131,7 +131,7 @@ class Ingo_Storage if (!isset($this->_cache[$field])) { $this->_cache[$field] = array('mod' => false); if (isset($_SESSION['ingo']['storage'][$field])) { - $cacheSess = &Horde_SessionObjects::singleton(); + $cacheSess = Horde_SessionObjects::singleton(); $this->_cache[$field]['ob'] = $cacheSess->query($_SESSION['ingo']['storage'][$field]); } else { $this->_cache[$field]['ob'] = &$this->_retrieve($field, $readonly); @@ -154,7 +154,7 @@ class Ingo_Storage * See lib/Storage.php for the available fields. * @param boolean $readonly Whether to disable any write operations. * - * @return Ingo_Storage_rule|Ingo_Storage_filters The specified data. + * @return Ingo_Storage_Rule|Ingo_Storage_Filters The specified data. */ protected function _retrieve($field, $readonly = false) { @@ -164,7 +164,7 @@ class Ingo_Storage /** * Stores the specified data. * - * @param Ingo_Storage_rule|Ingo_Storage_filters $ob The object to store. + * @param Ingo_Storage_Rule|Ingo_Storage_Filters $ob The object to store. * @param boolean $cache Cache the object? * * @return boolean True on success. @@ -221,7 +221,7 @@ class Ingo_Storage * * @abstract * - * @param Ingo_Storage_rule|Ingo_Storage_filters $ob The object to store. + * @param Ingo_Storage_Rule|Ingo_Storage_Filters $ob The object to store. * * @return boolean True on success. */ @@ -365,666 +365,3 @@ class Ingo_Storage } } - -/** - * Ingo_Storage_rule:: is the base class for the various action objects - * used by Ingo_Storage. - * - * @author Michael Slusarz - * @package Ingo - */ -class Ingo_Storage_rule -{ - /** - * The object type. - * - * @var integer - */ - protected $_obtype; - - /** - * Whether the rule has been saved (if being saved separately). - * - * @var boolean - */ - protected $_saved = false; - - /** - * Returns the object rule type. - * - * @return integer The object rule type. - */ - public function obType() - { - return $this->_obtype; - } - - /** - * Marks the rule as saved or unsaved. - * - * @param boolean $data Whether the rule has been saved. - */ - public function setSaved($data) - { - $this->_saved = $data; - } - - /** - * Returns whether the rule has been saved. - * - * @return boolean True if the rule has been saved. - */ - public function isSaved() - { - return $this->_saved; - } - - /** - * Function to manage an internal address list. - * - * @param mixed $data The incoming data (array or string). - * @param boolean $sort Sort the list? - * - * @return array The address list. - */ - protected function _addressList($data, $sort) - { - $output = array(); - - if (is_array($data)) { - $output = $data; - } else { - $data = trim($data); - $output = (empty($data)) ? array() : preg_split("/\s+/", $data); - } - - if ($sort) { - $output = Horde_Array::prepareAddressList($output); - } - - return $output; - } - -} - -/** - * Ingo_Storage_blacklist is the object used to hold blacklist rule - * information. - * - * @author Michael Slusarz - * @package Ingo - */ -class Ingo_Storage_blacklist extends Ingo_Storage_rule -{ - protected $_addr = array(); - protected $_folder = ''; - protected $_obtype = Ingo_Storage::ACTION_BLACKLIST; - - /** - * Sets the list of blacklisted addresses. - * - * @param mixed $data The list of addresses (array or string). - * @param boolean $sort Sort the list? - * - * @return mixed PEAR_Error on error, true on success. - */ - public function setBlacklist($data, $sort = true) - { - $addr = &$this->_addressList($data, $sort); - if (!empty($GLOBALS['conf']['storage']['maxblacklist'])) { - $addr_count = count($addr); - if ($addr_count > $GLOBALS['conf']['storage']['maxblacklist']) { - return PEAR::raiseError(sprintf(_("Maximum number of blacklisted addresses exceeded (Total addresses: %s, Maximum addresses: %s). Could not add new addresses to blacklist."), $addr_count, $GLOBALS['conf']['storage']['maxblacklist']), 'horde.error'); - } - } - - $this->_addr = $addr; - return true; - } - - public function setBlacklistFolder($data) - { - $this->_folder = $data; - } - - public function getBlacklist() - { - return empty($this->_addr) - ? array() - : array_filter($this->_addr, array('Ingo', 'filterEmptyAddress')); - } - - public function getBlacklistFolder() - { - return $this->_folder; - } - -} - -/** - * Ingo_Storage_whitelist is the object used to hold whitelist rule - * information. - * - * @author Michael Slusarz - * @package Ingo - */ -class Ingo_Storage_whitelist extends Ingo_Storage_rule -{ - protected $_addr = array(); - protected $_obtype = Ingo_Storage::ACTION_WHITELIST; - - /** - * Sets the list of whitelisted addresses. - * - * @param mixed $data The list of addresses (array or string). - * @param boolean $sort Sort the list? - * - * @return mixed PEAR_Error on error, true on success. - */ - public function setWhitelist($data, $sort = true) - { - $addr = &$this->_addressList($data, $sort); - $addr = array_filter($addr, array('Ingo', 'filterEmptyAddress')); - if (!empty($GLOBALS['conf']['storage']['maxwhitelist'])) { - $addr_count = count($addr); - if ($addr_count > $GLOBALS['conf']['storage']['maxwhitelist']) { - return PEAR::raiseError(sprintf(_("Maximum number of whitelisted addresses exceeded (Total addresses: %s, Maximum addresses: %s). Could not add new addresses to whitelist."), $addr_count, $GLOBALS['conf']['storage']['maxwhitelist']), 'horde.error'); - } - } - - $this->_addr = $addr; - return true; - } - - public function getWhitelist() - { - return empty($this->_addr) - ? array() - : array_filter($this->_addr, array('Ingo', 'filterEmptyAddress')); - } - -} - -/** - * Ingo_Storage_forward is the object used to hold mail forwarding rule - * information. - * - * @author Michael Slusarz - * @package Ingo - */ -class Ingo_Storage_forward extends Ingo_Storage_rule -{ - protected $_addr = array(); - protected $_keep = true; - protected $_obtype = Ingo_Storage::ACTION_FORWARD; - - public function setForwardAddresses($data, $sort = true) - { - $this->_addr = &$this->_addressList($data, $sort); - } - - public function setForwardKeep($data) - { - $this->_keep = $data; - } - - public function getForwardAddresses() - { - if (is_array($this->_addr)) { - foreach ($this->_addr as $key => $val) { - if (empty($val)) { - unset($this->_addr[$key]); - } - } - } - return $this->_addr; - } - - public function getForwardKeep() - { - return $this->_keep; - } - -} - -/** - * Ingo_Storage_vacation is the object used to hold vacation rule - * information. - * - * @author Michael Slusarz - * @package Ingo - */ -class Ingo_Storage_vacation extends Ingo_Storage_rule -{ - protected $_addr = array(); - protected $_days = 7; - protected $_excludes = array(); - protected $_ignorelist = true; - protected $_reason = ''; - protected $_subject = ''; - protected $_start; - protected $_end; - protected $_obtype = Ingo_Storage::ACTION_VACATION; - - public function setVacationAddresses($data, $sort = true) - { - $this->_addr = &$this->_addressList($data, $sort); - } - - public function setVacationDays($data) - { - $this->_days = $data; - } - - public function setVacationExcludes($data, $sort = true) - { - $this->_excludes = &$this->_addressList($data, $sort); - } - - public function setVacationIgnorelist($data) - { - $this->_ignorelist = $data; - } - - public function setVacationReason($data) - { - $this->_reason = $data; - } - - public function setVacationSubject($data) - { - $this->_subject = $data; - } - - public function setVacationStart($data) - { - $this->_start = $data; - } - - public function setVacationEnd($data) - { - $this->_end = $data; - } - - public function getVacationAddresses() - { - if (empty($GLOBALS['conf']['hooks']['vacation_addresses'])) { - return $this->_addr; - } - - try { - return Horde::callHook('_ingo_hook_vacation_addresses', array(Ingo::getUser()), 'ingo'); - } catch (Horde_Exception $e) { - return array(); - } - } - - public function getVacationDays() - { - return $this->_days; - } - - public function getVacationExcludes() - { - return $this->_excludes; - } - - public function getVacationIgnorelist() - { - return $this->_ignorelist; - } - - public function getVacationReason() - { - return $this->_reason; - } - - public function getVacationSubject() - { - return $this->_subject; - } - - public function getVacationStart() - { - return $this->_start; - } - - public function getVacationStartYear() - { - return date('Y', $this->_start); - } - - public function getVacationStartMonth() - { - return date('n', $this->_start); - } - - public function getVacationStartDay() - { - return date('j', $this->_start); - } - - public function getVacationEnd() - { - return $this->_end; - } - - public function getVacationEndYear() - { - return date('Y', $this->_end); - } - - public function getVacationEndMonth() - { - return date('n', $this->_end); - } - - public function getVacationEndDay() - { - return date('j', $this->_end); - } - -} - -/** - * Ingo_Storage_spam is an object used to hold default spam-rule filtering - * information. - * - * @author Jason M. Felice - * @package Ingo - */ -class Ingo_Storage_spam extends Ingo_Storage_rule -{ - - /** - * The object type. - * - * @var integer - */ - protected $_obtype = Ingo_Storage::ACTION_SPAM; - - protected $_folder = null; - protected $_level = 5; - - public function setSpamFolder($folder) - { - $this->_folder = $folder; - } - - public function setSpamLevel($level) - { - $this->_level = $level; - } - - public function getSpamFolder() - { - return $this->_folder; - } - - public function getSpamLevel() - { - return $this->_level; - } - -} - -/** - * Ingo_Storage_filters is the object used to hold user-defined filtering rule - * information. - * - * @author Michael Slusarz - * @package Ingo - */ -class Ingo_Storage_filters -{ - /** - * The filter list. - * - * @var array - */ - protected $_filters = array(); - - /** - * The object type. - * - * @var integer - */ - protected $_obtype = Ingo_Storage::ACTION_FILTERS; - - /** - * Returns the object rule type. - * - * @return integer The object rule type. - */ - public function obType() - { - return $this->_obtype; - } - - /** - * Propagates the filter list with data. - * - * @param array $data A list of rule hashes. - */ - public function setFilterlist($data) - { - $this->_filters = $data; - } - - /** - * Returns the filter list. - * - * @return array The list of rule hashes. - */ - public function getFilterList() - { - return $this->_filters; - } - - /** - * Return the filter entry for a given ID. - * - * @return mixed The rule hash entry, or false if not defined. - */ - public function getFilter($id) - { - return isset($this->_filters[$id]) - ? $this->_filters[$id] - : false; - } - - /** - * Returns a single rule hash. - * - * @param integer $id A rule number. - * - * @return array The requested rule hash. - */ - public function getRule($id) - { - return $this->_filters[$id]; - } - - /** - * Returns a rule hash with default value used when creating new rules. - * - * @return array A rule hash. - */ - public function getDefaultRule() - { - return array( - 'name' => _("New Rule"), - 'combine' => Ingo_Storage::COMBINE_ALL, - 'conditions' => array(), - 'action' => Ingo_Storage::ACTION_KEEP, - 'action-value' => '', - 'stop' => true, - 'flags' => 0, - 'disable' => false - ); - } - - /** - * Searches for the first rule of a certain action type and returns its - * number. - * - * @param integer $action The field type of the searched rule - * (ACTION_* constants). - * - * @return integer The number of the first matching rule or null. - */ - public function findRuleId($action) - { - foreach ($this->_filters as $id => $rule) { - if ($rule['action'] == $action) { - return $id; - } - } - } - - /** - * Searches for and returns the first rule of a certain action type. - * - * @param integer $action The field type of the searched rule - * (ACTION_* constants). - * - * @return array The first matching rule hash or null. - */ - public function findRule($action) - { - $id = $this->findRuleId($action); - if ($id !== null) { - return $this->getRule($id); - } - } - - /** - * Adds a rule hash to the filters list. - * - * @param array $rule A rule hash. - * @param boolean $default If true merge the rule hash with default rule - * values. - */ - public function addRule($rule, $default = true) - { - if ($default) { - $this->_filters[] = array_merge($this->getDefaultRule(), $rule); - } else { - $this->_filters[] = $rule; - } - } - - /** - * Updates an existing rule with a rule hash. - * - * @param array $rule A rule hash - * @param integer $id A rule number - */ - public function updateRule($rule, $id) - { - $this->_filters[$id] = $rule; - } - - /** - * Deletes a rule from the filters list. - * - * @param integer $id Number of the rule to delete. - * - * @return boolean True if the rule has been found and deleted. - */ - public function deleteRule($id) - { - if (isset($this->_filters[$id])) { - unset($this->_filters[$id]); - $this->_filters = array_values($this->_filters); - return true; - } - - return false; - } - - /** - * Creates a copy of an existing rule. - * - * The created copy is added to the filters list right after the original - * rule. - * - * @param integer $id Number of the rule to copy. - * - * @return boolean True if the rule has been found and copied. - */ - public function copyRule($id) - { - if (isset($this->_filters[$id])) { - $newrule = $this->_filters[$id]; - $newrule['name'] = sprintf(_("Copy of %s"), $this->_filters[$id]['name']); - $this->_filters = array_merge(array_slice($this->_filters, 0, $id + 1), array($newrule), array_slice($this->_filters, $id + 1)); - return true; - } - - return false; - } - - /** - * Moves a rule up in the filters list. - * - * @param integer $id Number of the rule to move. - * @param integer $steps Number of positions to move the rule up. - */ - public function ruleUp($id, $steps = 1) - { - for ($i = 0; $i < $steps && $id > 0;) { - $temp = $this->_filters[$id - 1]; - $this->_filters[$id - 1] = $this->_filters[$id]; - $this->_filters[$id] = $temp; - /* Continue to move up until we swap with a viewable category. */ - if (in_array($temp['action'], $_SESSION['ingo']['script_categories'])) { - $i++; - } - $id--; - } - } - - /** - * Moves a rule down in the filters list. - * - * @param integer $id Number of the rule to move. - * @param integer $steps Number of positions to move the rule down. - */ - public function ruleDown($id, $steps = 1) - { - $rulecount = count($this->_filters) - 1; - for ($i = 0; $i < $steps && $id < $rulecount;) { - $temp = $this->_filters[$id + 1]; - $this->_filters[$id + 1] = $this->_filters[$id]; - $this->_filters[$id] = $temp; - /* Continue to move down until we swap with a viewable - category. */ - if (in_array($temp['action'], $_SESSION['ingo']['script_categories'])) { - $i++; - } - $id++; - } - } - - /** - * Disables a rule. - * - * @param integer $id Number of the rule to disable. - */ - public function ruleDisable($id) - { - $this->_filters[$id]['disable'] = true; - } - - /** - * Enables a rule. - * - * @param integer $id Number of the rule to enable. - */ - public function ruleEnable($id) - { - $this->_filters[$id]['disable'] = false; - } - -} diff --git a/ingo/lib/Storage/Blacklist.php b/ingo/lib/Storage/Blacklist.php new file mode 100644 index 000000000..2733db5ef --- /dev/null +++ b/ingo/lib/Storage/Blacklist.php @@ -0,0 +1,57 @@ + + * @package Ingo + */ +class Ingo_Storage_Blacklist extends Ingo_Storage_Rule +{ + protected $_addr = array(); + protected $_folder = ''; + protected $_obtype = Ingo_Storage::ACTION_BLACKLIST; + + /** + * Sets the list of blacklisted addresses. + * + * @param mixed $data The list of addresses (array or string). + * @param boolean $sort Sort the list? + * + * @return mixed PEAR_Error on error, true on success. + */ + public function setBlacklist($data, $sort = true) + { + $addr = &$this->_addressList($data, $sort); + if (!empty($GLOBALS['conf']['storage']['maxblacklist'])) { + $addr_count = count($addr); + if ($addr_count > $GLOBALS['conf']['storage']['maxblacklist']) { + return PEAR::raiseError(sprintf(_("Maximum number of blacklisted addresses exceeded (Total addresses: %s, Maximum addresses: %s). Could not add new addresses to blacklist."), $addr_count, $GLOBALS['conf']['storage']['maxblacklist']), 'horde.error'); + } + } + + $this->_addr = $addr; + return true; + } + + public function setBlacklistFolder($data) + { + $this->_folder = $data; + } + + public function getBlacklist() + { + return empty($this->_addr) + ? array() + : array_filter($this->_addr, array('Ingo', 'filterEmptyAddress')); + } + + public function getBlacklistFolder() + { + return $this->_folder; + } + +} diff --git a/ingo/lib/Storage/Filters.php b/ingo/lib/Storage/Filters.php new file mode 100644 index 000000000..4fdea7c83 --- /dev/null +++ b/ingo/lib/Storage/Filters.php @@ -0,0 +1,264 @@ + + * @package Ingo + */ +class Ingo_Storage_Filters +{ + /** + * The filter list. + * + * @var array + */ + protected $_filters = array(); + + /** + * The object type. + * + * @var integer + */ + protected $_obtype = Ingo_Storage::ACTION_FILTERS; + + /** + * Returns the object rule type. + * + * @return integer The object rule type. + */ + public function obType() + { + return $this->_obtype; + } + + /** + * Propagates the filter list with data. + * + * @param array $data A list of rule hashes. + */ + public function setFilterlist($data) + { + $this->_filters = $data; + } + + /** + * Returns the filter list. + * + * @return array The list of rule hashes. + */ + public function getFilterList() + { + return $this->_filters; + } + + /** + * Return the filter entry for a given ID. + * + * @return mixed The rule hash entry, or false if not defined. + */ + public function getFilter($id) + { + return isset($this->_filters[$id]) + ? $this->_filters[$id] + : false; + } + + /** + * Returns a single rule hash. + * + * @param integer $id A rule number. + * + * @return array The requested rule hash. + */ + public function getRule($id) + { + return $this->_filters[$id]; + } + + /** + * Returns a rule hash with default value used when creating new rules. + * + * @return array A rule hash. + */ + public function getDefaultRule() + { + return array( + 'name' => _("New Rule"), + 'combine' => Ingo_Storage::COMBINE_ALL, + 'conditions' => array(), + 'action' => Ingo_Storage::ACTION_KEEP, + 'action-value' => '', + 'stop' => true, + 'flags' => 0, + 'disable' => false + ); + } + + /** + * Searches for the first rule of a certain action type and returns its + * number. + * + * @param integer $action The field type of the searched rule + * (ACTION_* constants). + * + * @return integer The number of the first matching rule or null. + */ + public function findRuleId($action) + { + foreach ($this->_filters as $id => $rule) { + if ($rule['action'] == $action) { + return $id; + } + } + } + + /** + * Searches for and returns the first rule of a certain action type. + * + * @param integer $action The field type of the searched rule + * (ACTION_* constants). + * + * @return array The first matching rule hash or null. + */ + public function findRule($action) + { + $id = $this->findRuleId($action); + if ($id !== null) { + return $this->getRule($id); + } + } + + /** + * Adds a rule hash to the filters list. + * + * @param array $rule A rule hash. + * @param boolean $default If true merge the rule hash with default rule + * values. + */ + public function addRule($rule, $default = true) + { + if ($default) { + $this->_filters[] = array_merge($this->getDefaultRule(), $rule); + } else { + $this->_filters[] = $rule; + } + } + + /** + * Updates an existing rule with a rule hash. + * + * @param array $rule A rule hash + * @param integer $id A rule number + */ + public function updateRule($rule, $id) + { + $this->_filters[$id] = $rule; + } + + /** + * Deletes a rule from the filters list. + * + * @param integer $id Number of the rule to delete. + * + * @return boolean True if the rule has been found and deleted. + */ + public function deleteRule($id) + { + if (isset($this->_filters[$id])) { + unset($this->_filters[$id]); + $this->_filters = array_values($this->_filters); + return true; + } + + return false; + } + + /** + * Creates a copy of an existing rule. + * + * The created copy is added to the filters list right after the original + * rule. + * + * @param integer $id Number of the rule to copy. + * + * @return boolean True if the rule has been found and copied. + */ + public function copyRule($id) + { + if (isset($this->_filters[$id])) { + $newrule = $this->_filters[$id]; + $newrule['name'] = sprintf(_("Copy of %s"), $this->_filters[$id]['name']); + $this->_filters = array_merge(array_slice($this->_filters, 0, $id + 1), array($newrule), array_slice($this->_filters, $id + 1)); + return true; + } + + return false; + } + + /** + * Moves a rule up in the filters list. + * + * @param integer $id Number of the rule to move. + * @param integer $steps Number of positions to move the rule up. + */ + public function ruleUp($id, $steps = 1) + { + for ($i = 0; $i < $steps && $id > 0;) { + $temp = $this->_filters[$id - 1]; + $this->_filters[$id - 1] = $this->_filters[$id]; + $this->_filters[$id] = $temp; + /* Continue to move up until we swap with a viewable category. */ + if (in_array($temp['action'], $_SESSION['ingo']['script_categories'])) { + $i++; + } + $id--; + } + } + + /** + * Moves a rule down in the filters list. + * + * @param integer $id Number of the rule to move. + * @param integer $steps Number of positions to move the rule down. + */ + public function ruleDown($id, $steps = 1) + { + $rulecount = count($this->_filters) - 1; + for ($i = 0; $i < $steps && $id < $rulecount;) { + $temp = $this->_filters[$id + 1]; + $this->_filters[$id + 1] = $this->_filters[$id]; + $this->_filters[$id] = $temp; + /* Continue to move down until we swap with a viewable + category. */ + if (in_array($temp['action'], $_SESSION['ingo']['script_categories'])) { + $i++; + } + $id++; + } + } + + /** + * Disables a rule. + * + * @param integer $id Number of the rule to disable. + */ + public function ruleDisable($id) + { + $this->_filters[$id]['disable'] = true; + } + + /** + * Enables a rule. + * + * @param integer $id Number of the rule to enable. + */ + public function ruleEnable($id) + { + $this->_filters[$id]['disable'] = false; + } + +} diff --git a/ingo/lib/Storage/Filters/Sql.php b/ingo/lib/Storage/Filters/Sql.php new file mode 100644 index 000000000..2202d72c1 --- /dev/null +++ b/ingo/lib/Storage/Filters/Sql.php @@ -0,0 +1,344 @@ + + * @package Ingo + */ +class Ingo_Storage_Filters_Sql extends Ingo_Storage_Filters { + + /** + * Handle for the current database connection. + * + * @var DB + */ + protected $_db; + + /** + * Handle for the current database connection, used for writing. + * + * Defaults to the same handle as $_db if a separate write database is not + * required. + * + * @var DB + */ + protected $_write_db; + + /** + * Driver specific parameters. + * + * @var array + */ + protected $_params; + + /** + * Constructor. + * + * @param DB $db Handle for the database connection. + * @param DB $write_db Handle for the database connection, used for + * writing. + * @param array $params Driver specific parameters. + */ + public function __construct($db, $write_db, $params) + { + $this->_db = $db; + $this->_write_db = $write_db; + $this->_params = $params; + } + + /** + * Loads all rules from the DB backend. + * + * @param boolean $readonly Whether to disable any write operations. + */ + public function init($readonly = false) + { + $query = sprintf('SELECT * FROM %s WHERE rule_owner = ? ORDER BY rule_order', + $this->_params['table_rules']); + $values = array(Ingo::getUser()); + Horde::logMessage('Ingo_Storage_Filters_Sql(): ' . $query, __FILE__, __LINE__, PEAR_LOG_DEBUG); + $result = $this->_db->query($query, $values); + if (is_a($result, 'PEAR_Error')) { + return $result; + } + $data = array(); + while ($row = $result->fetchRow(DB_FETCHMODE_ASSOC)) { + $data[$row['rule_order']] = array( + 'id' => (int)$row['rule_id'], + 'name' => Horde_String::convertCharset($row['rule_name'], $this->_params['charset']), + 'action' => (int)$row['rule_action'], + 'action-value' => Horde_String::convertCharset($row['rule_value'], $this->_params['charset']), + 'flags' => (int)$row['rule_flags'], + 'conditions' => empty($row['rule_conditions']) ? null : Horde_String::convertCharset(unserialize($row['rule_conditions']), $this->_params['charset']), + 'combine' => (int)$row['rule_combine'], + 'stop' => (bool)$row['rule_stop'], + 'disable' => !(bool)$row['rule_active']); + } + $this->setFilterlist($data); + + if (empty($data) && !$readonly) { + $data = @unserialize($GLOBALS['prefs']->getDefault('rules')); + if ($data) { + foreach ($data as $val) { + $this->addRule($val, false); + } + } else { + $this->addRule( + array('name' => 'Whitelist', + 'action' => Ingo_Storage::ACTION_WHITELIST), + false); + $this->addRule( + array('name' => 'Vacation', + 'action' => Ingo_Storage::ACTION_VACATION, + 'disable' => true), + false); + $this->addRule( + array('name' => 'Blacklist', + 'action' => Ingo_Storage::ACTION_BLACKLIST), + false); + $this->addRule( + array('name' => 'Spam Filter', + 'action' => Ingo_Storage::ACTION_SPAM, + 'disable' => true), + false); + $this->addRule( + array('name' => 'Forward', + 'action' => Ingo_Storage::ACTION_FORWARD), + false); + } + } + } + + /** + * Converts a rule hash from Ingo's internal format to the database + * format. + * + * @param array $rule Rule hash in Ingo's format. + * + * @return array Rule hash in DB's format. + */ + protected function _ruleToBackend($rule) + { + return array(Horde_String::convertCharset($rule['name'], Horde_Nls::getCharset(), $this->_params['charset']), + (int)$rule['action'], + isset($rule['action-value']) ? Horde_String::convertCharset($rule['action-value'], Horde_Nls::getCharset(), $this->_params['charset']) : null, + isset($rule['flags']) ? (int)$rule['flags'] : null, + isset($rule['conditions']) ? serialize(Horde_String::convertCharset($rule['conditions'], Horde_Nls::getCharset(), $this->_params['charset'])) : null, + isset($rule['combine']) ? (int)$rule['combine'] : null, + isset($rule['stop']) ? (int)$rule['stop'] : null, + isset($rule['disable']) ? (int)(!$rule['disable']) : 1); + } + + /** + * Adds a rule hash to the filters list. + * + * @param array $rule A rule hash. + * @param boolean $default If true merge the rule hash with default rule + * values. + */ + public function addRule($rule, $default = true) + { + if ($default) { + $rule = array_merge($this->getDefaultRule(), $rule); + } + + $query = sprintf('INSERT INTO %s (rule_id, rule_owner, rule_name, rule_action, rule_value, rule_flags, rule_conditions, rule_combine, rule_stop, rule_active, rule_order) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', + $this->_params['table_rules']); + $id = $this->_write_db->nextId($this->_params['table_rules']); + if (is_a($id, 'PEAR_Error')) { + return $id; + } + $order = key(array_reverse($this->_filters, true)) + 1; + $values = array_merge(array($id, Ingo::getUser()), + $this->_ruleToBackend($rule), + array($order)); + Horde::logMessage('Ingo_Storage_Filters_Sql::addRule(): ' . $query, __FILE__, __LINE__, PEAR_LOG_DEBUG); + $result = $this->_write_db->query($query, $values); + if (is_a($result, 'PEAR_Error')) { + Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR); + return $result; + } + + $rule['id'] = $id; + $this->_filters[$order] = $rule; + } + + /** + * Updates an existing rule with a rule hash. + * + * @param array $rule A rule hash + * @param integer $id A rule number + */ + public function updateRule($rule, $id) + { + $query = sprintf('UPDATE %s SET rule_name = ?, rule_action = ?, rule_value = ?, rule_flags = ?, rule_conditions = ?, rule_combine = ?, rule_stop = ?, rule_active = ?, rule_order = ? WHERE rule_id = ? AND rule_owner = ?', + $this->_params['table_rules']); + $values = array_merge($this->_ruleToBackend($rule), + array($id, $rule['id'], Ingo::getUser())); + Horde::logMessage('Ingo_Storage_Filters_Sql::updateRule(): ' . $query, __FILE__, __LINE__, PEAR_LOG_DEBUG); + $result = $this->_write_db->query($query, $values); + if (is_a($result, 'PEAR_Error')) { + Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR); + return $result; + } + + $this->_filters[$id] = $rule; + } + + /** + * Deletes a rule from the filters list. + * + * @param integer $id Number of the rule to delete. + * + * @return boolean True if the rule has been found and deleted. + */ + public function deleteRule($id) + { + if (!isset($this->_filters[$id])) { + return false; + } + + $query = sprintf('DELETE FROM %s WHERE rule_id = ? AND rule_owner = ?', + $this->_params['table_rules']); + $values = array($this->_filters[$id]['id'], Ingo::getUser()); + Horde::logMessage('Ingo_Storage_Filters_Sql::deleteRule(): ' . $query, __FILE__, __LINE__, PEAR_LOG_DEBUG); + $result = $this->_write_db->query($query, $values); + if (is_a($result, 'PEAR_Error')) { + Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR); + return $result; + } + unset($this->_filters[$id]); + + $query = sprintf('UPDATE %s SET rule_order = rule_order - 1 WHERE rule_owner = ? AND rule_order > ?', + $this->_params['table_rules']); + $values = array(Ingo::getUser(), $id); + Horde::logMessage('Ingo_Storage_Filters_Sql::deleteRule(): ' . $query, + __FILE__, __LINE__, PEAR_LOG_DEBUG); + $result = $this->_write_db->query($query, $values); + if (is_a($result, 'PEAR_Error')) { + Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR); + return $result; + } + + return true; + } + + /** + * Creates a copy of an existing rule. + * + * The created copy is added to the filters list right after the original + * rule. + * + * @param integer $id Number of the rule to copy. + * + * @return boolean True if the rule has been found and copied. + */ + public function copyRule($id) + { + if (isset($this->_filters[$id])) { + $newrule = $this->_filters[$id]; + $newrule['name'] = sprintf(_("Copy of %s"), $this->_filters[$id]['name']); + $this->addRule($newrule, false); + $this->ruleUp(count($this->_filters) - 1, count($this->_filters) - $id - 2); + return true; + } + + return false; + } + + /** + * Moves a rule up in the filters list. + * + * @param integer $id Number of the rule to move. + * @param integer $steps Number of positions to move the rule up. + */ + public function ruleUp($id, $steps = 1) + { + return $this->_ruleMove($id, -$steps); + } + + /** + * Moves a rule down in the filters list. + * + * @param integer $id Number of the rule to move. + * @param integer $steps Number of positions to move the rule down. + */ + public function ruleDown($id, $steps = 1) + { + return $this->_ruleMove($id, $steps); + } + + /** + * Moves a rule in the filters list. + * + * @param integer $id Number of the rule to move. + * @param integer $steps Number of positions and direction to move the + * rule. + */ + protected function _ruleMove($id, $steps) + { + $query = sprintf('UPDATE %s SET rule_order = rule_order %s 1 WHERE rule_owner = ? AND rule_order %s ? AND rule_order %s ?', + $this->_params['table_rules'], + $steps > 0 ? '-' : '+', + $steps > 0 ? '>' : '>=', + $steps > 0 ? '<=' : '<'); + $values = array(Ingo::getUser()); + if ($steps < 0) { + $values[] = (int)($id + $steps); + $values[] = (int)$id; + } else { + $values[] = (int)$id; + $values[] = (int)($id + $steps); + } + Horde::logMessage('Ingo_Storage_Filters_Sql::ruleUp(): ' . $query, + __FILE__, __LINE__, PEAR_LOG_DEBUG); + $result = $this->_write_db->query($query, $values); + if (is_a($result, 'PEAR_Error')) { + Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR); + return $result; + } + $query = sprintf('UPDATE %s SET rule_order = ? WHERE rule_owner = ? AND rule_id = ?', + $this->_params['table_rules']); + $values = array((int)($id + $steps), + Ingo::getUser(), + $this->_filters[$id]['id']); + Horde::logMessage('Ingo_Storage_Filters_Sql::ruleUp(): ' . $query, + __FILE__, __LINE__, PEAR_LOG_DEBUG); + $result = $this->_write_db->query($query, $values); + if (is_a($result, 'PEAR_Error')) { + Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR); + return $result; + } + + $this->init(); + } + + /** + * Disables a rule. + * + * @param integer $id Number of the rule to disable. + */ + public function ruleDisable($id) + { + $rule = $this->_filters[$id]; + $rule['disable'] = true; + $this->updateRule($rule, $id); + } + + /** + * Enables a rule. + * + * @param integer $id Number of the rule to enable. + */ + public function ruleEnable($id) + { + $rule = $this->_filters[$id]; + $rule['disable'] = false; + $this->updateRule($rule, $id); + } + +} diff --git a/ingo/lib/Storage/Forward.php b/ingo/lib/Storage/Forward.php new file mode 100644 index 000000000..051dd1f0b --- /dev/null +++ b/ingo/lib/Storage/Forward.php @@ -0,0 +1,45 @@ + + * @package Ingo + */ +class Ingo_Storage_Forward extends Ingo_Storage_Rule +{ + protected $_addr = array(); + protected $_keep = true; + protected $_obtype = Ingo_Storage::ACTION_FORWARD; + + public function setForwardAddresses($data, $sort = true) + { + $this->_addr = &$this->_addressList($data, $sort); + } + + public function setForwardKeep($data) + { + $this->_keep = $data; + } + + public function getForwardAddresses() + { + if (is_array($this->_addr)) { + foreach ($this->_addr as $key => $val) { + if (empty($val)) { + unset($this->_addr[$key]); + } + } + } + return $this->_addr; + } + + public function getForwardKeep() + { + return $this->_keep; + } + +} diff --git a/ingo/lib/Storage/Mock.php b/ingo/lib/Storage/Mock.php new file mode 100644 index 000000000..38825f484 --- /dev/null +++ b/ingo/lib/Storage/Mock.php @@ -0,0 +1,55 @@ + + * @package Ingo + */ + +class Ingo_Storage_Mock extends Ingo_Storage +{ + protected $_data = array(); + + protected function &_retrieve($field) + { + if (empty($this->_data[$field])) { + switch ($field) { + case self::ACTION_BLACKLIST: + return new Ingo_Storage_Blacklist(); + + case self::ACTION_FILTERS: + $ob = new Ingo_Storage_Filters(); + include INGO_BASE . '/config/prefs.php.dist'; + $ob->setFilterList(unserialize($_prefs['rules']['value'])); + return $ob; + + case self::ACTION_FORWARD: + return new Ingo_Storage_Forward(); + + case self::ACTION_VACATION: + return new Ingo_Storage_Vacation(); + + case self::ACTION_WHITELIST: + return new Ingo_Storage_Whitelist(); + + case self::ACTION_SPAM: + return new Ingo_Storage_Spam(); + + default: + return false; + } + } + + return $this->_data[$field]; + } + + protected function _store(&$ob) + { + $this->_data[$ob->obType()] = $ob; + } + +} diff --git a/ingo/lib/Storage/Prefs.php b/ingo/lib/Storage/Prefs.php new file mode 100644 index 000000000..be3547edf --- /dev/null +++ b/ingo/lib/Storage/Prefs.php @@ -0,0 +1,183 @@ + + * @author Jan Schneider + * @package Ingo + */ +class Ingo_Storage_Prefs extends Ingo_Storage +{ + /** + * Constructor. + * + * @param array $params Additional parameters for the subclass. + */ + public function __construct($params = array()) + { + $this->_params = $params; + } + + /** + * Retrieves the specified data from the storage backend. + * + * @param integer $field The field name of the desired data. + * See lib/Storage.php for the available fields. + * @param boolean $readonly Whether to disable any write operations. + * + * @return Ingo_Storage_Rule|Ingo_Storage_Filters The specified data. + */ + protected function _retrieve($field, $readonly = false) + { + $prefs = &Prefs::singleton($GLOBALS['conf']['prefs']['driver'], + $GLOBALS['registry']->getApp(), + Ingo::getUser(), '', null, false); + $prefs->retrieve(); + + switch ($field) { + case self::ACTION_BLACKLIST: + $ob = new Ingo_Storage_Blacklist(); + $data = @unserialize($prefs->getValue('blacklist')); + if ($data) { + $ob->setBlacklist($data['a'], false); + $ob->setBlacklistFolder($data['f']); + } + break; + + case self::ACTION_WHITELIST: + $ob = new Ingo_Storage_Whitelist(); + $data = @unserialize($prefs->getValue('whitelist')); + if ($data) { + $ob->setWhitelist($data, false); + } + break; + + case self::ACTION_FILTERS: + $ob = new Ingo_Storage_Filters(); + $data = @unserialize($prefs->getValue('rules', false)); + if ($data === false) { + /* Convert rules from the old format. */ + $data = @unserialize($prefs->getValue('rules')); + } else { + $data = Horde_String::convertCharset($data, $prefs->getCharset(), Horde_Nls::getCharset()); + } + if ($data) { + $ob->setFilterlist($data); + } + break; + + case self::ACTION_FORWARD: + $ob = new Ingo_Storage_Forward(); + $data = @unserialize($prefs->getValue('forward')); + if ($data) { + $ob->setForwardAddresses($data['a'], false); + $ob->setForwardKeep($data['k']); + } + break; + + case self::ACTION_VACATION: + $ob = new Ingo_Storage_Vacation(); + $data = @unserialize($prefs->getValue('vacation', false)); + if ($data === false) { + /* Convert vacation from the old format. */ + $data = unserialize($prefs->getValue('vacation')); + } elseif (is_array($data)) { + $data = $prefs->convertFromDriver($data, Horde_Nls::getCharset()); + } + if ($data) { + $ob->setVacationAddresses($data['addresses'], false); + $ob->setVacationDays($data['days']); + $ob->setVacationExcludes($data['excludes'], false); + $ob->setVacationIgnorelist($data['ignorelist']); + $ob->setVacationReason($data['reason']); + $ob->setVacationSubject($data['subject']); + if (isset($data['start'])) { + $ob->setVacationStart($data['start']); + } + if (isset($data['end'])) { + $ob->setVacationEnd($data['end']); + } + } + break; + + case self::ACTION_SPAM: + $ob = new Ingo_Storage_Spam(); + $data = @unserialize($prefs->getValue('spam')); + if ($data) { + $ob->setSpamFolder($data['folder']); + $ob->setSpamLevel($data['level']); + } + break; + + default: + $ob = false; + break; + } + + return $ob; + } + + /** + * Stores the specified data in the storage backend. + * + * @param Ingo_Storage_Rule|Ingo_Storage_Filters $ob The object to store. + * + * @return boolean True on success. + */ + protected function _store($ob) + { + $prefs = &Prefs::singleton($GLOBALS['conf']['prefs']['driver'], + $GLOBALS['registry']->getApp(), + Ingo::getUser(), '', null, false); + $prefs->retrieve(); + + switch ($ob->obType()) { + case self::ACTION_BLACKLIST: + $data = array( + 'a' => $ob->getBlacklist(), + 'f' => $ob->getBlacklistFolder(), + ); + return $prefs->setValue('blacklist', serialize($data)); + + case self::ACTION_FILTERS: + return $prefs->setValue('rules', serialize(Horde_String::convertCharset($ob->getFilterList(), Horde_Nls::getCharset(), $prefs->getCharset())), false); + + case self::ACTION_FORWARD: + $data = array( + 'a' => $ob->getForwardAddresses(), + 'k' => $ob->getForwardKeep(), + ); + return $prefs->setValue('forward', serialize($data)); + + case self::ACTION_VACATION: + $data = array( + 'addresses' => $ob->getVacationAddresses(), + 'days' => $ob->getVacationDays(), + 'excludes' => $ob->getVacationExcludes(), + 'ignorelist' => $ob->getVacationIgnorelist(), + 'reason' => $ob->getVacationReason(), + 'subject' => $ob->getVacationSubject(), + 'start' => $ob->getVacationStart(), + 'end' => $ob->getVacationEnd(), + ); + return $prefs->setValue('vacation', serialize($prefs->convertToDriver($data, Horde_Nls::getCharset())), false); + + case self::ACTION_WHITELIST: + return $prefs->setValue('whitelist', serialize($ob->getWhitelist())); + + case self::ACTION_SPAM: + $data = array( + 'folder' => $ob->getSpamFolder(), + 'level' => $ob->getSpamLevel(), + ); + return $prefs->setValue('spam', serialize($data)); + } + + return false; + } + +} diff --git a/ingo/lib/Storage/Rule.php b/ingo/lib/Storage/Rule.php new file mode 100644 index 000000000..9dfb0e7f2 --- /dev/null +++ b/ingo/lib/Storage/Rule.php @@ -0,0 +1,84 @@ + + * @package Ingo + */ +class Ingo_Storage_Rule +{ + /** + * The object type. + * + * @var integer + */ + protected $_obtype; + + /** + * Whether the rule has been saved (if being saved separately). + * + * @var boolean + */ + protected $_saved = false; + + /** + * Returns the object rule type. + * + * @return integer The object rule type. + */ + public function obType() + { + return $this->_obtype; + } + + /** + * Marks the rule as saved or unsaved. + * + * @param boolean $data Whether the rule has been saved. + */ + public function setSaved($data) + { + $this->_saved = $data; + } + + /** + * Returns whether the rule has been saved. + * + * @return boolean True if the rule has been saved. + */ + public function isSaved() + { + return $this->_saved; + } + + /** + * Function to manage an internal address list. + * + * @param mixed $data The incoming data (array or string). + * @param boolean $sort Sort the list? + * + * @return array The address list. + */ + protected function _addressList($data, $sort) + { + $output = array(); + + if (is_array($data)) { + $output = $data; + } else { + $data = trim($data); + $output = (empty($data)) ? array() : preg_split("/\s+/", $data); + } + + if ($sort) { + $output = Horde_Array::prepareAddressList($output); + } + + return $output; + } + +} diff --git a/ingo/lib/Storage/Spam.php b/ingo/lib/Storage/Spam.php new file mode 100644 index 000000000..9f3f2da2b --- /dev/null +++ b/ingo/lib/Storage/Spam.php @@ -0,0 +1,44 @@ + + * @package Ingo + */ +class Ingo_Storage_Spam extends Ingo_Storage_Rule +{ + /** + * The object type. + * + * @var integer + */ + protected $_obtype = Ingo_Storage::ACTION_SPAM; + + protected $_folder = null; + protected $_level = 5; + + public function setSpamFolder($folder) + { + $this->_folder = $folder; + } + + public function setSpamLevel($level) + { + $this->_level = $level; + } + + public function getSpamFolder() + { + return $this->_folder; + } + + public function getSpamLevel() + { + return $this->_level; + } + +} diff --git a/ingo/lib/Storage/Sql.php b/ingo/lib/Storage/Sql.php new file mode 100644 index 000000000..6b3c45bdf --- /dev/null +++ b/ingo/lib/Storage/Sql.php @@ -0,0 +1,396 @@ + + * 'phptype' - The database type (e.g. 'pgsql', 'mysql', etc.). + * 'charset' - The database's internal charset. + * + * Required by some database implementations:
+ *   'database' - The name of the database.
+ *   'hostspec' - The hostname of the database server.
+ *   'protocol' - The communication protocol ('tcp', 'unix', etc.).
+ *   'username' - The username with which to connect to the database.
+ *   'password' - The password associated with 'username'.
+ *   'options'  - Additional options to pass to the database.
+ *   'tty'      - The TTY on which to connect to the database.
+ *   'port'     - The port on which to connect to the database.
+ * + * The table structure can be created by the scripts/drivers/sql/ingo.sql + * script. + * + * See the enclosed file LICENSE for license information (ASL). If you + * did not receive this file, see http://www.horde.org/licenses/asl.php. + * + * @author Jan Schneider + * @package Ingo + */ +class Ingo_Storage_Sql extends Ingo_Storage +{ + /** + * Handle for the current database connection. + * + * @var DB + */ + protected $_db; + + /** + * Handle for the current database connection, used for writing. + * + * Defaults to the same handle as $_db if a separate write database is not + * required. + * + * @var DB + */ + protected $_write_db; + + /** + * Boolean indicating whether or not we're connected to the SQL server. + * + * @var boolean + */ + protected $_connected = false; + + /** + * Constructor. + * + * @param array $params Additional parameters for the subclass. + */ + public function __construct($params = array()) + { + $this->_params = $params; + + Horde::assertDriverConfig($this->_params, 'storage', + array('phptype', 'charset')); + + if (!isset($this->_params['database'])) { + $this->_params['database'] = ''; + } + if (!isset($this->_params['username'])) { + $this->_params['username'] = ''; + } + if (!isset($this->_params['hostspec'])) { + $this->_params['hostspec'] = ''; + } + $this->_params['table_rules'] = 'ingo_rules'; + $this->_params['table_lists'] = 'ingo_lists'; + $this->_params['table_vacations'] = 'ingo_vacations'; + $this->_params['table_forwards'] = 'ingo_forwards'; + $this->_params['table_spam'] = 'ingo_spam'; + + /* Connect to the SQL server using the supplied parameters. */ + $this->_write_db = &DB::connect($this->_params, + array('persistent' => !empty($this->_params['persistent']), + 'ssl' => !empty($this->_params['ssl']))); + if (is_a($this->_write_db, 'PEAR_Error')) { + Horde::fatal($this->_write_db, __FILE__, __LINE__); + } + /* Set DB portability options. */ + switch ($this->_write_db->phptype) { + case 'mssql': + $this->_write_db->setOption('portability', DB_PORTABILITY_LOWERCASE | DB_PORTABILITY_ERRORS | DB_PORTABILITY_RTRIM); + break; + default: + $this->_write_db->setOption('portability', DB_PORTABILITY_LOWERCASE | DB_PORTABILITY_ERRORS); + } + + + /* Check if we need to set up the read DB connection seperately. */ + if (!empty($this->_params['splitread'])) { + $params = array_merge($this->_params, $this->_params['read']); + $this->_db = &DB::connect($params, + array('persistent' => !empty($params['persistent']), + 'ssl' => !empty($params['ssl']))); + if (is_a($this->_db, 'PEAR_Error')) { + Horde::fatal($this->_db, __FILE__, __LINE__); + } + + switch ($this->_db->phptype) { + case 'mssql': + $this->_db->setOption('portability', DB_PORTABILITY_LOWERCASE | DB_PORTABILITY_ERRORS | DB_PORTABILITY_RTRIM); + break; + default: + $this->_db->setOption('portability', DB_PORTABILITY_LOWERCASE | DB_PORTABILITY_ERRORS); + } + } else { + /* Default to the same DB handle for the writer too. */ + $this->_db =& $this->_write_db; + } + } + + /** + * Retrieves the specified data from the storage backend. + * + * @param integer $field The field name of the desired data. + * See lib/Storage.php for the available fields. + * @param boolean $readonly Whether to disable any write operations. + * + * @return Ingo_Storage_Rule|Ingo_Storage_Filters The specified data. + */ + protected function _retrieve($field, $readonly = false) + { + switch ($field) { + case self::ACTION_BLACKLIST: + case self::ACTION_WHITELIST: + if ($field == self::ACTION_BLACKLIST) { + $ob = new Ingo_Storage_Blacklist(); + $filters = &$this->retrieve(self::ACTION_FILTERS); + if (is_a($filters, 'PEAR_Error')) { + return $filters; + } + $rule = $filters->findRule($field); + if (isset($rule['action-value'])) { + $ob->setBlacklistFolder($rule['action-value']); + } + } else { + $ob = new Ingo_Storage_Whitelist(); + } + $query = sprintf('SELECT list_address FROM %s WHERE list_owner = ? AND list_blacklist = ?', + $this->_params['table_lists']); + $values = array(Ingo::getUser(), + (int)($field == self::ACTION_BLACKLIST)); + Horde::logMessage('Ingo_Storage_Sql::_retrieve(): ' . $query, + __FILE__, __LINE__, PEAR_LOG_DEBUG); + $addresses = $this->_db->getCol($query, 0, $values); + if (is_a($addresses, 'PEAR_Error')) { + Horde::logMessage($addresses, __FILE__, __LINE__, PEAR_LOG_ERR); + return $addresses; + } + if ($field == self::ACTION_BLACKLIST) { + $ob->setBlacklist($addresses, false); + } else { + $ob->setWhitelist($addresses, false); + } + break; + + case self::ACTION_FILTERS: + $ob = new Ingo_Storage_Filters_Sql($this->_db, $this->_write_db, $this->_params); + if (is_a($result = $ob->init($readonly), 'PEAR_Error')) { + return $result; + } + break; + + case self::ACTION_FORWARD: + $query = sprintf('SELECT * FROM %s WHERE forward_owner = ?', + $this->_params['table_forwards']); + Horde::logMessage('Ingo_Storage_Sql::_retrieve(): ' . $query, + __FILE__, __LINE__, PEAR_LOG_DEBUG); + $result = $this->_db->query($query, Ingo::getUser()); + $data = $result->fetchRow(DB_FETCHMODE_ASSOC); + + $ob = new Ingo_Storage_Forward(); + if ($data && !is_a($data, 'PEAR_Error')) { + $ob->setForwardAddresses(explode("\n", $data['forward_addresses']), false); + $ob->setForwardKeep((bool)$data['forward_keep']); + $ob->setSaved(true); + } elseif ($data = @unserialize($GLOBALS['prefs']->getDefault('vacation'))) { + $ob->setForwardAddresses($data['a'], false); + $ob->setForwardKeep($data['k']); + } + break; + + case self::ACTION_VACATION: + $query = sprintf('SELECT * FROM %s WHERE vacation_owner = ?', + $this->_params['table_vacations']); + Horde::logMessage('Ingo_Storage_Sql::_retrieve(): ' . $query, + __FILE__, __LINE__, PEAR_LOG_DEBUG); + $result = $this->_db->query($query, Ingo::getUser()); + $data = $result->fetchRow(DB_FETCHMODE_ASSOC); + + $ob = new Ingo_Storage_Vacation(); + if ($data && !is_a($data, 'PEAR_Error')) { + $ob->setVacationAddresses(explode("\n", $data['vacation_addresses']), false); + $ob->setVacationDays((int)$data['vacation_days']); + $ob->setVacationStart((int)$data['vacation_start']); + $ob->setVacationEnd((int)$data['vacation_end']); + $ob->setVacationExcludes(explode("\n", $data['vacation_excludes']), false); + $ob->setVacationIgnorelist((bool)$data['vacation_ignorelists']); + $ob->setVacationReason(Horde_String::convertCharset($data['vacation_reason'], $this->_params['charset'])); + $ob->setVacationSubject(Horde_String::convertCharset($data['vacation_subject'], $this->_params['charset'])); + $ob->setSaved(true); + } elseif ($data = @unserialize($GLOBALS['prefs']->getDefault('vacation'))) { + $ob->setVacationAddresses($data['addresses'], false); + $ob->setVacationDays($data['days']); + $ob->setVacationExcludes($data['excludes'], false); + $ob->setVacationIgnorelist($data['ignorelist']); + $ob->setVacationReason($data['reason']); + $ob->setVacationSubject($data['subject']); + if (isset($data['start'])) { + $ob->setVacationStart($data['start']); + } + if (isset($data['end'])) { + $ob->setVacationEnd($data['end']); + } + } + break; + + case self::ACTION_SPAM: + $query = sprintf('SELECT * FROM %s WHERE spam_owner = ?', + $this->_params['table_spam']); + Horde::logMessage('Ingo_Storage_Sql::_retrieve(): ' . $query, + __FILE__, __LINE__, PEAR_LOG_DEBUG); + $result = $this->_db->query($query, Ingo::getUser()); + $data = $result->fetchRow(DB_FETCHMODE_ASSOC); + + $ob = new Ingo_Storage_Spam(); + if ($data && !is_a($data, 'PEAR_Error')) { + $ob->setSpamFolder($data['spam_folder']); + $ob->setSpamLevel((int)$data['spam_level']); + $ob->setSaved(true); + } elseif ($data = @unserialize($GLOBALS['prefs']->getDefault('spam'))) { + $ob->setSpamFolder($data['folder']); + $ob->setSpamLevel($data['level']); + } + break; + + default: + $ob = false; + } + + return $ob; + } + + /** + * Stores the specified data in the storage backend. + * + * @access private + * + * @param Ingo_Storage_Rule|Ingo_Storage_Filters $ob The object to store. + * + * @return boolean True on success. + */ + protected function _store(&$ob) + { + switch ($ob->obType()) { + case self::ACTION_BLACKLIST: + case self::ACTION_WHITELIST: + $is_blacklist = (int)($ob->obType() == self::ACTION_BLACKLIST); + if ($is_blacklist) { + $filters = &$this->retrieve(self::ACTION_FILTERS); + if (is_a($filters, 'PEAR_Error')) { + return $filters; + } + $id = $filters->findRuleId(self::ACTION_BLACKLIST); + if ($id !== null) { + $rule = $filters->getRule($id); + if (!isset($rule['action-value']) || + $rule['action-value'] != $ob->getBlacklistFolder()) { + $rule['action-value'] = $ob->getBlacklistFolder(); + $filters->updateRule($rule, $id); + } + } + } + $query = sprintf('DELETE FROM %s WHERE list_owner = ? AND list_blacklist = ?', + $this->_params['table_lists']); + $values = array(Ingo::getUser(), $is_blacklist); + Horde::logMessage('Ingo_Storage_Sql::_store(): ' . $query, + __FILE__, __LINE__, PEAR_LOG_DEBUG); + $result = $this->_write_db->query($query, $values); + if (is_a($result, 'PEAR_Error')) { + Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR); + return $result; + } + $query = sprintf('INSERT INTO %s (list_owner, list_blacklist, list_address) VALUES (?, ?, ?)', + $this->_params['table_lists']); + Horde::logMessage('Ingo_Storage_Sql::_store(): ' . $query, + __FILE__, __LINE__, PEAR_LOG_DEBUG); + $addresses = $is_blacklist ? $ob->getBlacklist() : $ob->getWhitelist(); + foreach ($addresses as $address) { + $result = $this->_write_db->query($query, + array(Ingo::getUser(), + $is_blacklist, + $address)); + if (is_a($result, 'PEAR_Error')) { + Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR); + return $result; + } + } + $ob->setSaved(true); + $ret = true; + break; + + case self::ACTION_FILTERS: + $ret = true; + break; + + case self::ACTION_FORWARD: + if ($ob->isSaved()) { + $query = 'UPDATE %s SET forward_addresses = ?, forward_keep = ? WHERE forward_owner = ?'; + } else { + $query = 'INSERT INTO %s (forward_addresses, forward_keep, forward_owner) VALUES (?, ?, ?)'; + } + $query = sprintf($query, $this->_params['table_forwards']); + $values = array( + implode("\n", $ob->getForwardAddresses()), + (int)(bool)$ob->getForwardKeep(), + Ingo::getUser()); + Horde::logMessage('Ingo_Storage_Sql::_store(): ' . $query, + __FILE__, __LINE__, PEAR_LOG_DEBUG); + $ret = $this->_write_db->query($query, $values); + if (!is_a($ret, 'PEAR_Error')) { + $ob->setSaved(true); + } + break; + + case self::ACTION_VACATION: + if ($ob->isSaved()) { + $query = 'UPDATE %s SET vacation_addresses = ?, vacation_subject = ?, vacation_reason = ?, vacation_days = ?, vacation_start = ?, vacation_end = ?, vacation_excludes = ?, vacation_ignorelists = ? WHERE vacation_owner = ?'; + } else { + $query = 'INSERT INTO %s (vacation_addresses, vacation_subject, vacation_reason, vacation_days, vacation_start, vacation_end, vacation_excludes, vacation_ignorelists, vacation_owner) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)'; + } + $query = sprintf($query, $this->_params['table_vacations']); + $values = array( + implode("\n", $ob->getVacationAddresses()), + Horde_String::convertCharset($ob->getVacationSubject(), + Horde_Nls::getCharset(), + $this->_params['charset']), + Horde_String::convertCharset($ob->getVacationReason(), + Horde_Nls::getCharset(), + $this->_params['charset']), + (int)$ob->getVacationDays(), + (int)$ob->getVacationStart(), + (int)$ob->getVacationEnd(), + implode("\n", $ob->getVacationExcludes()), + (int)(bool)$ob->getVacationIgnorelist(), + Ingo::getUser()); + Horde::logMessage('Ingo_Storage_Sql::_store(): ' . $query, + __FILE__, __LINE__, PEAR_LOG_DEBUG); + $ret = $this->_write_db->query($query, $values); + if (!is_a($ret, 'PEAR_Error')) { + $ob->setSaved(true); + } + break; + + case self::ACTION_SPAM: + if ($ob->isSaved()) { + $query = 'UPDATE %s SET spam_level = ?, spam_folder = ? WHERE spam_owner = ?'; + } else { + $query = 'INSERT INTO %s (spam_level, spam_folder, spam_owner) VALUES (?, ?, ?)'; + } + $query = sprintf($query, $this->_params['table_spam']); + $values = array( + (int)$ob->getSpamLevel(), + $ob->getSpamFolder(), + Ingo::getUser()); + Horde::logMessage('Ingo_Storage_Sql::_store(): ' . $query, + __FILE__, __LINE__, PEAR_LOG_DEBUG); + $ret = $this->_write_db->query($query, $values); + if (!is_a($ret, 'PEAR_Error')) { + $ob->setSaved(true); + } + break; + + default: + $ret = false; + break; + } + + if (is_a($ret, 'PEAR_Error')) { + Horde::logMessage($ret, __FILE__, __LINE__); + } + + return $ret; + } + +} diff --git a/ingo/lib/Storage/Vacation.php b/ingo/lib/Storage/Vacation.php new file mode 100644 index 000000000..1b22b39cf --- /dev/null +++ b/ingo/lib/Storage/Vacation.php @@ -0,0 +1,142 @@ + + * @package Ingo + */ +class Ingo_Storage_Vacation extends Ingo_Storage_Rule +{ + protected $_addr = array(); + protected $_days = 7; + protected $_excludes = array(); + protected $_ignorelist = true; + protected $_reason = ''; + protected $_subject = ''; + protected $_start; + protected $_end; + protected $_obtype = Ingo_Storage::ACTION_VACATION; + + public function setVacationAddresses($data, $sort = true) + { + $this->_addr = &$this->_addressList($data, $sort); + } + + public function setVacationDays($data) + { + $this->_days = $data; + } + + public function setVacationExcludes($data, $sort = true) + { + $this->_excludes = &$this->_addressList($data, $sort); + } + + public function setVacationIgnorelist($data) + { + $this->_ignorelist = $data; + } + + public function setVacationReason($data) + { + $this->_reason = $data; + } + + public function setVacationSubject($data) + { + $this->_subject = $data; + } + + public function setVacationStart($data) + { + $this->_start = $data; + } + + public function setVacationEnd($data) + { + $this->_end = $data; + } + + public function getVacationAddresses() + { + if (empty($GLOBALS['conf']['hooks']['vacation_addresses'])) { + return $this->_addr; + } + + try { + return Horde::callHook('_ingo_hook_vacation_addresses', array(Ingo::getUser()), 'ingo'); + } catch (Horde_Exception $e) { + return array(); + } + } + + public function getVacationDays() + { + return $this->_days; + } + + public function getVacationExcludes() + { + return $this->_excludes; + } + + public function getVacationIgnorelist() + { + return $this->_ignorelist; + } + + public function getVacationReason() + { + return $this->_reason; + } + + public function getVacationSubject() + { + return $this->_subject; + } + + public function getVacationStart() + { + return $this->_start; + } + + public function getVacationStartYear() + { + return date('Y', $this->_start); + } + + public function getVacationStartMonth() + { + return date('n', $this->_start); + } + + public function getVacationStartDay() + { + return date('j', $this->_start); + } + + public function getVacationEnd() + { + return $this->_end; + } + + public function getVacationEndYear() + { + return date('Y', $this->_end); + } + + public function getVacationEndMonth() + { + return date('n', $this->_end); + } + + public function getVacationEndDay() + { + return date('j', $this->_end); + } + +} diff --git a/ingo/lib/Storage/Whitelist.php b/ingo/lib/Storage/Whitelist.php new file mode 100644 index 000000000..19f5295a6 --- /dev/null +++ b/ingo/lib/Storage/Whitelist.php @@ -0,0 +1,47 @@ + + * @package Ingo + */ +class Ingo_Storage_Whitelist extends Ingo_Storage_Rule +{ + protected $_addr = array(); + protected $_obtype = Ingo_Storage::ACTION_WHITELIST; + + /** + * Sets the list of whitelisted addresses. + * + * @param mixed $data The list of addresses (array or string). + * @param boolean $sort Sort the list? + * + * @return mixed PEAR_Error on error, true on success. + */ + public function setWhitelist($data, $sort = true) + { + $addr = &$this->_addressList($data, $sort); + $addr = array_filter($addr, array('Ingo', 'filterEmptyAddress')); + if (!empty($GLOBALS['conf']['storage']['maxwhitelist'])) { + $addr_count = count($addr); + if ($addr_count > $GLOBALS['conf']['storage']['maxwhitelist']) { + return PEAR::raiseError(sprintf(_("Maximum number of whitelisted addresses exceeded (Total addresses: %s, Maximum addresses: %s). Could not add new addresses to whitelist."), $addr_count, $GLOBALS['conf']['storage']['maxwhitelist']), 'horde.error'); + } + } + + $this->_addr = $addr; + return true; + } + + public function getWhitelist() + { + return empty($this->_addr) + ? array() + : array_filter($this->_addr, array('Ingo', 'filterEmptyAddress')); + } + +} diff --git a/ingo/lib/Storage/mock.php b/ingo/lib/Storage/mock.php deleted file mode 100644 index aeafa9986..000000000 --- a/ingo/lib/Storage/mock.php +++ /dev/null @@ -1,55 +0,0 @@ - - * @package Ingo - */ - -class Ingo_Storage_mock extends Ingo_Storage -{ - protected $_data = array(); - - protected function &_retrieve($field) - { - if (empty($this->_data[$field])) { - switch ($field) { - case self::ACTION_BLACKLIST: - return new Ingo_Storage_blacklist(); - - case self::ACTION_FILTERS: - $ob = new Ingo_Storage_filters(); - include INGO_BASE . '/config/prefs.php.dist'; - $ob->setFilterList(unserialize($_prefs['rules']['value'])); - return $ob; - - case self::ACTION_FORWARD: - return new Ingo_Storage_forward(); - - case self::ACTION_VACATION: - return new Ingo_Storage_vacation(); - - case self::ACTION_WHITELIST: - return new Ingo_Storage_whitelist(); - - case self::ACTION_SPAM: - return new Ingo_Storage_spam(); - - default: - return false; - } - } - - return $this->_data[$field]; - } - - protected function _store(&$ob) - { - $this->_data[$ob->obType()] = $ob; - } - -} diff --git a/ingo/lib/Storage/prefs.php b/ingo/lib/Storage/prefs.php deleted file mode 100644 index 3c61bce8b..000000000 --- a/ingo/lib/Storage/prefs.php +++ /dev/null @@ -1,183 +0,0 @@ - - * @author Jan Schneider - * @package Ingo - */ -class Ingo_Storage_prefs extends Ingo_Storage -{ - /** - * Constructor. - * - * @param array $params Additional parameters for the subclass. - */ - public function __construct($params = array()) - { - $this->_params = $params; - } - - /** - * Retrieves the specified data from the storage backend. - * - * @param integer $field The field name of the desired data. - * See lib/Storage.php for the available fields. - * @param boolean $readonly Whether to disable any write operations. - * - * @return Ingo_Storage_rule|Ingo_Storage_filters The specified data. - */ - protected function _retrieve($field, $readonly = false) - { - $prefs = &Prefs::singleton($GLOBALS['conf']['prefs']['driver'], - $GLOBALS['registry']->getApp(), - Ingo::getUser(), '', null, false); - $prefs->retrieve(); - - switch ($field) { - case self::ACTION_BLACKLIST: - $ob = new Ingo_Storage_blacklist(); - $data = @unserialize($prefs->getValue('blacklist')); - if ($data) { - $ob->setBlacklist($data['a'], false); - $ob->setBlacklistFolder($data['f']); - } - break; - - case self::ACTION_WHITELIST: - $ob = new Ingo_Storage_whitelist(); - $data = @unserialize($prefs->getValue('whitelist')); - if ($data) { - $ob->setWhitelist($data, false); - } - break; - - case self::ACTION_FILTERS: - $ob = new Ingo_Storage_filters(); - $data = @unserialize($prefs->getValue('rules', false)); - if ($data === false) { - /* Convert rules from the old format. */ - $data = @unserialize($prefs->getValue('rules')); - } else { - $data = Horde_String::convertCharset($data, $prefs->getCharset(), Horde_Nls::getCharset()); - } - if ($data) { - $ob->setFilterlist($data); - } - break; - - case self::ACTION_FORWARD: - $ob = new Ingo_Storage_forward(); - $data = @unserialize($prefs->getValue('forward')); - if ($data) { - $ob->setForwardAddresses($data['a'], false); - $ob->setForwardKeep($data['k']); - } - break; - - case self::ACTION_VACATION: - $ob = new Ingo_Storage_vacation(); - $data = @unserialize($prefs->getValue('vacation', false)); - if ($data === false) { - /* Convert vacation from the old format. */ - $data = unserialize($prefs->getValue('vacation')); - } elseif (is_array($data)) { - $data = $prefs->convertFromDriver($data, Horde_Nls::getCharset()); - } - if ($data) { - $ob->setVacationAddresses($data['addresses'], false); - $ob->setVacationDays($data['days']); - $ob->setVacationExcludes($data['excludes'], false); - $ob->setVacationIgnorelist($data['ignorelist']); - $ob->setVacationReason($data['reason']); - $ob->setVacationSubject($data['subject']); - if (isset($data['start'])) { - $ob->setVacationStart($data['start']); - } - if (isset($data['end'])) { - $ob->setVacationEnd($data['end']); - } - } - break; - - case self::ACTION_SPAM: - $ob = new Ingo_Storage_spam(); - $data = @unserialize($prefs->getValue('spam')); - if ($data) { - $ob->setSpamFolder($data['folder']); - $ob->setSpamLevel($data['level']); - } - break; - - default: - $ob = false; - break; - } - - return $ob; - } - - /** - * Stores the specified data in the storage backend. - * - * @param Ingo_Storage_rule|Ingo_Storage_filters $ob The object to store. - * - * @return boolean True on success. - */ - protected function _store($ob) - { - $prefs = &Prefs::singleton($GLOBALS['conf']['prefs']['driver'], - $GLOBALS['registry']->getApp(), - Ingo::getUser(), '', null, false); - $prefs->retrieve(); - - switch ($ob->obType()) { - case self::ACTION_BLACKLIST: - $data = array( - 'a' => $ob->getBlacklist(), - 'f' => $ob->getBlacklistFolder(), - ); - return $prefs->setValue('blacklist', serialize($data)); - - case self::ACTION_FILTERS: - return $prefs->setValue('rules', serialize(Horde_String::convertCharset($ob->getFilterList(), Horde_Nls::getCharset(), $prefs->getCharset())), false); - - case self::ACTION_FORWARD: - $data = array( - 'a' => $ob->getForwardAddresses(), - 'k' => $ob->getForwardKeep(), - ); - return $prefs->setValue('forward', serialize($data)); - - case self::ACTION_VACATION: - $data = array( - 'addresses' => $ob->getVacationAddresses(), - 'days' => $ob->getVacationDays(), - 'excludes' => $ob->getVacationExcludes(), - 'ignorelist' => $ob->getVacationIgnorelist(), - 'reason' => $ob->getVacationReason(), - 'subject' => $ob->getVacationSubject(), - 'start' => $ob->getVacationStart(), - 'end' => $ob->getVacationEnd(), - ); - return $prefs->setValue('vacation', serialize($prefs->convertToDriver($data, Horde_Nls::getCharset())), false); - - case self::ACTION_WHITELIST: - return $prefs->setValue('whitelist', serialize($ob->getWhitelist())); - - case self::ACTION_SPAM: - $data = array( - 'folder' => $ob->getSpamFolder(), - 'level' => $ob->getSpamLevel(), - ); - return $prefs->setValue('spam', serialize($data)); - } - - return false; - } - -} diff --git a/ingo/lib/Storage/sql.php b/ingo/lib/Storage/sql.php deleted file mode 100644 index ceb515713..000000000 --- a/ingo/lib/Storage/sql.php +++ /dev/null @@ -1,737 +0,0 @@ - - * 'phptype' - The database type (e.g. 'pgsql', 'mysql', etc.). - * 'charset' - The database's internal charset. - * - * Required by some database implementations:
- *   'database' - The name of the database.
- *   'hostspec' - The hostname of the database server.
- *   'protocol' - The communication protocol ('tcp', 'unix', etc.).
- *   'username' - The username with which to connect to the database.
- *   'password' - The password associated with 'username'.
- *   'options'  - Additional options to pass to the database.
- *   'tty'      - The TTY on which to connect to the database.
- *   'port'     - The port on which to connect to the database.
- * - * The table structure can be created by the scripts/drivers/sql/ingo.sql - * script. - * - * See the enclosed file LICENSE for license information (ASL). If you - * did not receive this file, see http://www.horde.org/licenses/asl.php. - * - * @author Jan Schneider - * @package Ingo - */ -class Ingo_Storage_sql extends Ingo_Storage -{ - /** - * Handle for the current database connection. - * - * @var DB - */ - protected $_db; - - /** - * Handle for the current database connection, used for writing. - * - * Defaults to the same handle as $_db if a separate write database is not - * required. - * - * @var DB - */ - protected $_write_db; - - /** - * Boolean indicating whether or not we're connected to the SQL server. - * - * @var boolean - */ - protected $_connected = false; - - /** - * Constructor. - * - * @param array $params Additional parameters for the subclass. - */ - public function __construct($params = array()) - { - $this->_params = $params; - - Horde::assertDriverConfig($this->_params, 'storage', - array('phptype', 'charset')); - - if (!isset($this->_params['database'])) { - $this->_params['database'] = ''; - } - if (!isset($this->_params['username'])) { - $this->_params['username'] = ''; - } - if (!isset($this->_params['hostspec'])) { - $this->_params['hostspec'] = ''; - } - $this->_params['table_rules'] = 'ingo_rules'; - $this->_params['table_lists'] = 'ingo_lists'; - $this->_params['table_vacations'] = 'ingo_vacations'; - $this->_params['table_forwards'] = 'ingo_forwards'; - $this->_params['table_spam'] = 'ingo_spam'; - - /* Connect to the SQL server using the supplied parameters. */ - $this->_write_db = &DB::connect($this->_params, - array('persistent' => !empty($this->_params['persistent']), - 'ssl' => !empty($this->_params['ssl']))); - if (is_a($this->_write_db, 'PEAR_Error')) { - Horde::fatal($this->_write_db, __FILE__, __LINE__); - } - /* Set DB portability options. */ - switch ($this->_write_db->phptype) { - case 'mssql': - $this->_write_db->setOption('portability', DB_PORTABILITY_LOWERCASE | DB_PORTABILITY_ERRORS | DB_PORTABILITY_RTRIM); - break; - default: - $this->_write_db->setOption('portability', DB_PORTABILITY_LOWERCASE | DB_PORTABILITY_ERRORS); - } - - - /* Check if we need to set up the read DB connection seperately. */ - if (!empty($this->_params['splitread'])) { - $params = array_merge($this->_params, $this->_params['read']); - $this->_db = &DB::connect($params, - array('persistent' => !empty($params['persistent']), - 'ssl' => !empty($params['ssl']))); - if (is_a($this->_db, 'PEAR_Error')) { - Horde::fatal($this->_db, __FILE__, __LINE__); - } - - switch ($this->_db->phptype) { - case 'mssql': - $this->_db->setOption('portability', DB_PORTABILITY_LOWERCASE | DB_PORTABILITY_ERRORS | DB_PORTABILITY_RTRIM); - break; - default: - $this->_db->setOption('portability', DB_PORTABILITY_LOWERCASE | DB_PORTABILITY_ERRORS); - } - } else { - /* Default to the same DB handle for the writer too. */ - $this->_db =& $this->_write_db; - } - } - - /** - * Retrieves the specified data from the storage backend. - * - * @param integer $field The field name of the desired data. - * See lib/Storage.php for the available fields. - * @param boolean $readonly Whether to disable any write operations. - * - * @return Ingo_Storage_rule|Ingo_Storage_filters The specified data. - */ - protected function _retrieve($field, $readonly = false) - { - switch ($field) { - case self::ACTION_BLACKLIST: - case self::ACTION_WHITELIST: - if ($field == self::ACTION_BLACKLIST) { - $ob = new Ingo_Storage_blacklist(); - $filters = &$this->retrieve(self::ACTION_FILTERS); - if (is_a($filters, 'PEAR_Error')) { - return $filters; - } - $rule = $filters->findRule($field); - if (isset($rule['action-value'])) { - $ob->setBlacklistFolder($rule['action-value']); - } - } else { - $ob = new Ingo_Storage_whitelist(); - } - $query = sprintf('SELECT list_address FROM %s WHERE list_owner = ? AND list_blacklist = ?', - $this->_params['table_lists']); - $values = array(Ingo::getUser(), - (int)($field == self::ACTION_BLACKLIST)); - Horde::logMessage('Ingo_Storage_sql::_retrieve(): ' . $query, - __FILE__, __LINE__, PEAR_LOG_DEBUG); - $addresses = $this->_db->getCol($query, 0, $values); - if (is_a($addresses, 'PEAR_Error')) { - Horde::logMessage($addresses, __FILE__, __LINE__, PEAR_LOG_ERR); - return $addresses; - } - if ($field == self::ACTION_BLACKLIST) { - $ob->setBlacklist($addresses, false); - } else { - $ob->setWhitelist($addresses, false); - } - break; - - case self::ACTION_FILTERS: - $ob = new Ingo_Storage_filters_sql($this->_db, $this->_write_db, $this->_params); - if (is_a($result = $ob->init($readonly), 'PEAR_Error')) { - return $result; - } - break; - - case self::ACTION_FORWARD: - $query = sprintf('SELECT * FROM %s WHERE forward_owner = ?', - $this->_params['table_forwards']); - Horde::logMessage('Ingo_Storage_sql::_retrieve(): ' . $query, - __FILE__, __LINE__, PEAR_LOG_DEBUG); - $result = $this->_db->query($query, Ingo::getUser()); - $data = $result->fetchRow(DB_FETCHMODE_ASSOC); - - $ob = new Ingo_Storage_forward(); - if ($data && !is_a($data, 'PEAR_Error')) { - $ob->setForwardAddresses(explode("\n", $data['forward_addresses']), false); - $ob->setForwardKeep((bool)$data['forward_keep']); - $ob->setSaved(true); - } elseif ($data = @unserialize($GLOBALS['prefs']->getDefault('vacation'))) { - $ob->setForwardAddresses($data['a'], false); - $ob->setForwardKeep($data['k']); - } - break; - - case self::ACTION_VACATION: - $query = sprintf('SELECT * FROM %s WHERE vacation_owner = ?', - $this->_params['table_vacations']); - Horde::logMessage('Ingo_Storage_sql::_retrieve(): ' . $query, - __FILE__, __LINE__, PEAR_LOG_DEBUG); - $result = $this->_db->query($query, Ingo::getUser()); - $data = $result->fetchRow(DB_FETCHMODE_ASSOC); - - $ob = new Ingo_Storage_vacation(); - if ($data && !is_a($data, 'PEAR_Error')) { - $ob->setVacationAddresses(explode("\n", $data['vacation_addresses']), false); - $ob->setVacationDays((int)$data['vacation_days']); - $ob->setVacationStart((int)$data['vacation_start']); - $ob->setVacationEnd((int)$data['vacation_end']); - $ob->setVacationExcludes(explode("\n", $data['vacation_excludes']), false); - $ob->setVacationIgnorelist((bool)$data['vacation_ignorelists']); - $ob->setVacationReason(Horde_String::convertCharset($data['vacation_reason'], $this->_params['charset'])); - $ob->setVacationSubject(Horde_String::convertCharset($data['vacation_subject'], $this->_params['charset'])); - $ob->setSaved(true); - } elseif ($data = @unserialize($GLOBALS['prefs']->getDefault('vacation'))) { - $ob->setVacationAddresses($data['addresses'], false); - $ob->setVacationDays($data['days']); - $ob->setVacationExcludes($data['excludes'], false); - $ob->setVacationIgnorelist($data['ignorelist']); - $ob->setVacationReason($data['reason']); - $ob->setVacationSubject($data['subject']); - if (isset($data['start'])) { - $ob->setVacationStart($data['start']); - } - if (isset($data['end'])) { - $ob->setVacationEnd($data['end']); - } - } - break; - - case self::ACTION_SPAM: - $query = sprintf('SELECT * FROM %s WHERE spam_owner = ?', - $this->_params['table_spam']); - Horde::logMessage('Ingo_Storage_sql::_retrieve(): ' . $query, - __FILE__, __LINE__, PEAR_LOG_DEBUG); - $result = $this->_db->query($query, Ingo::getUser()); - $data = $result->fetchRow(DB_FETCHMODE_ASSOC); - - $ob = new Ingo_Storage_spam(); - if ($data && !is_a($data, 'PEAR_Error')) { - $ob->setSpamFolder($data['spam_folder']); - $ob->setSpamLevel((int)$data['spam_level']); - $ob->setSaved(true); - } elseif ($data = @unserialize($GLOBALS['prefs']->getDefault('spam'))) { - $ob->setSpamFolder($data['folder']); - $ob->setSpamLevel($data['level']); - } - break; - - default: - $ob = false; - } - - return $ob; - } - - /** - * Stores the specified data in the storage backend. - * - * @access private - * - * @param Ingo_Storage_rule|Ingo_Storage_filters $ob The object to store. - * - * @return boolean True on success. - */ - protected function _store(&$ob) - { - switch ($ob->obType()) { - case self::ACTION_BLACKLIST: - case self::ACTION_WHITELIST: - $is_blacklist = (int)($ob->obType() == self::ACTION_BLACKLIST); - if ($is_blacklist) { - $filters = &$this->retrieve(self::ACTION_FILTERS); - if (is_a($filters, 'PEAR_Error')) { - return $filters; - } - $id = $filters->findRuleId(self::ACTION_BLACKLIST); - if ($id !== null) { - $rule = $filters->getRule($id); - if (!isset($rule['action-value']) || - $rule['action-value'] != $ob->getBlacklistFolder()) { - $rule['action-value'] = $ob->getBlacklistFolder(); - $filters->updateRule($rule, $id); - } - } - } - $query = sprintf('DELETE FROM %s WHERE list_owner = ? AND list_blacklist = ?', - $this->_params['table_lists']); - $values = array(Ingo::getUser(), $is_blacklist); - Horde::logMessage('Ingo_Storage_sql::_store(): ' . $query, - __FILE__, __LINE__, PEAR_LOG_DEBUG); - $result = $this->_write_db->query($query, $values); - if (is_a($result, 'PEAR_Error')) { - Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR); - return $result; - } - $query = sprintf('INSERT INTO %s (list_owner, list_blacklist, list_address) VALUES (?, ?, ?)', - $this->_params['table_lists']); - Horde::logMessage('Ingo_Storage_sql::_store(): ' . $query, - __FILE__, __LINE__, PEAR_LOG_DEBUG); - $addresses = $is_blacklist ? $ob->getBlacklist() : $ob->getWhitelist(); - foreach ($addresses as $address) { - $result = $this->_write_db->query($query, - array(Ingo::getUser(), - $is_blacklist, - $address)); - if (is_a($result, 'PEAR_Error')) { - Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR); - return $result; - } - } - $ob->setSaved(true); - $ret = true; - break; - - case self::ACTION_FILTERS: - $ret = true; - break; - - case self::ACTION_FORWARD: - if ($ob->isSaved()) { - $query = 'UPDATE %s SET forward_addresses = ?, forward_keep = ? WHERE forward_owner = ?'; - } else { - $query = 'INSERT INTO %s (forward_addresses, forward_keep, forward_owner) VALUES (?, ?, ?)'; - } - $query = sprintf($query, $this->_params['table_forwards']); - $values = array( - implode("\n", $ob->getForwardAddresses()), - (int)(bool)$ob->getForwardKeep(), - Ingo::getUser()); - Horde::logMessage('Ingo_Storage_sql::_store(): ' . $query, - __FILE__, __LINE__, PEAR_LOG_DEBUG); - $ret = $this->_write_db->query($query, $values); - if (!is_a($ret, 'PEAR_Error')) { - $ob->setSaved(true); - } - break; - - case self::ACTION_VACATION: - if ($ob->isSaved()) { - $query = 'UPDATE %s SET vacation_addresses = ?, vacation_subject = ?, vacation_reason = ?, vacation_days = ?, vacation_start = ?, vacation_end = ?, vacation_excludes = ?, vacation_ignorelists = ? WHERE vacation_owner = ?'; - } else { - $query = 'INSERT INTO %s (vacation_addresses, vacation_subject, vacation_reason, vacation_days, vacation_start, vacation_end, vacation_excludes, vacation_ignorelists, vacation_owner) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)'; - } - $query = sprintf($query, $this->_params['table_vacations']); - $values = array( - implode("\n", $ob->getVacationAddresses()), - Horde_String::convertCharset($ob->getVacationSubject(), - Horde_Nls::getCharset(), - $this->_params['charset']), - Horde_String::convertCharset($ob->getVacationReason(), - Horde_Nls::getCharset(), - $this->_params['charset']), - (int)$ob->getVacationDays(), - (int)$ob->getVacationStart(), - (int)$ob->getVacationEnd(), - implode("\n", $ob->getVacationExcludes()), - (int)(bool)$ob->getVacationIgnorelist(), - Ingo::getUser()); - Horde::logMessage('Ingo_Storage_sql::_store(): ' . $query, - __FILE__, __LINE__, PEAR_LOG_DEBUG); - $ret = $this->_write_db->query($query, $values); - if (!is_a($ret, 'PEAR_Error')) { - $ob->setSaved(true); - } - break; - - case self::ACTION_SPAM: - if ($ob->isSaved()) { - $query = 'UPDATE %s SET spam_level = ?, spam_folder = ? WHERE spam_owner = ?'; - } else { - $query = 'INSERT INTO %s (spam_level, spam_folder, spam_owner) VALUES (?, ?, ?)'; - } - $query = sprintf($query, $this->_params['table_spam']); - $values = array( - (int)$ob->getSpamLevel(), - $ob->getSpamFolder(), - Ingo::getUser()); - Horde::logMessage('Ingo_Storage_sql::_store(): ' . $query, - __FILE__, __LINE__, PEAR_LOG_DEBUG); - $ret = $this->_write_db->query($query, $values); - if (!is_a($ret, 'PEAR_Error')) { - $ob->setSaved(true); - } - break; - - default: - $ret = false; - break; - } - - if (is_a($ret, 'PEAR_Error')) { - Horde::logMessage($ret, __FILE__, __LINE__); - } - - return $ret; - } - -} - -/** - * Ingo_Storage_filters_sql is the object used to hold user-defined filtering - * rule information. - * - * @author Jan Schneider - * @package Ingo - */ -class Ingo_Storage_filters_sql extends Ingo_Storage_filters { - - /** - * Handle for the current database connection. - * - * @var DB - */ - protected $_db; - - /** - * Handle for the current database connection, used for writing. - * - * Defaults to the same handle as $_db if a separate write database is not - * required. - * - * @var DB - */ - protected $_write_db; - - /** - * Driver specific parameters. - * - * @var array - */ - protected $_params; - - /** - * Constructor. - * - * @param DB $db Handle for the database connection. - * @param DB $write_db Handle for the database connection, used for - * writing. - * @param array $params Driver specific parameters. - */ - public function __construct($db, $write_db, $params) - { - $this->_db = $db; - $this->_write_db = $write_db; - $this->_params = $params; - } - - /** - * Loads all rules from the DB backend. - * - * @param boolean $readonly Whether to disable any write operations. - */ - public function init($readonly = false) - { - $query = sprintf('SELECT * FROM %s WHERE rule_owner = ? ORDER BY rule_order', - $this->_params['table_rules']); - $values = array(Ingo::getUser()); - Horde::logMessage('Ingo_Storage_filters_sql(): ' . $query, __FILE__, __LINE__, PEAR_LOG_DEBUG); - $result = $this->_db->query($query, $values); - if (is_a($result, 'PEAR_Error')) { - return $result; - } - $data = array(); - while ($row = $result->fetchRow(DB_FETCHMODE_ASSOC)) { - $data[$row['rule_order']] = array( - 'id' => (int)$row['rule_id'], - 'name' => Horde_String::convertCharset($row['rule_name'], $this->_params['charset']), - 'action' => (int)$row['rule_action'], - 'action-value' => Horde_String::convertCharset($row['rule_value'], $this->_params['charset']), - 'flags' => (int)$row['rule_flags'], - 'conditions' => empty($row['rule_conditions']) ? null : Horde_String::convertCharset(unserialize($row['rule_conditions']), $this->_params['charset']), - 'combine' => (int)$row['rule_combine'], - 'stop' => (bool)$row['rule_stop'], - 'disable' => !(bool)$row['rule_active']); - } - $this->setFilterlist($data); - - if (empty($data) && !$readonly) { - $data = @unserialize($GLOBALS['prefs']->getDefault('rules')); - if ($data) { - foreach ($data as $val) { - $this->addRule($val, false); - } - } else { - $this->addRule( - array('name' => 'Whitelist', - 'action' => Ingo_Storage::ACTION_WHITELIST), - false); - $this->addRule( - array('name' => 'Vacation', - 'action' => Ingo_Storage::ACTION_VACATION, - 'disable' => true), - false); - $this->addRule( - array('name' => 'Blacklist', - 'action' => Ingo_Storage::ACTION_BLACKLIST), - false); - $this->addRule( - array('name' => 'Spam Filter', - 'action' => Ingo_Storage::ACTION_SPAM, - 'disable' => true), - false); - $this->addRule( - array('name' => 'Forward', - 'action' => Ingo_Storage::ACTION_FORWARD), - false); - } - } - } - - /** - * Converts a rule hash from Ingo's internal format to the database - * format. - * - * @param array $rule Rule hash in Ingo's format. - * - * @return array Rule hash in DB's format. - */ - protected function _ruleToBackend($rule) - { - return array(Horde_String::convertCharset($rule['name'], Horde_Nls::getCharset(), $this->_params['charset']), - (int)$rule['action'], - isset($rule['action-value']) ? Horde_String::convertCharset($rule['action-value'], Horde_Nls::getCharset(), $this->_params['charset']) : null, - isset($rule['flags']) ? (int)$rule['flags'] : null, - isset($rule['conditions']) ? serialize(Horde_String::convertCharset($rule['conditions'], Horde_Nls::getCharset(), $this->_params['charset'])) : null, - isset($rule['combine']) ? (int)$rule['combine'] : null, - isset($rule['stop']) ? (int)$rule['stop'] : null, - isset($rule['disable']) ? (int)(!$rule['disable']) : 1); - } - - /** - * Adds a rule hash to the filters list. - * - * @param array $rule A rule hash. - * @param boolean $default If true merge the rule hash with default rule - * values. - */ - public function addRule($rule, $default = true) - { - if ($default) { - $rule = array_merge($this->getDefaultRule(), $rule); - } - - $query = sprintf('INSERT INTO %s (rule_id, rule_owner, rule_name, rule_action, rule_value, rule_flags, rule_conditions, rule_combine, rule_stop, rule_active, rule_order) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', - $this->_params['table_rules']); - $id = $this->_write_db->nextId($this->_params['table_rules']); - if (is_a($id, 'PEAR_Error')) { - return $id; - } - $order = key(array_reverse($this->_filters, true)) + 1; - $values = array_merge(array($id, Ingo::getUser()), - $this->_ruleToBackend($rule), - array($order)); - Horde::logMessage('Ingo_Storage_filters_sql::addRule(): ' . $query, __FILE__, __LINE__, PEAR_LOG_DEBUG); - $result = $this->_write_db->query($query, $values); - if (is_a($result, 'PEAR_Error')) { - Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR); - return $result; - } - - $rule['id'] = $id; - $this->_filters[$order] = $rule; - } - - /** - * Updates an existing rule with a rule hash. - * - * @param array $rule A rule hash - * @param integer $id A rule number - */ - public function updateRule($rule, $id) - { - $query = sprintf('UPDATE %s SET rule_name = ?, rule_action = ?, rule_value = ?, rule_flags = ?, rule_conditions = ?, rule_combine = ?, rule_stop = ?, rule_active = ?, rule_order = ? WHERE rule_id = ? AND rule_owner = ?', - $this->_params['table_rules']); - $values = array_merge($this->_ruleToBackend($rule), - array($id, $rule['id'], Ingo::getUser())); - Horde::logMessage('Ingo_Storage_filters_sql::updateRule(): ' . $query, __FILE__, __LINE__, PEAR_LOG_DEBUG); - $result = $this->_write_db->query($query, $values); - if (is_a($result, 'PEAR_Error')) { - Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR); - return $result; - } - - $this->_filters[$id] = $rule; - } - - /** - * Deletes a rule from the filters list. - * - * @param integer $id Number of the rule to delete. - * - * @return boolean True if the rule has been found and deleted. - */ - public function deleteRule($id) - { - if (!isset($this->_filters[$id])) { - return false; - } - - $query = sprintf('DELETE FROM %s WHERE rule_id = ? AND rule_owner = ?', - $this->_params['table_rules']); - $values = array($this->_filters[$id]['id'], Ingo::getUser()); - Horde::logMessage('Ingo_Storage_filters_sql::deleteRule(): ' . $query, __FILE__, __LINE__, PEAR_LOG_DEBUG); - $result = $this->_write_db->query($query, $values); - if (is_a($result, 'PEAR_Error')) { - Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR); - return $result; - } - unset($this->_filters[$id]); - - $query = sprintf('UPDATE %s SET rule_order = rule_order - 1 WHERE rule_owner = ? AND rule_order > ?', - $this->_params['table_rules']); - $values = array(Ingo::getUser(), $id); - Horde::logMessage('Ingo_Storage_filters_sql::deleteRule(): ' . $query, - __FILE__, __LINE__, PEAR_LOG_DEBUG); - $result = $this->_write_db->query($query, $values); - if (is_a($result, 'PEAR_Error')) { - Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR); - return $result; - } - - return true; - } - - /** - * Creates a copy of an existing rule. - * - * The created copy is added to the filters list right after the original - * rule. - * - * @param integer $id Number of the rule to copy. - * - * @return boolean True if the rule has been found and copied. - */ - public function copyRule($id) - { - if (isset($this->_filters[$id])) { - $newrule = $this->_filters[$id]; - $newrule['name'] = sprintf(_("Copy of %s"), $this->_filters[$id]['name']); - $this->addRule($newrule, false); - $this->ruleUp(count($this->_filters) - 1, count($this->_filters) - $id - 2); - return true; - } - - return false; - } - - /** - * Moves a rule up in the filters list. - * - * @param integer $id Number of the rule to move. - * @param integer $steps Number of positions to move the rule up. - */ - public function ruleUp($id, $steps = 1) - { - return $this->_ruleMove($id, -$steps); - } - - /** - * Moves a rule down in the filters list. - * - * @param integer $id Number of the rule to move. - * @param integer $steps Number of positions to move the rule down. - */ - public function ruleDown($id, $steps = 1) - { - return $this->_ruleMove($id, $steps); - } - - /** - * Moves a rule in the filters list. - * - * @param integer $id Number of the rule to move. - * @param integer $steps Number of positions and direction to move the - * rule. - */ - protected function _ruleMove($id, $steps) - { - $query = sprintf('UPDATE %s SET rule_order = rule_order %s 1 WHERE rule_owner = ? AND rule_order %s ? AND rule_order %s ?', - $this->_params['table_rules'], - $steps > 0 ? '-' : '+', - $steps > 0 ? '>' : '>=', - $steps > 0 ? '<=' : '<'); - $values = array(Ingo::getUser()); - if ($steps < 0) { - $values[] = (int)($id + $steps); - $values[] = (int)$id; - } else { - $values[] = (int)$id; - $values[] = (int)($id + $steps); - } - Horde::logMessage('Ingo_Storage_filters_sql::ruleUp(): ' . $query, - __FILE__, __LINE__, PEAR_LOG_DEBUG); - $result = $this->_write_db->query($query, $values); - if (is_a($result, 'PEAR_Error')) { - Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR); - return $result; - } - $query = sprintf('UPDATE %s SET rule_order = ? WHERE rule_owner = ? AND rule_id = ?', - $this->_params['table_rules']); - $values = array((int)($id + $steps), - Ingo::getUser(), - $this->_filters[$id]['id']); - Horde::logMessage('Ingo_Storage_filters_sql::ruleUp(): ' . $query, - __FILE__, __LINE__, PEAR_LOG_DEBUG); - $result = $this->_write_db->query($query, $values); - if (is_a($result, 'PEAR_Error')) { - Horde::logMessage($result, __FILE__, __LINE__, PEAR_LOG_ERR); - return $result; - } - - $this->init(); - } - - /** - * Disables a rule. - * - * @param integer $id Number of the rule to disable. - */ - public function ruleDisable($id) - { - $rule = $this->_filters[$id]; - $rule['disable'] = true; - $this->updateRule($rule, $id); - } - - /** - * Enables a rule. - * - * @param integer $id Number of the rule to enable. - */ - public function ruleEnable($id) - { - $rule = $this->_filters[$id]; - $rule['disable'] = false; - $this->updateRule($rule, $id); - } - -}