<?php
/**
 * The Ingo_Script_sieve:: class represents a Sieve Script.
 *
 * $Horde: ingo/lib/Script/sieve.php,v 1.63.10.30 2006/10/10 13:38:10 jan Exp $
 *
 * 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>
 * @since   Ingo 0.1
 * @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
    );

    /**
     * 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
    );

    /**
     * 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();

    /**
     * Returns a script previously generated with generate().
     *
     * @return string  The Sieve script.
     */
    function toCode()
    {
        $code = "# Sieve Filter\n# " . _("Generated by Ingo (http://www.horde.org/ingo/)") . ' (' . date('F j, Y, g:i a') . ")\n\n";
        $code = String::convertCharset($code, 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 $code;
    }

    /**
     * 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);
        }
    }

    function addBlock($block)
    {
        $this->_blocks[] = $block;
    }

    function check()
    {
        foreach ($this->_blocks as $block) {
            $res = $block->check();
            if ($res !== true) {
                return $res;
            }
        }

        return true;
    }

    function requires()
    {
        $requires = array();
        foreach ($this->_blocks as $block) {
            $requires = array_merge($requires, $block->requires());
        }

        return array_unique($requires);
    }

    /**
     * Generates the Sieve script to do the filtering specified in
     * the rules.
     *
     * @return string  The Sieve script.
     */
    function generate()
    {
        global $ingo_storage;

        $_blocks = array();

        /* Forward Script */
        if ($this->_validRule(INGO_STORAGE_ACTION_FORWARD)) {
            $forwardBlocks = array();

            $forward = &$ingo_storage->retrieve(INGO_STORAGE_ACTION_FORWARD);
            $fwd_addr = $forward->getForwardAddresses();
            if (!empty($fwd_addr)) {
                $action = $addrs = array();

                foreach ($fwd_addr as $addr) {
                    $addr = trim($addr);
                    if (!empty($addr)) {
                        $action[] = &new Sieve_Action_Redirect(array('address' => $addr));
                    }
                }

                if ($forward->getForwardKeep()) {
                    $action[] = &new Sieve_Action_Keep();
                }

                $forwardBlocks[] = &new Sieve_Comment(_("Forwards"));
                $test = &new Sieve_Test_True();
                $if = &new Sieve_If($test);
                $if->setActions($action);
                $forwardBlocks[] = $if;
            }
        }

        /* Blacklist Script */
        if ($this->_validRule(INGO_STORAGE_ACTION_BLACKLIST)) {
            $blacklist = &$ingo_storage->retrieve(INGO_STORAGE_ACTION_BLACKLIST);
            $bl_addr = $blacklist->getBlacklist();
            $folder = $blacklist->getBlacklistFolder();
            $blacklistBlocks = array();

            if (!empty($bl_addr)) {
                $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();

                $comment = &new Sieve_Comment(_("Blacklisted Addresses"));
                $blacklistBlocks[] = $comment;

                // 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);
                        $blacklistBlocks[] = $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);
                        $blacklistBlocks[] = $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);
                    $blacklistBlocks[] = $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);
                    $blacklistBlocks[] = $if;
                }
            }
        }

        /* Whitelist Script */
        if ($this->_validRule(INGO_STORAGE_ACTION_WHITELIST)) {
            $whitelist = &$ingo_storage->retrieve(INGO_STORAGE_ACTION_WHITELIST);
            $wl_addr = $whitelist->getWhitelist();
            $whitelistBlocks = array();

            if (!empty($wl_addr)) {
                $whitelistBlocks[] = &new Sieve_Comment(_("Whitelisted Addresses"));
                $action = array();
                $action[] = &new Sieve_Action_Keep();
                $action[] = &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);
                $whitelistBlocks[] = $if;
            }
        }

        /* Vacation Script */
        if ($this->_validRule(INGO_STORAGE_ACTION_VACATION)) {
            $action = array();
            $tests = array();
            $vacation = &$ingo_storage->retrieve(INGO_STORAGE_ACTION_VACATION);
            $vacation_addr = $vacation->getVacationAddresses();
            $vacationBlocks = array();
            if (count($vacation_addr)) {
                $vals = array('subject' => String::convertCharset($vacation->getVacationSubject(), NLS::getCharset(), 'UTF-8'),
                              'days' => $vacation->getVacationDays(),
                              'addresses' => $vacation_addr,
                              'reason' => String::convertCharset($vacation->getVacationReason(), NLS::getCharset(), 'UTF-8'));

                include_once 'Horde/MIME/Headers.php';
                $mime_headers = &new MIME_Headers();
                $listHeaders = $mime_headers->listHeaders();

                $action[] = &new Sieve_Action_Vacation($vals);

                if ($vacation->getVacationIgnorelist()) {
                    $tmp = &new Sieve_Test_Exists(array('headers' => implode("\n", array_keys($listHeaders))));
                    $tests[] = &new Sieve_Test_Not($tmp);
                    $vals = array('headers' => 'Precedence',
                                  'match-type' => ':is',
                                  'strings' => 'list,bulk',
                                  '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);
                }

                $comment = &new Sieve_Comment(_("Vacation"));
                $vacationBlocks[] = $comment;

                if ($tests) {
                    $test = &new Sieve_Test_Allof($tests);
                    $if = &new Sieve_If($test);
                    $if->setActions($action);
                    $vacationBlocks[] = $if;
                } else {
                    $vacationBlocks[] = $action[0];
                }
            }
        }

        $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:
                foreach ($whitelistBlocks as $white) {
                    $this->addBlock($white);
                }
                continue 2;

            case INGO_STORAGE_ACTION_BLACKLIST:
                foreach ($blacklistBlocks as $block) {
                    $this->addBlock($block);
                }
                continue 2;

            case INGO_STORAGE_ACTION_VACATION:
                foreach ($vacationBlocks as $block) {
                    $this->addBlock($block);
                }
                continue 2;

            case INGO_STORAGE_ACTION_FORWARD:
                 foreach ($forwardBlocks as $block) {
                     $this->addBlock($block);
                 }
                 continue 2;
            }

            $comment = &new Sieve_Comment($filter['name']);
            $this->addBlock($comment);

            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 to':
                    $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('/(?<!\\\)\,(.)/',
                                                            "\n$1",
                                                            $condition['field']),
                                  'comparator' => $comparator);
                    $use_address_test = false;

                    if ($condition['match'] != 'regex') {
                        $condition['value'] = preg_replace('/(?<!\\\)\,(.)/',
                                                           "\n$1",
                                                           $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);
                        } 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);
                        } 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);
                        } 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);
                        } 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'] .= '*';
                            }
                            $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'] .= '*';
                            }
                            $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'];
                            }
                            $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'];
                            }
                            $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);
                        } 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);
                        } 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);
                        } else {
                            $tmp = &new Sieve_Test_Header($vals);
                        }
                        $test->addTest(new Sieve_Test_Not($tmp));
                        break;
                    }
                }
            }

            $if = &new Sieve_If($test);
            $if->setActions($action);
            $this->addBlock($if);
        }

        return $this->toCode();
    }

}

