Horde 4/autoloading conventions
authorMichael M Slusarz <slusarz@curecanti.org>
Fri, 17 Jul 2009 19:19:44 +0000 (13:19 -0600)
committerMichael M Slusarz <slusarz@curecanti.org>
Fri, 17 Jul 2009 19:19:44 +0000 (13:19 -0600)
40 files changed:
ingo/lib/Driver.php
ingo/lib/Driver/Ldap.php [new file with mode: 0644]
ingo/lib/Driver/Null.php [new file with mode: 0644]
ingo/lib/Driver/Sivtest.php [new file with mode: 0644]
ingo/lib/Driver/Timsieved.php [new file with mode: 0644]
ingo/lib/Driver/Vfs.php [new file with mode: 0644]
ingo/lib/Driver/ldap.php [deleted file]
ingo/lib/Driver/null.php [deleted file]
ingo/lib/Driver/sivtest.php [deleted file]
ingo/lib/Driver/timsieved.php [deleted file]
ingo/lib/Driver/vfs.php [deleted file]
ingo/lib/Script.php
ingo/lib/Script/Imap.php [new file with mode: 0644]
ingo/lib/Script/Imap/Api.php [new file with mode: 0644]
ingo/lib/Script/Imap/Live.php [new file with mode: 0644]
ingo/lib/Script/Imap/Mock.php [new file with mode: 0644]
ingo/lib/Script/Maildrop.php [new file with mode: 0644]
ingo/lib/Script/Procmail.php [new file with mode: 0644]
ingo/lib/Script/Sieve.php [new file with mode: 0644]
ingo/lib/Script/imap.php [deleted file]
ingo/lib/Script/imap/live.php [deleted file]
ingo/lib/Script/imap/mock.php [deleted file]
ingo/lib/Script/maildrop.php [deleted file]
ingo/lib/Script/procmail.php [deleted file]
ingo/lib/Script/sieve.php [deleted file]
ingo/lib/Storage.php
ingo/lib/Storage/Blacklist.php [new file with mode: 0644]
ingo/lib/Storage/Filters.php [new file with mode: 0644]
ingo/lib/Storage/Filters/Sql.php [new file with mode: 0644]
ingo/lib/Storage/Forward.php [new file with mode: 0644]
ingo/lib/Storage/Mock.php [new file with mode: 0644]
ingo/lib/Storage/Prefs.php [new file with mode: 0644]
ingo/lib/Storage/Rule.php [new file with mode: 0644]
ingo/lib/Storage/Spam.php [new file with mode: 0644]
ingo/lib/Storage/Sql.php [new file with mode: 0644]
ingo/lib/Storage/Vacation.php [new file with mode: 0644]
ingo/lib/Storage/Whitelist.php [new file with mode: 0644]
ingo/lib/Storage/mock.php [deleted file]
ingo/lib/Storage/prefs.php [deleted file]
ingo/lib/Storage/sql.php [deleted file]