/**
 * The Sieve_If:: This class represents a Sieve If Statement
 *
 * @author  Mike Cochrane <mike@graftonhall.co.nz>
 * @since   Ingo 0.1
 * @package Ingo
 */
class Sieve_If {

    var $_test;
    var $_actions = array();
    var $_elsifs = array();
    var $_else;

    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;
    }

    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";
    }

    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;
    }

    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:: This class represents a Sieve Else Statement
 *
 * @author  Mike Cochrane <mike@graftonhall.co.nz>
 * @since   Ingo 0.1
 * @package Ingo
 */
class Sieve_Else {

    var $_actions = array();

    function Sieve_Else($actions = null)
    {
        if (is_array($actions)) {
            $this->_actions = $acitons;
        } elseif (!is_null($actions)) {
            $this->_actions[] = $actions;
        }
    }

    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;
    }

    function check()
    {
        foreach ($this->_actions as $action) {
            $res = $action->check();
            if ($res !== true) {
                return $res;
            }
        }

        return true;
    }

    function requires()
    {
        $requires = array();

        foreach ($this->_actions as $action) {
            $requires = array_merge($requires, $action->requires());
        }

        return $requires;
    }

}

/**
 * The Sieve_Elsif:: This class represents a Sieve Elsif Statement
 *
 * @author  Mike Cochrane <mike@graftonhall.co.nz>
 * @since   Ingo 0.1
 * @package Ingo
 */
class Sieve_Elsif {

    var $_test;
    var $_actions = array();

    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;
    }

    function toCode()
    {
        $code = 'elsif ' . $this->_test->toCode() . " { \n";
        foreach ($this->_actions as $action) {
            $code .= '    ' . $action->toCode() . "\n";
        }
        $code .= "} ";

        return $code;
    }

    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;
    }

    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:: This 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 {

    function toCode()
    {
        return 'toCode() Function Not Implemented in class ' . get_class($this);
    }

    function check()
    {
        return 'check() Function Not Implemented in class ' . get_class($this);
    }

    function requires()
    {
        return array();
    }

}

/**
 * The Sieve_Test_True:: This class represents a True test.
 *
 * @author  Mike Cochrane <mike@graftonhall.co.nz>
 * @since   Ingo 0.1
 * @package Ingo
 */
class Sieve_Test_True extends Sieve_Test {

    function toCode()
    {
        return 'true';
    }

    function check()
    {
        return true;
    }

}

/**
 * The Sieve_Test_False:: This class represents a False test.
 *
 * @author  Mike Cochrane <mike@graftonhall.co.nz>
 * @since   Ingo 0.1
 * @package Ingo
 */
class Sieve_Test_False extends Sieve_Test {

    function toCode()
    {
        return 'false';
    }

    function check()
    {
        return true;
    }

}

/**
 * The Sieve_Test_Allof:: This 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();

    function Sieve_Test_Allof($test = null)
    {
        if (is_array($test)) {
            $this->_tests = $test;
        } elseif (!is_null($test)) {
            $this->_tests[] = $test;
        }
    }

    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;
    }

    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;
    }

    function requires()
    {
        $requires = array();

        foreach ($this->_tests as $test) {
            $requires = array_merge($requires, $test->requires());
        }

        return $requires;
    }

}

/**
 * The Sieve_Test_Anyof:: This 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();

    function Sieve_Test_Anyof($test = null)
    {
        if (is_array($test)) {
            $this->_tests = $test;
        } elseif (!is_null($test)) {
            $this->_tests[] = $test;
        }
    }

    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;
    }

    function check()
    {
        foreach ($this->_tests as $test) {
            $res = $test->check();
            if ($res !== true) {
                return $res;
            }
        }

        return true;
    }

    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 {

    var $_vars = array();

    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;
    }

    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'] . '"]';
     }

     function check()
     {
         $headers = preg_split('(\r\n|\n|\r)', $this->_vars['headers']);
         return $headers ? true : _("No headers specified");
     }

     function requires()
     {
         return array('relational', 'comparator-i;ascii-numeric');
     }

}

/**
 * The Sieve_Test_Size:: This 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 {

    var $_vars = array();

    function Sieve_Test_Size($vars = array())
    {
        $this->_vars['comparison'] = (isset($vars['comparison'])) ? $vars['comparison'] : '';
        $this->_vars['size'] = (isset($vars['size'])) ? $vars['size'] : '';
    }

    function toCode()
    {
        return 'size ' . $this->_vars['comparison'] . ' ' . $this->_vars['size'];
    }

    function check()
    {
        if (!(isset($this->_vars['comparison']) &&
              isset($this->_vars['size']))) {
            return false;
        }

        return true;
    }

}

/**
 * The Sieve_Test_Not:: This 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();

    function Sieve_Test_Not($test)
    {
        $this->_test = $test;
    }

    function check()
    {
        return $this->_test->check();
    }

    function toCode()
    {
        return 'not ' . $this->_test->toCode();
    }

}

/**
 * The Sieve_Test_Exists:: This 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 {

    var $_vars = array();

    function Sieve_Test_Exists($vars = array())
    {
        $this->_vars['headers'] = (isset($vars['headers'])) ? $vars['headers'] : '';
    }

    function check()
    {
        $headers = preg_split('(\r\n|\n|\r)', $this->_vars['headers']);
        if (!$headers) {
            return _("No headers specified");
        }

        return true;
    }

    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:: This 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 {

    var $_vars = array();

    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'] : '';
    }

    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;
    }

    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, create_function('$a', 'return !empty($a);'));
        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, create_function('$a', 'return !empty($a);'));
        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;
    }

    function requires()
    {
        if ($this->_vars['match-type'] == ':regex') {
            return array('regex');
        }
        return array();
    }

}

/**
 * The Sieve_Test_Header:: This 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 {

    var $_vars = array();

    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'] : '';
    }

    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;
    }

    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, create_function('$a', 'return !empty($a);'));
        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, create_function('$a', 'return !empty($a);'));
        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;
    }

    function requires()
    {
        if ($this->_vars['match-type'] == ':regex') {
            return array('regex');
        }
        return array();
    }

}

/**
 * A Comment. This and Sieve_If should really extends a Sieve_Block eventually.
 *
 * @author  Mike Cochrane <mike@graftonhall.co.nz>
 * @since   Ingo 0.1
 * @package Ingo
 */