index a972abb..b85d5e2 100644 (file)
@@ -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 (file)
index 0000000..d6a5ddf
--- /dev/null
@@ -0,0 +1,263 @@
+<?php
+/**
+ * Ingo_Driver_Ldap:: implements the Sieve_Driver api to allow scripts to be
+ * installed and set active via an LDAP server.
+ *
+ * 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  Jason M. Felice <jason.m.felice@gmail.com>
+ * @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 (file)
index 0000000..231b6d8
--- /dev/null
@@ -0,0 +1,24 @@
+<?php
+/**
+ * Ingo_Driver_Null:: implements a null api -- useful for just testing
+ * the UI and storage.
+ *
+ * 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  Brent J. Nordquist <bjn@horde.org>
+ * @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 (file)
index 0000000..6f3caed
--- /dev/null
@@ -0,0 +1,210 @@
+<?php
+/**
+ * Ingo_Driver_Sivtest:: implements the Sieve_Driver api to allow scripts to
+ * be installed and set active via the Cyrus sivtest command line utility.
+ *
+ * Copyright 2003-2009 The Horde Project (http://www.horde.org/)
+ * Copyright 2004-2007 Liam Hoekenga <liamr@umich.edu>
+ *
+ * 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 <mike@graftonhall.co.nz>
+ * @author  Jan Schneider <jan@horde.org>
+ * @author  Liam Hoekenga <liamr@umich.edu>
+ * @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 (file)
index 0000000..d600b8d
--- /dev/null
@@ -0,0 +1,124 @@
+<?php
+/**
+ * Ingo_Driver_Timsieved:: implements the Sieve_Driver api to allow scripts to
+ * be installed and set active via a Cyrus timsieved server.
+ *
+ * Copyright 2003-2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file LICENSE for license information (ASL).  If you
+ * did not receive this file, see http://www.horde.org/licenses/asl.php.
+ *
+ * @author  Mike Cochrane <mike@graftonhall.co.nz>
+ * @author  Jan Schneider <jan@horde.org>
+ * @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 (file)
index 0000000..5e33423
--- /dev/null
@@ -0,0 +1,143 @@
+<?php
+/**
+ * Ingo_Driver_Vfs:: implements an Ingo storage driver using Horde VFS.
+ *
+ * Copyright 2003-2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file LICENSE for license information (ASL).  If you
+ * did not receive this file, see http://www.horde.org/licenses/asl.php.
+ *
+ * @author  Brent J. Nordquist <bjn@horde.org>
+ * @author  Jan Schneider <jan@horde.org>
+ * @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 (file)
index 7d15410..0000000
+++ /dev/null
@@ -1,263 +0,0 @@
-<?php
-/**
- * Ingo_Driver_ldap:: Implements the Sieve_Driver api to allow scripts to be
- * installed and set active via an LDAP server.
- *
- * 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  Jason M. Felice <jason.m.felice@gmail.com>
- * @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 (file)
index 54cadee..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-<?php
-/**
- * Ingo_Driver_null:: Implements a null api -- useful for just testing
- * the UI and storage.
- *
- * 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  Brent J. Nordquist <bjn@horde.org>
- * @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 (file)
index 3fc3953..0000000
+++ /dev/null
@@ -1,210 +0,0 @@
-<?php
-/**
- * Ingo_Driver_sivtest:: Implements the Sieve_Driver api to allow scripts to
- * be installed and set active via the Cyrus sivtest command line utility.
- *
- * Copyright 2003-2009 The Horde Project (http://www.horde.org/)
- * Copyright 2004-2007 Liam Hoekenga <liamr@umich.edu>
- *
- * 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 <mike@graftonhall.co.nz>
- * @author  Jan Schneider <jan@horde.org>
- * @author  Liam Hoekenga <liamr@umich.edu>
- * @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 (file)
index afc9ffa..0000000
+++ /dev/null
@@ -1,124 +0,0 @@
-<?php
-/**
- * Ingo_Driver_timsieved:: Implements the Sieve_Driver api to allow scripts to
- * be installed and set active via a Cyrus timsieved server.
- *
- * Copyright 2003-2009 The Horde Project (http://www.horde.org/)
- *
- * See the enclosed file LICENSE for license information (ASL).  If you
- * did not receive this file, see http://www.horde.org/licenses/asl.php.
- *
- * @author  Mike Cochrane <mike@graftonhall.co.nz>
- * @author  Jan Schneider <jan@horde.org>
- * @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 (file)
index 257a97c..0000000
+++ /dev/null
@@ -1,143 +0,0 @@
-<?php
-/**
- * Ingo_Driver_vfs:: Implements an Ingo storage driver using Horde VFS.
- *
- * Copyright 2003-2009 The Horde Project (http://www.horde.org/)
- *
- * See the enclosed file LICENSE for license information (ASL).  If you
- * did not receive this file, see http://www.horde.org/licenses/asl.php.
- *
- * @author  Brent J. Nordquist <bjn@horde.org>
- * @author  Jan Schneider <jan@horde.org>
- * @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;
-        }
-    }
-
-}
index b61a28c..eda050f 100644 (file)
@@ -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 (file)
index 0000000..a3f0a87
--- /dev/null
@@ -0,0 +1,370 @@
+<?php
+/**
+ * The Ingo_Script_Imap:: class represents an IMAP client-side script
+ * generator.
+ *
+ * $Horde: ingo/lib/Script/imap.php,v 1.76 2009/01/19 18:10:01 mrubinsk Exp $
+ *
+ * Copyright 2003-2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file LICENSE for license information (ASL).  If you
+ * did not receive this file, see http://www.horde.org/licenses/asl.php.
+ *
+ * @author  Michael Slusarz <slusarz@horde.org>
+ * @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:
+     * <pre>
+     * 'mailbox' - The name of the mailbox to filter.
+     * </pre>
+     *
+     * @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 (file)
index 0000000..a2d2db8
--- /dev/null
@@ -0,0 +1,100 @@
+<?php
+/**
+ * The Ingo_Script_Imap_Api:: is the base driver class for Ingo_Script_Imap::.
+ *
+ * Copyright 2003-2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file LICENSE for license information (ASL).  If you
+ * did not receive this file, see http://www.horde.org/licenses/asl.php.
+ *
+ * @author  Michael Slusarz <slusarz@horde.org>
+ * @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 (file)
index 0000000..dee3fac
--- /dev/null
@@ -0,0 +1,110 @@
+<?php
+/**
+ * The Ingo_Script_Imap_Live:: driver.
+ *
+ * Copyright 2006-2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file LICENSE for license information (ASL).  If you
+ * did not receive this file, see http://www.horde.org/licenses/asl.php.
+ *
+ * @author  Jason M. Felice <jason.m.felice@gmail.com>
+ * @author  Michael Slusarz <slusarz@curecanti.org>
+ * @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 (file)
index 0000000..a88afe9
--- /dev/null
@@ -0,0 +1,125 @@
+<?php
+/**
+ * TODO
+ */
+class Ingo_Script_Imap_Mock extends Ingo_Script_Imap_Api
+{
+    /**
+     * TODO
+     */
+    protected $_fixtures = array();
+
+    /**
+     * TODO
+     */
+    protected $_folders = array();
+
+    /**
+     * TODO
+     */
+    public function loadFixtures($dir)
+    {
+        $this->_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 (file)
index 0000000..676f945
--- /dev/null
@@ -0,0 +1,762 @@
+<?php
+/**
+ * The Ingo_Script_Maildrop:: class represents a maildrop script generator.
+ *
+ * Copyright 2005-2007 Matt Weyland <mathias@weyland.ch>
+ *
+ * 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 <mathias@weyland.ch>
+ * @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 <mathias@weyland.ch>
+ * @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 <mathias@weyland.ch>
+ * @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 <mathias@weyland.ch>
+ * @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 (file)
index 0000000..f022843
--- /dev/null
@@ -0,0 +1,802 @@
+<?php
+/**
+ * The Ingo_Script_Procmail:: class represents a Procmail script generator.
+ *
+ * Copyright 2003-2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file LICENSE for license information (ASL).  If you
+ * did not receive this file, see http://www.horde.org/licenses/asl.php.
+ *
+ * @author  Brent J. Nordquist <bjn@horde.org>
+ * @author  Ben Chavet <ben@horde.org>
+ * @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 <ben@chavet.net>
+ * @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 <ben@chavet.net>
+ * @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 <slusarz@horde.org>
+ * @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 (file)
index 0000000..6dd2fd9
--- /dev/null
@@ -0,0 +1,2975 @@
+<?php
+/**
+ * The Ingo_Script_Sieve class represents a Sieve 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  Mike Cochrane <mike@graftonhall.co.nz>
+ * @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('/(.)(?<!\\\)\,(.)/',
+                                                            "$1\n$2",
+                                                            $condition['field']),
+                                  'comparator' => $comparator);
+                    $use_address_test = false;
+
+                    if ($condition['match'] != 'regex') {
+                        $condition['value'] = preg_replace('/(.)(?<!\\\)\,(.)/',
+                                                           "$1\n$2",
+                                                           $condition['value']);
+                    }
+
+                    /* Do 'smarter' searching for fields where we know we have
+                     * e-mail addresses. */
+                    if (preg_match('/^(From|To|Cc|Bcc)/', $condition['field'])) {
+                        $vals['addresses'] = $condition['value'];
+                        $use_address_test = true;
+                    } else {
+                        $vals['strings'] = $condition['value'];
+                    }
+
+                    switch ($condition['match']) {
+                    case 'contains':
+                        $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($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 <mike@graftonhall.co.nz>
+ * @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 <mike@graftonhall.co.nz>
+ * @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 <mike@graftonhall.co.nz>
+ * @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 <mike@graftonhall.co.nz>
+ * @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 <mike@graftonhall.co.nz>
+ * @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 <mike@graftonhall.co.nz>
+ * @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 <mike@graftonhall.co.nz>
+ * @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 <mike@graftonhall.co.nz>
+ * @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 <tmerritt@email.arizona.edu>
+ * @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 <mike@graftonhall.co.nz>
+ * @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 <mike@graftonhall.co.nz>
+ * @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 <mike@graftonhall.co.nz>
+ * @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 <mike@graftonhall.co.nz>
+ * @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 <mike@graftonhall.co.nz>
+ * @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('((?<!\\\)\,|\r\n|\n|\r)', $this->_vars['headers']);
+        if (!$headers) {
+            return false;
+        }
+
+        $strings = preg_split('((?<!\\\)\,|\r\n|\n|\r)', $this->_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 <michael.menge@zdv.uni-tuebingen.de>
+ * @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('((?<!\\\)\,|\r\n|\n|\r)', $this->_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 <mike@graftonhall.co.nz>
+ * @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 <mike@graftonhall.co.nz>
+ * @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 <mike@graftonhall.co.nz>
+ * @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 <mike@graftonhall.co.nz>
+ * @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 <mike@graftonhall.co.nz>
+ * @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 <mike@graftonhall.co.nz>
+ * @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 <mike@graftonhall.co.nz>
+ * @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 <mike@graftonhall.co.nz>
+ * @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 <mike@graftonhall.co.nz>
+ * @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 <slusarz@horde.org>
+ * @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 <mike@graftonhall.co.nz>
+ * @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 <mike@graftonhall.co.nz>
+ * @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 <wolstena@sfu.ca>
+ * @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 (file)
index aff92de..0000000
+++ /dev/null
@@ -1,459 +0,0 @@
-<?php
-/**
- * The Ingo_Script_imap:: class represents an IMAP client-side script
- * generator.
- *
- * $Horde: ingo/lib/Script/imap.php,v 1.76 2009/01/19 18:10:01 mrubinsk Exp $
- *
- * Copyright 2003-2009 The Horde Project (http://www.horde.org/)
- *
- * See the enclosed file LICENSE for license information (ASL).  If you
- * did not receive this file, see http://www.horde.org/licenses/asl.php.
- *
- * @author  Michael Slusarz <slusarz@horde.org>
- * @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:
-     * <pre>
-     * 'mailbox' - The name of the mailbox to filter.
-     * </pre>
-     *
-     * @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 (file)
index a2ad225..0000000
+++ /dev/null
@@ -1,110 +0,0 @@
-<?php
-/**
- * The Ingo_Script_imap_live:: driver.
- *
- * Copyright 2006-2009 The Horde Project (http://www.horde.org/)
- *
- * See the enclosed file LICENSE for license information (ASL).  If you
- * did not receive this file, see http://www.horde.org/licenses/asl.php.
- *
- * @author  Jason M. Felice <jason.m.felice@gmail.com>
- * @author  Michael Slusarz <slusarz@curecanti.org>
- * @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 (file)
index ecd4784..0000000
+++ /dev/null
@@ -1,125 +0,0 @@
-<?php
-/**
- * TODO
- */
-class Ingo_Script_imap_mock extends Ingo_Script_imap_api
-{
-    /**
-     * TODO
-     */
-    protected $_fixtures = array();
-
-    /**
-     * TODO
-     */
-    protected $_folders = array();
-
-    /**
-     * TODO
-     */
-    public function loadFixtures($dir)
-    {
-        $this->_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 (file)
index 2c3b448..0000000
+++ /dev/null
@@ -1,762 +0,0 @@
-<?php
-/**
- * The Ingo_Script_maildrop:: class represents a maildrop script generator.
- *
- * Copyright 2005-2007 Matt Weyland <mathias@weyland.ch>
- *
- * 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 <mathias@weyland.ch>
- * @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 <mathias@weyland.ch>
- * @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 <mathias@weyland.ch>
- * @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 <mathias@weyland.ch>
- * @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 (file)
index 494cfd2..0000000
+++ /dev/null
@@ -1,802 +0,0 @@
-<?php
-/**
- * The Ingo_Script_procmail:: class represents a Procmail script generator.
- *
- * Copyright 2003-2009 The Horde Project (http://www.horde.org/)
- *
- * See the enclosed file LICENSE for license information (ASL).  If you
- * did not receive this file, see http://www.horde.org/licenses/asl.php.
- *
- * @author  Brent J. Nordquist <bjn@horde.org>
- * @author  Ben Chavet <ben@horde.org>
- * @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 <ben@chavet.net>
- * @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 <ben@chavet.net>
- * @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 <slusarz@horde.org>
- * @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 (file)
index 7539542..0000000
+++ /dev/null
@@ -1,2976 +0,0 @@
-<?php
-/**
- * The Ingo_Script_sieve class represents a Sieve 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  Mike Cochrane <mike@graftonhall.co.nz>
- * @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('/(.)(?<!\\\)\,(.)/',
-                                                            "$1\n$2",
-                                                            $condition['field']),
-                                  'comparator' => $comparator);
-                    $use_address_test = false;
-
-                    if ($condition['match'] != 'regex') {
-                        $condition['value'] = preg_replace('/(.)(?<!\\\)\,(.)/',
-                                                           "$1\n$2",
-                                                           $condition['value']);
-                    }
-
-                    /* Do 'smarter' searching for fields where we know we have
-                     * e-mail addresses. */
-                    if (preg_match('/^(From|To|Cc|Bcc)/', $condition['field'])) {
-                        $vals['addresses'] = $condition['value'];
-                        $use_address_test = true;
-                    } else {
-                        $vals['strings'] = $condition['value'];
-                    }
-
-                    switch ($condition['match']) {
-                    case 'contains':
-                        $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($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 <mike@graftonhall.co.nz>
- * @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 <mike@graftonhall.co.nz>
- * @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 <mike@graftonhall.co.nz>
- * @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 <mike@graftonhall.co.nz>
- * @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 <mike@graftonhall.co.nz>
- * @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 <mike@graftonhall.co.nz>
- * @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 <mike@graftonhall.co.nz>
- * @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 <mike@graftonhall.co.nz>
- * @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 <tmerritt@email.arizona.edu>
- * @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 <mike@graftonhall.co.nz>
- * @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 <mike@graftonhall.co.nz>
- * @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 <mike@graftonhall.co.nz>
- * @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 <mike@graftonhall.co.nz>
- * @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 <mike@graftonhall.co.nz>
- * @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('((?<!\\\)\,|\r\n|\n|\r)', $this->_vars['headers']);
-        if (!$headers) {
-            return false;
-        }
-
-        $strings = preg_split('((?<!\\\)\,|\r\n|\n|\r)', $this->_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 <michael.menge@zdv.uni-tuebingen.de>
- * @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('((?<!\\\)\,|\r\n|\n|\r)', $this->_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 <mike@graftonhall.co.nz>
- * @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 <mike@graftonhall.co.nz>
- * @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 <mike@graftonhall.co.nz>
- * @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 <mike@graftonhall.co.nz>
- * @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 <mike@graftonhall.co.nz>
- * @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 <mike@graftonhall.co.nz>
- * @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 <mike@graftonhall.co.nz>
- * @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 <mike@graftonhall.co.nz>
- * @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 <mike@graftonhall.co.nz>
- * @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 <slusarz@horde.org>
- * @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 <mike@graftonhall.co.nz>
- * @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 <mike@graftonhall.co.nz>
- * @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 <wolstena@sfu.ca>
- * @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');
-    }
-
-}
index c627482..8e908a4 100644 (file)
@@ -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 <slusarz@horde.org>
- * @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 <slusarz@horde.org>
- * @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 <slusarz@horde.org>
- * @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 <slusarz@horde.org>
- * @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 <slusarz@horde.org>
- * @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 <jason.m.felice@gmail.com>
- * @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 <slusarz@horde.org>
- * @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 (file)
index 0000000..2733db5
--- /dev/null
@@ -0,0 +1,57 @@
+<?php
+/**
+ * Ingo_Storage_Blacklist is the object used to hold blacklist rule
+ * information.
+ *
+ * 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  Michael Slusarz <slusarz@horde.org>
+ * @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 (file)
index 0000000..4fdea7c
--- /dev/null
@@ -0,0 +1,264 @@
+<?php
+/**
+ * Ingo_Storage_Filters is the object used to hold user-defined filtering rule
+ * information.
+ *
+ * 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  Michael Slusarz <slusarz@horde.org>
+ * @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 (file)
index 0000000..2202d72
--- /dev/null
@@ -0,0 +1,344 @@
+<?php
+/**
+ * Ingo_Storage_Filters_Sql is the object used to hold user-defined filtering
+ * rule information.
+ *
+ * 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 <jan@horde.org>
+ * @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 (file)
index 0000000..051dd1f
--- /dev/null
@@ -0,0 +1,45 @@
+<?php
+/**
+ * Ingo_Storage_Forward is the object used to hold mail forwarding rule
+ * information.
+ *
+ * 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  Michael Slusarz <slusarz@horde.org>
+ * @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 (file)
index 0000000..38825f4
--- /dev/null
@@ -0,0 +1,55 @@
+<?php
+/**
+ * Ingo_Storage_Mock:: is used for testing purposes.  It just keeps the
+ * data local and doesn't put it anywhere.
+ *
+ * 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  Jason M. Felice <jason.m.felice@gmail.com>
+ * @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 (file)
index 0000000..be3547e
--- /dev/null
@@ -0,0 +1,183 @@
+<?php
+/**
+ * Ingo_Storage_Prefs:: implements the Ingo_Storage:: API to save Ingo data
+ * via the Horde preferences system.
+ *
+ * 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  Michael Slusarz <slusarz@horde.org>
+ * @author  Jan Schneider <jan@horde.org>
+ * @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 (file)
index 0000000..9dfb0e7
--- /dev/null
@@ -0,0 +1,84 @@
+<?php
+/**
+ * Ingo_Storage_Rule:: is the base class for the various action objects
+ * used by Ingo_Storage.
+ *
+ * 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  Michael Slusarz <slusarz@horde.org>
+ * @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 (file)
index 0000000..9f3f2da
--- /dev/null
@@ -0,0 +1,44 @@
+<?php
+/**
+ * Ingo_Storage_Spam is an object used to hold default spam-rule filtering
+ * information.
+ *
+ * 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  Jason M. Felice <jason.m.felice@gmail.com>
+ * @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 (file)
index 0000000..6b3c45b
--- /dev/null
@@ -0,0 +1,396 @@
+<?php
+/**
+ * Ingo_Storage_Sql implements the Ingo_Storage API to save Ingo data via
+ * PHP's PEAR database abstraction layer.
+ *
+ * Required values for $params:<pre>
+ *   'phptype'  - The database type (e.g. 'pgsql', 'mysql', etc.).
+ *   'charset'  - The database's internal charset.</pre>
+ *
+ * Required by some database implementations:<pre>
+ *   '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.</pre>
+ *
+ * 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 <jan@horde.org>
+ * @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 (file)
index 0000000..1b22b39
--- /dev/null
@@ -0,0 +1,142 @@
+<?php
+/**
+ * Ingo_Storage_Vacation is the object used to hold vacation rule
+ * information.
+ *
+ * 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  Michael Slusarz <slusarz@horde.org>
+ * @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 (file)
index 0000000..19f5295
--- /dev/null
@@ -0,0 +1,47 @@
+<?php
+/**
+ * Ingo_Storage_Whitelist is the object used to hold whitelist rule
+ * information.
+ *
+ * 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  Michael Slusarz <slusarz@horde.org>
+ * @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 (file)
index aeafa99..0000000
+++ /dev/null
@@ -1,55 +0,0 @@
-<?php
-/**
- * Ingo_Storage_mock:: is used for testing purposes.  It just keeps the
- * data local and doesn't put it anywhere.
- *
- * 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  Jason M. Felice <jason.m.felice@gmail.com>
- * @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 (file)
index 3c61bce..0000000
+++ /dev/null
@@ -1,183 +0,0 @@
-<?php
-/**
- * Ingo_Storage_prefs:: implements the Ingo_Storage:: API to save Ingo data
- * via the Horde preferences system.
- *
- * 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  Michael Slusarz <slusarz@horde.org>
- * @author  Jan Schneider <jan@horde.org>
- * @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 (file)
index ceb5157..0000000
+++ /dev/null
@@ -1,737 +0,0 @@
-<?php
-/**
- * Ingo_Storage_sql implements the Ingo_Storage API to save Ingo data via
- * PHP's PEAR database abstraction layer.
- *
- * Required values for $params:<pre>
- *   'phptype'  - The database type (e.g. 'pgsql', 'mysql', etc.).
- *   'charset'  - The database's internal charset.</pre>
- *
- * Required by some database implementations:<pre>
- *   '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.</pre>
- *
- * 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 <jan@horde.org>
- * @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 <jan@horde.org>
- * @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);
-    }
-
-}