class Sieve_Comment {

    var $_comment;

    function Sieve_Comment($comment)
    {
        $this->_comment = $comment;
    }

    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 String::convertCharset($code, NLS::getCharset(), 'UTF-8');
    }

    function check()
    {
        return true;
    }

    function requires()
    {
        return array();
    }

}

/**
 * The Sieve_Action:: This 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 {

    function toCode()
    {
        return 'toCode() Function Not Implemented in class ' . get_class($this) ;
    }

    function toString()
    {
        return $this->toCode();
    }

    function check()
    {
        return 'check() Function Not Implemented in class ' . get_class($this) ;
    }

    function requires()
    {
        return array();
    }

}

/**
 * The Sieve_Action_Redirect:: This 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 {

    var $_vars = array();

    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']) . '";';
    }

    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 {

    var $_vars = array();

    function Sieve_Action_Reject($vars = array())
    {
        $this->_vars['reason'] = (isset($vars['reason'])) ? $vars['reason'] : '';
    }

    function toCode()
    {
        return 'reject "' . Ingo_Script_sieve::escapeString($this->_vars['reason']) . '";';
    }

    function check()
    {
        if (empty($this->_vars['reason'])) {
            return _("Missing reason for reject");
        }

        return true;
    }

    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 {

    function toCode()
    {
        return 'keep;';
    }

    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 {

    function toCode()
    {
        return 'discard;';
    }

    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 {

    function toCode()
    {
        return 'stop;';
    }

    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 {

    var $_vars = array();

    function Sieve_Action_Fileinto($vars = array())
    {
        $this->_vars['folder'] = (isset($vars['folder'])) ? $vars['folder'] : '';
    }

    function toCode()
    {
        return 'fileinto "' . Ingo_Script_sieve::escapeString($this->_vars['folder']) . '";';
    }

    function check()
    {
        if (empty($this->_vars['folder'])) {
            return _("Inexistant mailbox specified for message delivery.");
        }

        return true;
    }

    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 {

    var $_vars = array();

    function Sieve_Action_Vacation($vars = array())
    {
        $this->_vars['days'] = (isset($vars['days'])) ? intval($vars['days']) : 7;
        $this->_vars['addresses'] = (isset($vars['addresses'])) ? $vars['addresses'] : '';
        $this->_vars['subject'] = (isset($vars['subject'])) ? $vars['subject'] : '';
        $this->_vars['reason'] = (isset($vars['reason'])) ? $vars['reason'] : '';
    }

    function toCode()
    {
        $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') . '" ';
        }
        $code .= '"' . Ingo_Script_sieve::escapeString($this->_vars['reason']) . '";';
        return $code;
    }

    function check()
    {
        if (empty($this->_vars['reason'])) {
            return _("Missing reason in vacation.");
        }

        return true;
    }

    function requires()
    {
        return array('vacation');
    }

}

/**
 * The Sieve_Action_Flag:: class is the base class for flag actions.
 *
 * @author  Michael Slusarz <slusarz@bigworm.colorado.edu>
 * @since   Ingo 0.1
 * @package Ingo
 */
class Sieve_Action_Flag extends Sieve_Action {

    var $_vars = array();

    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'] = '';
        }
    }

    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;
    }

    function check()
    {
        return true;
    }

    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 {

    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 {

    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 {

    var $_vars = array();

    function Sieve_Action_Notify($vars = array())
    {
        $this->_vars['address'] = isset($vars['address']) ? $vars['address'] : '';
        $this->_vars['name'] = isset($vars['name']) ? $vars['name'] : '';
    }

    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'] . '";';
    }

    function check()
    {
        if (empty($this->_vars['address'])) {
            return _("Missing address to notify");
        }

        return true;
    }

    function requires()
    {
        return array('notify');
    }

}
