alecpl
2011-11-18 c994e0e7cd9f593eb21ff80c7c1ddbeaf2a1b12a
- Applied fixes from trunk up to r5451


11 files added
15 files modified
1 files deleted
1152 ■■■■ changed files
CHANGELOG 1 ●●●● patch | view | raw | blame | history
plugins/managesieve/Changelog 12 ●●●●● patch | view | raw | blame | history
plugins/managesieve/lib/rcube_sieve.php 15 ●●●● patch | view | raw | blame | history
plugins/managesieve/lib/rcube_sieve_script.php 187 ●●●● patch | view | raw | blame | history
plugins/managesieve/localization/en_US.inc 19 ●●●●● patch | view | raw | blame | history
plugins/managesieve/localization/pl_PL.inc 19 ●●●●● patch | view | raw | blame | history
plugins/managesieve/managesieve.js 41 ●●●●● patch | view | raw | blame | history
plugins/managesieve/managesieve.php 347 ●●●● patch | view | raw | blame | history
plugins/managesieve/package.xml 100 ●●●●● patch | view | raw | blame | history
plugins/managesieve/skins/default/images/down_small.gif patch | view | raw | blame | history
plugins/managesieve/skins/default/images/toolbar.png patch | view | raw | blame | history
plugins/managesieve/skins/default/images/up_small.gif patch | view | raw | blame | history
plugins/managesieve/skins/default/managesieve.css 38 ●●●●● patch | view | raw | blame | history
plugins/managesieve/tests/parser.phpt 90 ●●●● patch | view | raw | blame | history
plugins/managesieve/tests/parser_body.phpt 49 ●●●●● patch | view | raw | blame | history
plugins/managesieve/tests/parser_imapflags.phpt 28 ●●●●● patch | view | raw | blame | history
plugins/managesieve/tests/parser_include.phpt 30 ●●●●● patch | view | raw | blame | history
plugins/managesieve/tests/parser_kep14.phpt 2 ●●● patch | view | raw | blame | history
plugins/managesieve/tests/parser_prefix.phpt 25 ●●●●● patch | view | raw | blame | history
plugins/managesieve/tests/parser_relational.phpt 25 ●●●●● patch | view | raw | blame | history
plugins/managesieve/tests/parser_vacation.phpt 39 ●●●●● patch | view | raw | blame | history
plugins/managesieve/tests/parser_variables.phpt 39 ●●●●● patch | view | raw | blame | history
plugins/managesieve/tests/parset_subaddress.phpt 38 ●●●●● patch | view | raw | blame | history
plugins/newmail_notifier/newmail_notifier.php 2 ●●● patch | view | raw | blame | history
program/include/rcube_imap.php 2 ●●● patch | view | raw | blame | history
program/js/app.js 2 ●●● patch | view | raw | blame | history
program/js/googiespell.js 2 ●●●●● patch | view | raw | blame | history
CHANGELOG
@@ -1,6 +1,7 @@
CHANGELOG Roundcube Webmail
===========================
- Fix handling of HTML form elements in messages (#1485137)
- Fix regression in setting recipient to self when replying to a Sent message (#1487074)
- Fix listing of folders in hidden namespaces (#1486796)
plugins/managesieve/Changelog
@@ -1,4 +1,16 @@
- Fixed setting test type to :is when none is specified
* version 5.0-rc1 [2011-11-17]
-----------------------------------------------------------
- Fixed sorting of scripts, scripts including aware of the sort order
- Fixed import of rules with unsupported tests
- Added 'address' and 'envelope' tests support
- Added 'body' extension support (RFC5173)
- Added 'subaddress' extension support (RFC5233)
- Added comparators support
- Changed Sender/Recipient labels to From/To
- Fixed importing rule names from Ingo
- Fixed handling of extensions disabled in config
* version 5.0-beta [2011-10-17]
-----------------------------------------------------------
plugins/managesieve/lib/rcube_sieve.php
@@ -44,7 +44,6 @@
    public $script;                 // rcube_sieve_script object
    public $current;                // name of currently loaded script
    private $disabled;              // array of disabled extensions
    private $exts;                  // array of supported extensions
@@ -89,7 +88,17 @@
        }
        $this->exts     = $this->get_extensions();
        $this->disabled = $disabled;
        // disable features by config
        if (!empty($disabled)) {
            // we're working on lower-cased names
            $disabled = array_map('strtolower', (array) $disabled);
            foreach ($disabled as $ext) {
                if (($idx = array_search($ext, $this->exts)) !== false) {
                    unset($this->exts[$idx]);
                }
            }
        }
    }
    public function __destruct() {
@@ -301,7 +310,7 @@
    private function _parse($txt)
    {
        // parse
        $script = new rcube_sieve_script($txt, $this->disabled, $this->exts);
        $script = new rcube_sieve_script($txt, $this->exts);
        // fix/convert to Roundcube format
        if (!empty($script->content)) {
plugins/managesieve/lib/rcube_sieve_script.php
@@ -29,9 +29,9 @@
    private $vars = array();        // "global" variables
    private $prefix = '';           // script header (comments)
    private $capabilities = array(); // Sieve extensions supported by server
    private $supported = array(     // Sieve extensions supported by class
        'fileinto',                 // RFC3028
        'fileinto',                 // RFC5228
        'envelope',                 // RFC5228
        'reject',                   // RFC5429
        'ereject',                  // RFC5429
        'copy',                     // RFC3894
@@ -42,29 +42,29 @@
        'imap4flags',               // RFC5232
        'include',                  // draft-ietf-sieve-include-12
        'variables',                // RFC5229
        // TODO: body, notify
        'body',                     // RFC5173
        'subaddress',               // RFC5233
        // @TODO: enotify/notify, spamtest+virustest, mailbox, date
    );
    /**
     * Object constructor
     *
     * @param  string  Script's text content
     * @param  array   List of disabled extensions
     * @param  array   List of capabilities supported by server
     */
    public function __construct($script, $disabled=array(), $capabilities=array())
    public function __construct($script, $capabilities=array())
    {
        if (!empty($disabled)) {
            // we're working on lower-cased names
            $disabled = array_map('strtolower', (array) $disabled);
            foreach ($disabled as $ext) {
                if (($idx = array_search($ext, $this->supported)) !== false) {
        $capabilities = array_map('strtolower', (array) $capabilities);
        // disable features by server capabilities
        if (!empty($capabilities)) {
            foreach ($this->supported as $idx => $ext) {
                if (!in_array($ext, $capabilities)) {
                    unset($this->supported[$idx]);
                }
            }
        }
        $this->capabilities = array_map('strtolower', (array) $capabilities);
        // Parse text content of the script
        $this->_parse_text($script);
@@ -182,7 +182,7 @@
        $idx    = 0;
        if (!empty($this->vars)) {
            if (in_array('variables', (array)$this->capabilities)) {
            if (in_array('variables', (array)$this->supported)) {
                $has_vars = true;
                array_push($exts, 'variables');
            }
@@ -222,33 +222,95 @@
                        $tests[$i] .= ($test['not'] ? 'not ' : '');
                        $tests[$i] .= 'size :' . ($test['type']=='under' ? 'under ' : 'over ') . $test['arg'];
                        break;
                    case 'true':
                        $tests[$i] .= ($test['not'] ? 'false' : 'true');
                        break;
                    case 'exists':
                        $tests[$i] .= ($test['not'] ? 'not ' : '');
                        $tests[$i] .= 'exists ' . self::escape_string($test['arg']);
                        break;
                    case 'header':
                        $tests[$i] .= ($test['not'] ? 'not ' : '');
                        $tests[$i] .= 'header';
                        // relational operator + comparator
                        if (preg_match('/^(value|count)-([gteqnl]{2})/', $test['type'], $m)) {
                            array_push($exts, 'relational');
                            array_push($exts, 'comparator-i;ascii-numeric');
                        if (!empty($test['type'])) {
                            // relational operator + comparator
                            if (preg_match('/^(value|count)-([gteqnl]{2})/', $test['type'], $m)) {
                                array_push($exts, 'relational');
                                array_push($exts, 'comparator-i;ascii-numeric');
                            $tests[$i] .= 'header :' . $m[1] . ' "' . $m[2] . '" :comparator "i;ascii-numeric"';
                        }
                        else {
                            if ($test['type'] == 'regex') {
                                array_push($exts, 'regex');
                                $tests[$i] .= ' :' . $m[1] . ' "' . $m[2] . '" :comparator "i;ascii-numeric"';
                            }
                            else {
                                $this->add_comparator($test, $tests[$i], $exts);
                            $tests[$i] .= 'header :' . $test['type'];
                                if ($test['type'] == 'regex') {
                                    array_push($exts, 'regex');
                                }
                                $tests[$i] .= ' :' . $test['type'];
                            }
                        }
                        $tests[$i] .= ' ' . self::escape_string($test['arg1']);
                        $tests[$i] .= ' ' . self::escape_string($test['arg2']);
                        break;
                    case 'address':
                    case 'envelope':
                        if ($test['test'] == 'envelope') {
                            array_push($exts, 'envelope');
                        }
                        $tests[$i] .= ($test['not'] ? 'not ' : '');
                        $tests[$i] .= $test['test'];
                        if (!empty($test['part'])) {
                            $tests[$i] .= ' :' . $test['part'];
                            if ($test['part'] == 'user' || $test['part'] == 'detail') {
                                array_push($exts, 'subaddress');
                            }
                        }
                        $this->add_comparator($test, $tests[$i], $exts);
                        if (!empty($test['type'])) {
                            if ($test['type'] == 'regex') {
                                array_push($exts, 'regex');
                            }
                            $tests[$i] .= ' :' . $test['type'];
                        }
                        $tests[$i] .= ' ' . self::escape_string($test['arg1']);
                        $tests[$i] .= ' ' . self::escape_string($test['arg2']);
                        break;
                    case 'body':
                        array_push($exts, 'body');
                        $tests[$i] .= ($test['not'] ? 'not ' : '') . 'body';
                        $this->add_comparator($test, $tests[$i], $exts);
                        if (!empty($test['part'])) {
                            $tests[$i] .= ' :' . $test['part'];
                            if (!empty($test['content']) && $test['part'] == 'content') {
                                $tests[$i] .= ' ' . self::escape_string($test['content']);
                            }
                        }
                        if (!empty($test['type'])) {
                            if ($test['type'] == 'regex') {
                                array_push($exts, 'regex');
                            }
                            $tests[$i] .= ' :' . $test['type'];
                        }
                        $tests[$i] .= ' ' . self::escape_string($test['arg']);
                        break;
                    }
                    $i++;
@@ -311,7 +373,7 @@
                    case 'addflag':
                    case 'setflag':
                    case 'removeflag':
                        if (is_array($this->capabilities) && in_array('imap4flags', $this->capabilities))
                        if (in_array('imap4flags', $this->supported))
                            array_push($exts, 'imap4flags');
                        else
                            array_push($exts, 'imapflags');
@@ -448,7 +510,7 @@
            // handle script header
            if (empty($options['prefix'])) {
                $options['prefix'] = true;
                if ($prefix && strpos($prefix, 'Generated by Ingo')) {
                if ($prefix && strpos($prefix, 'horde.org/ingo')) {
                    $options['format'] = 'INGO';
                }
            }
@@ -559,7 +621,7 @@
                $header = array('test' => 'header', 'not' => $not, 'arg1' => '', 'arg2' => '');
                for ($i=0, $len=count($tokens); $i<$len; $i++) {
                    if (!is_array($tokens[$i]) && preg_match('/^:comparator$/i', $tokens[$i])) {
                        $i++;
                        $header['comparator'] = $tokens[++$i];
                    }
                    else if (!is_array($tokens[$i]) && preg_match('/^:(count|value)$/i', $tokens[$i])) {
                        $header['type'] = strtolower(substr($tokens[$i], 1)) . '-' . $tokens[++$i];
@@ -570,6 +632,52 @@
                    else {
                        $header['arg1'] = $header['arg2'];
                        $header['arg2'] = $tokens[$i];
                    }
                }
                $tests[] = $header;
                break;
            case 'address':
            case 'envelope':
                $header = array('test' => $token, 'not' => $not, 'arg1' => '', 'arg2' => '');
                for ($i=0, $len=count($tokens); $i<$len; $i++) {
                    if (!is_array($tokens[$i]) && preg_match('/^:comparator$/i', $tokens[$i])) {
                        $header['comparator'] = $tokens[++$i];
                    }
                    else if (!is_array($tokens[$i]) && preg_match('/^:(is|contains|matches|regex)$/i', $tokens[$i])) {
                        $header['type'] = strtolower(substr($tokens[$i], 1));
                    }
                    else if (!is_array($tokens[$i]) && preg_match('/^:(localpart|domain|all|user|detail)$/i', $tokens[$i])) {
                        $header['part'] = strtolower(substr($tokens[$i], 1));
                    }
                    else {
                        $header['arg1'] = $header['arg2'];
                        $header['arg2'] = $tokens[$i];
                    }
                }
                $tests[] = $header;
                break;
            case 'body':
                $header = array('test' => 'body', 'not' => $not, 'arg' => '');
                for ($i=0, $len=count($tokens); $i<$len; $i++) {
                    if (!is_array($tokens[$i]) && preg_match('/^:comparator$/i', $tokens[$i])) {
                        $header['comparator'] = $tokens[++$i];
                    }
                    else if (!is_array($tokens[$i]) && preg_match('/^:(is|contains|matches|regex)$/i', $tokens[$i])) {
                        $header['type'] = strtolower(substr($tokens[$i], 1));
                    }
                    else if (!is_array($tokens[$i]) && preg_match('/^:(raw|content|text)$/i', $tokens[$i])) {
                        $header['part'] = strtolower(substr($tokens[$i], 1));
                        if ($header['part'] == 'content') {
                            $header['content'] = $tokens[++$i];
                        }
                    }
                    else {
                        $header['arg'] = $tokens[$i];
                    }
                }
@@ -597,9 +705,7 @@
        }
        // ...and actions block
        if ($tests) {
            $actions = $this->_parse_actions($content);
        }
        $actions = $this->_parse_actions($content);
        if ($tests && $actions) {
            $result = array(
@@ -747,6 +853,29 @@
    }
    /**
     *
     */
    private function add_comparator($test, &$out, &$exts)
    {
        if (empty($test['comparator'])) {
            return;
        }
        if ($test['comparator'] == 'i;ascii-numeric') {
            array_push($exts, 'relational');
            array_push($exts, 'comparator-i;ascii-numeric');
        }
        else if (!in_array($test['comparator'], array('i;octet', 'i;ascii-casemap'))) {
            array_push($exts, 'comparator-' . $test['comparator']);
        }
        // skip default comparator
        if ($test['comparator'] != 'i;ascii-casemap') {
            $out .= ' :comparator ' . self::escape_string($test['comparator']);
        }
    }
    /**
     * Escape special chars into quoted string value or multi-line string
     * or list of strings
     *
plugins/managesieve/localization/en_US.inc
@@ -82,6 +82,25 @@
$labels['usedata'] = 'Use following data in the filter:';
$labels['nextstep'] = 'Next Step';
$labels['...'] = '...';
$labels['advancedopts'] = 'Advanced options';
$labels['body'] = 'Body';
$labels['address'] = 'address';
$labels['envelope'] = 'envelope';
$labels['modifier'] = 'modifier:';
$labels['text'] = 'text';
$labels['undecoded'] = 'undecoded (raw)';
$labels['contenttype'] = 'content type';
$labels['modtype'] = 'type:';
$labels['allparts'] = 'all';
$labels['domain'] = 'domain';
$labels['localpart'] = 'local part';
$labels['user'] = 'user';
$labels['detail'] = 'detail';
$labels['comparator'] = 'comparator:';
$labels['default'] = 'default';
$labels['octet'] = 'strict (octet)';
$labels['asciicasemap'] = 'case insensitive (ascii-casemap)';
$labels['asciinumeric'] = 'numeric (ascii-numeric)';
$messages = array();
$messages['filterunknownerror'] = 'Unknown server error.';
plugins/managesieve/localization/pl_PL.inc
@@ -81,6 +81,25 @@
$labels['usedata'] = 'Użyj nastÄ™pujÄ…cych danych do utworzenia filtra:';
$labels['nextstep'] = 'NastÄ™pny krok';
$labels['...'] = '...';
$labels['advancedopts'] = 'Zaawansowane opcje';
$labels['body'] = 'Treść';
$labels['address'] = 'adres';
$labels['envelope'] = 'koperta (envelope)';
$labels['modifier'] = 'modyfikator:';
$labels['text'] = 'tekst';
$labels['undecoded'] = 'nie (raw)';
$labels['contenttype'] = 'typ części (content type)';
$labels['modtype'] = 'typ:';
$labels['allparts'] = 'wszystkie';
$labels['domain'] = 'domena';
$labels['localpart'] = 'część lokalna';
$labels['user'] = 'użytkownik';
$labels['detail'] = 'detal';
$labels['comparator'] = 'komparator:';
$labels['default'] = 'domyÅ›lny';
$labels['octet'] = 'dokÅ‚adny (octet)';
$labels['asciicasemap'] = 'nierozróżniajÄ…cy wielkoÅ›ci liter (ascii-casemap)';
$labels['asciinumeric'] = 'numeryczny (ascii-numeric)';
$messages = array();
$messages['filterunknownerror'] = 'Nieznany bÅ‚Ä…d serwera.';
plugins/managesieve/managesieve.js
@@ -542,19 +542,28 @@
    size = document.getElementById('rule_size' + id),
    op = document.getElementById('rule_op' + id),
    target = document.getElementById('rule_target' + id),
    header = document.getElementById('custom_header' + id);
    header = document.getElementById('custom_header' + id),
    mod = document.getElementById('rule_mod' + id),
    trans = document.getElementById('rule_trans' + id),
    comp = document.getElementById('rule_comp' + id);
  if (obj.value == 'size') {
    size.style.display = 'inline';
    op.style.display = 'none';
    target.style.display = 'none';
    header.style.display = 'none';
    mod.style.display = 'none';
    trans.style.display = 'none';
    comp.style.display = 'none';
  }
  else {
    header.style.display = obj.value != '...' ? 'none' : 'inline';
    size.style.display = 'none';
    op.style.display = 'inline';
    comp.style.display = '';
    rule_op_select(id);
    mod.style.display = obj.value == 'body' ? 'none' : 'block';
    trans.style.display = obj.value == 'body' ? 'block' : 'none';
  }
  obj.style.width = obj.value == '...' ? '40px' : '';
@@ -568,11 +577,41 @@
  target.style.display = obj.value == 'exists' || obj.value == 'notexists' ? 'none' : 'inline';
};
function rule_trans_select(id)
{
  var obj = document.getElementById('rule_trans_op' + id),
    target = document.getElementById('rule_trans_type' + id);
  target.style.display = obj.value != 'content' ? 'none' : 'inline';
};
function rule_mod_select(id)
{
  var obj = document.getElementById('rule_mod_op' + id),
    target = document.getElementById('rule_mod_type' + id);
  target.style.display = obj.value != 'address' && obj.value != 'envelope' ? 'none' : 'inline';
};
function rule_join_radio(value)
{
  $('#rules').css('display', value == 'any' ? 'none' : 'block');
};
function rule_adv_switch(id, elem)
{
  var elem = $(elem), enabled = elem.hasClass('hide'), adv = $('#rule_advanced'+id);
  if (enabled) {
    adv.hide();
    elem.removeClass('hide').addClass('show');
  }
  else {
    adv.show();
    elem.removeClass('show').addClass('hide');
  }
}
function action_type_select(id)
{
  var obj = document.getElementById('action_type' + id),
plugins/managesieve/managesieve.php
@@ -45,9 +45,23 @@
    private $list;
    private $active = array();
    private $headers = array(
        'subject'   => 'Subject',
        'sender'    => 'From',
        'recipient' => 'To',
        'subject' => 'Subject',
        'from'    => 'From',
        'to'      => 'To',
    );
    private $addr_headers = array(
        // Required
        "from", "to", "cc", "bcc", "sender", "resent-from", "resent-to",
        // Additional (RFC 822 / RFC 2822)
        "reply-to", "resent-reply-to", "resent-sender", "resent-cc", "resent-bcc",
        // Non-standard (RFC 2076, draft-palme-mailext-headers-08.txt)
        "for-approval", "for-handling", "for-comment", "apparently-to", "errors-to",
        "delivered-to", "return-receipt-to", "x-admin", "read-receipt-to",
        "x-confirm-reading-to", "return-receipt-requested",
        "registered-mail-reply-requested-by", "mail-followup-to", "mail-reply-to",
        "abuse-reports-to", "x-complaints-to", "x-report-abuse-to",
        // Undocumented
        "x-beenthere",
    );
    const VERSION = '5.0';
@@ -588,6 +602,11 @@
            $sizeitems      = get_input_value('_rule_size_item', RCUBE_INPUT_POST);
            $sizetargets    = get_input_value('_rule_size_target', RCUBE_INPUT_POST);
            $targets        = get_input_value('_rule_target', RCUBE_INPUT_POST, true);
            $mods           = get_input_value('_rule_mod', RCUBE_INPUT_POST);
            $mod_types      = get_input_value('_rule_mod_type', RCUBE_INPUT_POST);
            $body_trans     = get_input_value('_rule_trans', RCUBE_INPUT_POST);
            $body_types     = get_input_value('_rule_trans_type', RCUBE_INPUT_POST, true);
            $comparators    = get_input_value('_rule_comp', RCUBE_INPUT_POST);
            $act_types      = get_input_value('_action_type', RCUBE_INPUT_POST, true);
            $mailboxes      = get_input_value('_action_mailbox', RCUBE_INPUT_POST, true);
            $act_targets    = get_input_value('_action_target', RCUBE_INPUT_POST, true);
@@ -625,23 +644,101 @@
            }
            else {
                foreach ($headers as $idx => $header) {
                    $header = $this->strip_value($header);
                    $target = $this->strip_value($targets[$idx], true);
                    $op     = $this->strip_value($ops[$idx]);
                    $header     = $this->strip_value($header);
                    $target     = $this->strip_value($targets[$idx], true);
                    $operator   = $this->strip_value($ops[$idx]);
                    $comparator = $this->strip_value($comparators[$idx]);
                    // normal header
                    if (in_array($header, $this->headers)) {
                        if (preg_match('/^not/', $op))
                    if ($header == 'size') {
                        $sizeop     = $this->strip_value($sizeops[$idx]);
                        $sizeitem   = $this->strip_value($items[$idx]);
                        $sizetarget = $this->strip_value($sizetargets[$idx]);
                        $this->form['tests'][$i]['test'] = 'size';
                        $this->form['tests'][$i]['type'] = $sizeop;
                        $this->form['tests'][$i]['arg']  = $sizetarget;
                        if ($sizetarget == '')
                            $this->errors['tests'][$i]['sizetarget'] = $this->gettext('cannotbeempty');
                        else if (!preg_match('/^[0-9]+(K|M|G)?$/i', $sizetarget.$sizeitem, $m)) {
                            $this->errors['tests'][$i]['sizetarget'] = $this->gettext('forbiddenchars');
                            $this->form['tests'][$i]['item'] = $sizeitem;
                        }
                        else
                            $this->form['tests'][$i]['arg'] .= $m[1];
                    }
                    else if ($header == 'body') {
                        $trans      = $this->strip_value($body_trans[$idx]);
                        $trans_type = $this->strip_value($body_types[$idx], true);
                        if (preg_match('/^not/', $operator))
                            $this->form['tests'][$i]['not'] = true;
                        $type = preg_replace('/^not/', '', $op);
                        $type = preg_replace('/^not/', '', $operator);
                        if ($type == 'exists') {
                            $this->errors['tests'][$i]['op'] = true;
                        }
                        $this->form['tests'][$i]['test'] = 'body';
                        $this->form['tests'][$i]['type'] = $type;
                        $this->form['tests'][$i]['arg']  = $target;
                        if ($target == '' && $type != 'exists')
                            $this->errors['tests'][$i]['target'] = $this->gettext('cannotbeempty');
                        else if (preg_match('/^(value|count)-/', $type) && !preg_match('/[0-9]+/', $target))
                            $this->errors['tests'][$i]['target'] = $this->gettext('forbiddenchars');
                        $this->form['tests'][$i]['part'] = $trans;
                        if ($trans == 'content') {
                            $this->form['tests'][$i]['content'] = $trans_type;
                        }
                    }
                    else {
                        $cust_header = $headers = $this->strip_value($cust_headers[$idx]);
                        $mod      = $this->strip_value($mods[$idx]);
                        $mod_type = $this->strip_value($mod_types[$idx]);
                        if (preg_match('/^not/', $operator))
                            $this->form['tests'][$i]['not'] = true;
                        $type = preg_replace('/^not/', '', $operator);
                        if ($header == '...') {
                            $headers = preg_split('/[\s,]+/', $cust_header, -1, PREG_SPLIT_NO_EMPTY);
                            if (!count($headers))
                                $this->errors['tests'][$i]['header'] = $this->gettext('cannotbeempty');
                            else {
                                foreach ($headers as $hr)
                                    if (!preg_match('/^[a-z0-9-]+$/i', $hr))
                                        $this->errors['tests'][$i]['header'] = $this->gettext('forbiddenchars');
                            }
                            if (empty($this->errors['tests'][$i]['header']))
                                $cust_header = (is_array($headers) && count($headers) == 1) ? $headers[0] : $headers;
                        }
                        if ($type == 'exists') {
                            $this->form['tests'][$i]['test'] = 'exists';
                            $this->form['tests'][$i]['arg'] = $header;
                            $this->form['tests'][$i]['arg'] = $header == '...' ? $cust_header : $header;
                        }
                        else {
                            $test   = 'header';
                            $header = $header == '...' ? $cust_header : $header;
                            if ($mod == 'address' || $mod == 'envelope') {
                                $found = false;
                                if (empty($this->errors['tests'][$i]['header'])) {
                                    foreach ((array)$header as $hdr) {
                                        if (!in_array(strtolower(trim($hdr)), $this->addr_headers))
                                            $found = true;
                                    }
                                }
                                if (!$found)
                                    $test = $mod;
                            }
                            $this->form['tests'][$i]['type'] = $type;
                            $this->form['tests'][$i]['test'] = 'header';
                            $this->form['tests'][$i]['test'] = $test;
                            $this->form['tests'][$i]['arg1'] = $header;
                            $this->form['tests'][$i]['arg2'] = $target;
@@ -649,65 +746,20 @@
                                $this->errors['tests'][$i]['target'] = $this->gettext('cannotbeempty');
                            else if (preg_match('/^(value|count)-/', $type) && !preg_match('/[0-9]+/', $target))
                                $this->errors['tests'][$i]['target'] = $this->gettext('forbiddenchars');
                            if ($mod) {
                                $this->form['tests'][$i]['part'] = $mod_type;
                            }
                        }
                    }
                    else
                        switch ($header) {
                        case 'size':
                            $sizeop     = $this->strip_value($sizeops[$idx]);
                            $sizeitem   = $this->strip_value($items[$idx]);
                            $sizetarget = $this->strip_value($sizetargets[$idx]);
                            $this->form['tests'][$i]['test'] = 'size';
                            $this->form['tests'][$i]['type'] = $sizeop;
                            $this->form['tests'][$i]['arg']  = $sizetarget.$sizeitem;
                    if ($header != 'size' && $comparator) {
                        if (preg_match('/^(value|count)/', $this->form['tests'][$i]['type']))
                            $comparator = 'i;ascii-numeric';
                            if ($sizetarget == '')
                                $this->errors['tests'][$i]['sizetarget'] = $this->gettext('cannotbeempty');
                            else if (!preg_match('/^[0-9]+(K|M|G)*$/i', $sizetarget))
                                $this->errors['tests'][$i]['sizetarget'] = $this->gettext('forbiddenchars');
                            break;
                        case '...':
                            $cust_header = $headers = $this->strip_value($cust_headers[$idx]);
                        $this->form['tests'][$i]['comparator'] = $comparator;
                    }
                            if (preg_match('/^not/', $op))
                                $this->form['tests'][$i]['not'] = true;
                            $type = preg_replace('/^not/', '', $op);
                            if ($cust_header == '')
                                $this->errors['tests'][$i]['header'] = $this->gettext('cannotbeempty');
                            else {
                                $headers = preg_split('/[\s,]+/', $cust_header, -1, PREG_SPLIT_NO_EMPTY);
                                if (!count($headers))
                                    $this->errors['tests'][$i]['header'] = $this->gettext('cannotbeempty');
                                else {
                                    foreach ($headers as $hr)
                                        if (!preg_match('/^[a-z0-9-]+$/i', $hr))
                                            $this->errors['tests'][$i]['header'] = $this->gettext('forbiddenchars');
                                }
                            }
                            if (empty($this->errors['tests'][$i]['header']))
                                $cust_header = (is_array($headers) && count($headers) == 1) ? $headers[0] : $headers;
                            if ($type == 'exists') {
                                $this->form['tests'][$i]['test'] = 'exists';
                                $this->form['tests'][$i]['arg']  = $cust_header;
                            }
                            else {
                                $this->form['tests'][$i]['test'] = 'header';
                                $this->form['tests'][$i]['type'] = $type;
                                $this->form['tests'][$i]['arg1'] = $cust_header;
                                $this->form['tests'][$i]['arg2'] = $target;
                                if ($target == '')
                                    $this->errors['tests'][$i]['target'] = $this->gettext('cannotbeempty');
                                else if (preg_match('/^(value|count)-/', $type) && !preg_match('/[0-9]+/', $target))
                                    $this->errors['tests'][$i]['target'] = $this->gettext('forbiddenchars');
                            }
                            break;
                        }
                    $i++;
                }
            }
@@ -1140,43 +1192,52 @@
        $rule     = isset($this->form) ? $this->form['tests'][$id] : $this->script[$fid]['tests'][$id];
        $rows_num = isset($this->form) ? sizeof($this->form['tests']) : sizeof($this->script[$fid]['tests']);
        $out = $div ? '<div class="rulerow" id="rulerow' .$id .'">'."\n" : '';
        $out .= '<table><tr><td class="rowactions">';
        // headers select
        $select_header = new html_select(array('name' => "_header[]", 'id' => 'header'.$id,
            'onchange' => 'rule_header_select(' .$id .')'));
        foreach($this->headers as $name => $val)
            $select_header->add(Q($this->gettext($name)), Q($val));
        if (in_array('body', $this->exts))
            $select_header->add(Q($this->gettext('body')), 'body');
        $select_header->add(Q($this->gettext('size')), 'size');
        $select_header->add(Q($this->gettext('...')), '...');
        // TODO: list arguments
        $aout = '';
        if ((isset($rule['test']) && $rule['test'] == 'header')
            && !is_array($rule['arg1']) && in_array($rule['arg1'], $this->headers))
            $out .= $select_header->show($rule['arg1']);
        if ((isset($rule['test']) && in_array($rule['test'], array('header', 'address', 'envelope')))
            && !is_array($rule['arg1']) && in_array($rule['arg1'], $this->headers)
        ) {
            $aout .= $select_header->show($rule['arg1']);
        }
        else if ((isset($rule['test']) && $rule['test'] == 'exists')
            && !is_array($rule['arg']) && in_array($rule['arg'], $this->headers))
            $out .= $select_header->show($rule['arg']);
            && !is_array($rule['arg']) && in_array($rule['arg'], $this->headers)
        ) {
            $aout .= $select_header->show($rule['arg']);
        }
        else if (isset($rule['test']) && $rule['test'] == 'size')
            $out .= $select_header->show('size');
            $aout .= $select_header->show('size');
        else if (isset($rule['test']) && $rule['test'] == 'body')
            $aout .= $select_header->show('body');
        else if (isset($rule['test']) && $rule['test'] != 'true')
            $out .= $select_header->show('...');
            $aout .= $select_header->show('...');
        else
            $out .= $select_header->show();
            $aout .= $select_header->show();
        $out .= '</td><td class="rowtargets">';
        if (isset($rule['test']) && in_array($rule['test'], array('header', 'address', 'envelope'))) {
            if (is_array($rule['arg1']))
                $custom = implode(', ', $rule['arg1']);
            else if (!in_array($rule['arg1'], $this->headers))
                $custom = $rule['arg1'];
        }
        else if (isset($rule['test']) && $rule['test'] == 'exists') {
            if (is_array($rule['arg']))
                $custom = implode(', ', $rule['arg']);
            else if (!in_array($rule['arg'], $this->headers))
                $custom = $rule['arg'];
        }
        if ((isset($rule['test']) && $rule['test'] == 'header')
            && (is_array($rule['arg1']) || !in_array($rule['arg1'], $this->headers)))
            $custom = is_array($rule['arg1']) ? implode(', ', $rule['arg1']) : $rule['arg1'];
        else if ((isset($rule['test']) && $rule['test'] == 'exists')
            && (is_array($rule['arg']) || !in_array($rule['arg'], $this->headers)))
            $custom = is_array($rule['arg']) ? implode(', ', $rule['arg']) : $rule['arg'];
        $out .= '<div id="custom_header' .$id. '" style="display:' .(isset($custom) ? 'inline' : 'none'). '">
        $tout = '<div id="custom_header' .$id. '" style="display:' .(isset($custom) ? 'inline' : 'none'). '">
            <input type="text" name="_custom_header[]" id="custom_header_i'.$id.'" '
            . $this->error_class($id, 'test', 'header', 'custom_header_i')
            .' value="' .Q($custom). '" size="15" />&nbsp;</div>' . "\n";
@@ -1215,33 +1276,43 @@
        // target input (TODO: lists)
        if ($rule['test'] == 'header') {
            $out .= $select_op->show(($rule['not'] ? 'not' : '').$rule['type']);
        if (in_array($rule['test'], array('header', 'address', 'envelope'))) {
            $test   = ($rule['not'] ? 'not' : '').($rule['type'] ? $rule['type'] : 'is');
            $target = $rule['arg2'];
        }
        else if ($rule['test'] == 'body') {
            $test   = ($rule['not'] ? 'not' : '').($rule['type'] ? $rule['type'] : 'is');
            $target = $rule['arg'];
        }
        else if ($rule['test'] == 'size') {
            $out .= $select_op->show();
            if (preg_match('/^([0-9]+)(K|M|G)*$/', $rule['arg'], $matches)) {
            $test   = '';
            $target = '';
            if (preg_match('/^([0-9]+)(K|M|G)?$/', $rule['arg'], $matches)) {
                $sizetarget = $matches[1];
                $sizeitem = $matches[2];
            }
            else {
                $sizetarget = $rule['arg'];
                $sizeitem = $rule['item'];
            }
        }
        else {
            $out .= $select_op->show(($rule['not'] ? 'not' : '').$rule['test']);
            $target = '';
            $test   = ($rule['not'] ? 'not' : '').$rule['test'];
            $target =  '';
        }
        $out .= '<input type="text" name="_rule_target[]" id="rule_target' .$id. '"
        $tout .= $select_op->show($test);
        $tout .= '<input type="text" name="_rule_target[]" id="rule_target' .$id. '"
            value="' .Q($target). '" size="20" ' . $this->error_class($id, 'test', 'target', 'rule_target')
            . ' style="display:' . ($rule['test']!='size' && $rule['test'] != 'exists' ? 'inline' : 'none') . '" />'."\n";
        $select_size_op = new html_select(array('name' => "_rule_size_op[]", 'id' => 'rule_size_op'.$id));
        $select_size_op->add(Q($this->gettext('filterunder')), 'under');
        $select_size_op->add(Q($this->gettext('filterover')), 'over');
        $select_size_op->add(Q($this->gettext('filterunder')), 'under');
        $out .= '<div id="rule_size' .$id. '" style="display:' . ($rule['test']=='size' ? 'inline' : 'none') .'">';
        $out .= $select_size_op->show($rule['test']=='size' ? $rule['type'] : '');
        $out .= '<input type="text" name="_rule_size_target[]" id="rule_size_i'.$id.'" value="'.$sizetarget.'" size="10" '
        $tout .= '<div id="rule_size' .$id. '" style="display:' . ($rule['test']=='size' ? 'inline' : 'none') .'">';
        $tout .= $select_size_op->show($rule['test']=='size' ? $rule['type'] : '');
        $tout .= '<input type="text" name="_rule_size_target[]" id="rule_size_i'.$id.'" value="'.$sizetarget.'" size="10" '
            . $this->error_class($id, 'test', 'sizetarget', 'rule_size_i') .' />
            <input type="radio" name="_rule_size_item['.$id.']" value=""'
                . (!$sizeitem ? ' checked="checked"' : '') .' class="radio" />'.rcube_label('B').'
@@ -1251,7 +1322,82 @@
                . ($sizeitem=='M' ? ' checked="checked"' : '') .' class="radio" />'.rcube_label('MB').'
            <input type="radio" name="_rule_size_item['.$id.']" value="G"'
                . ($sizeitem=='G' ? ' checked="checked"' : '') .' class="radio" />'.rcube_label('GB');
        $out .= '</div>';
        $tout .= '</div>';
        // Advanced modifiers (address, envelope)
        $select_mod = new html_select(array('name' => "_rule_mod[]", 'id' => 'rule_mod_op'.$id,
            'onchange' => 'rule_mod_select(' .$id .')'));
        $select_mod->add(Q($this->gettext('none')), '');
        $select_mod->add(Q($this->gettext('address')), 'address');
        if (in_array('envelope', $this->exts))
            $select_mod->add(Q($this->gettext('envelope')), 'envelope');
        $select_type = new html_select(array('name' => "_rule_mod_type[]", 'id' => 'rule_mod_type'.$id));
        $select_type->add(Q($this->gettext('allparts')), 'all');
        $select_type->add(Q($this->gettext('domain')), 'domain');
        $select_type->add(Q($this->gettext('localpart')), 'localpart');
        if (in_array('subaddress', $this->exts)) {
            $select_type->add(Q($this->gettext('user')), 'user');
            $select_type->add(Q($this->gettext('detail')), 'detail');
        }
        $need_mod = $rule['test'] != 'size' && $rule['test'] != 'body';
        $mout = '<div id="rule_mod' .$id. '" class="adv" style="display:' . ($need_mod ? 'block' : 'none') .'">';
        $mout .= ' <span>';
        $mout .= Q($this->gettext('modifier')) . ' ';
        $mout .= $select_mod->show($rule['test']);
        $mout .= '</span>';
        $mout .= ' <span id="rule_mod_type' . $id . '"';
        $mout .= ' style="display:' . (in_array($rule['test'], array('address', 'envelope')) ? 'inline' : 'none') .'">';
        $mout .= Q($this->gettext('modtype')) . ' ';
        $mout .= $select_type->show($rule['part']);
        $mout .= '</span>';
        $mout .= '</div>';
        // Advanced modifiers (body transformations)
        $select_mod = new html_select(array('name' => "_rule_trans[]", 'id' => 'rule_trans_op'.$id,
            'onchange' => 'rule_trans_select(' .$id .')'));
        $select_mod->add(Q($this->gettext('text')), 'text');
        $select_mod->add(Q($this->gettext('undecoded')), 'raw');
        $select_mod->add(Q($this->gettext('contenttype')), 'content');
        $mout .= '<div id="rule_trans' .$id. '" class="adv" style="display:' . ($rule['test'] == 'body' ? 'block' : 'none') .'">';
        $mout .= ' <span>';
        $mout .= Q($this->gettext('modifier')) . ' ';
        $mout .= $select_mod->show($rule['part']);
        $mout .= '<input type="text" name="_rule_trans_type[]" id="rule_trans_type'.$id
            . '" value="'.(is_array($rule['content']) ? implode(',', $rule['content']) : $rule['content'])
            .'" size="20" style="display:' . ($rule['part'] == 'content' ? 'inline' : 'none') .'"'
            . $this->error_class($id, 'test', 'part', 'rule_trans_type') .' />';
        $mout .= '</span>';
        $mout .= '</div>';
        // Advanced modifiers (body transformations)
        $select_comp = new html_select(array('name' => "_rule_comp[]", 'id' => 'rule_comp_op'.$id));
        $select_comp->add(Q($this->gettext('default')), '');
        $select_comp->add(Q($this->gettext('octet')), 'i;octet');
        $select_comp->add(Q($this->gettext('asciicasemap')), 'i;ascii-casemap');
        if (in_array('comparator-i;ascii-numeric', $this->exts)) {
            $select_comp->add(Q($this->gettext('asciinumeric')), 'i;ascii-numeric');
        }
        $mout .= '<div id="rule_comp' .$id. '" class="adv" style="display:' . ($rule['test'] != 'size' ? 'block' : 'none') .'">';
        $mout .= ' <span>';
        $mout .= Q($this->gettext('comparator')) . ' ';
        $mout .= $select_comp->show($rule['comparator']);
        $mout .= '</span>';
        $mout .= '</div>';
        // Build output table
        $out = $div ? '<div class="rulerow" id="rulerow' .$id .'">'."\n" : '';
        $out .= '<table><tr>';
        $out .= '<td class="advbutton">';
        $out .= '<a href="#" id="ruleadv' . $id .'" title="'. Q($this->gettext('advancedopts')). '"
            onclick="rule_adv_switch(' . $id .', this)" class="show">&nbsp;&nbsp;</a>';
        $out .= '</td>';
        $out .= '<td class="rowactions">' . $aout . '</td>';
        $out .= '<td class="rowtargets">' . $tout . "\n";
        $out .= '<div id="rule_advanced' .$id. '" style="display:none">' . $mout . '</div>';
        $out .= '</td>';
        // add/del buttons
@@ -1260,7 +1406,8 @@
            onclick="rcmail.managesieve_ruleadd(' . $id .')" class="button add"></a>';
        $out .= '<a href="#" id="ruledel' . $id .'" title="'. Q($this->gettext('del')). '"
            onclick="rcmail.managesieve_ruledel(' . $id .')" class="button del' . ($rows_num<2 ? ' disabled' : '') .'"></a>';
        $out .= '</td></tr></table>';
        $out .= '</td>';
        $out .= '</tr></table>';
        $out .= $div ? "</div>\n" : '';
plugins/managesieve/package.xml
New file
@@ -0,0 +1,100 @@
<?xml version="1.0" encoding="UTF-8"?>
<package xmlns="http://pear.php.net/dtd/package-2.0" xmlns:tasks="http://pear.php.net/dtd/tasks-1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" packagerversion="1.9.0" version="2.0" xsi:schemaLocation="http://pear.php.net/dtd/tasks-1.0
    http://pear.php.net/dtd/tasks-1.0.xsd
    http://pear.php.net/dtd/package-2.0
    http://pear.php.net/dtd/package-2.0.xsd">
    <name>managesieve</name>
    <channel>pear.roundcube.net</channel>
    <summary>Sieve filters manager for Roundcube</summary>
    <description>
        Adds a possibility to manage Sieve scripts (incoming mail filters).
        It's clickable interface which operates on text scripts and communicates
        with server using managesieve protocol. Adds Filters tab in Settings.
    </description>
    <lead>
        <name>Aleksander Machniak</name>
        <user>alec</user>
        <email>alec@alec.pl</email>
        <active>yes</active>
    </lead>
    <date>2011-11-17</date>
    <version>
        <release>5.0</release>
        <api>5.0</api>
    </version>
    <stability>
        <release>stable</release>
        <api>stable</api>
    </stability>
    <license uri="http://www.gnu.org/licenses/gpl-2.0.html">GNU GPLv2</license>
    <notes>-</notes>
    <contents>
        <dir baseinstalldir="/" name="/">
            <file name="managesieve.php" role="php">
                <tasks:replace from="@name@" to="name" type="package-info"/>
                <tasks:replace from="@package_version@" to="version" type="package-info"/>
            </file>
            <file name="managesieve.js" role="data">
                <tasks:replace from="@name@" to="name" type="package-info"/>
                <tasks:replace from="@package_version@" to="version" type="package-info"/>
            </file>
            <file name="localization/bg_BG.inc" role="data"></file>
            <file name="localization/cs_CZ.inc" role="data"></file>
            <file name="localization/de_CH.inc" role="data"></file>
            <file name="localization/de_DE.inc" role="data"></file>
            <file name="localization/el_GR.inc" role="data"></file>
            <file name="localization/en_GB.inc" role="data"></file>
            <file name="localization/en_US.inc" role="data"></file>
            <file name="localization/es_AR.inc" role="data"></file>
            <file name="localization/es_ES.inc" role="data"></file>
            <file name="localization/et_EE.inc" role="data"></file>
            <file name="localization/fi_FI.inc" role="data"></file>
            <file name="localization/fr_FR.inc" role="data"></file>
            <file name="localization/gl_ES.inc" role="data"></file>
            <file name="localization/hr_HR.inc" role="data"></file>
            <file name="localization/hu_HU.inc" role="data"></file>
            <file name="localization/it_IT.inc" role="data"></file>
            <file name="localization/ja_JP.inc" role="data"></file>
            <file name="localization/lv_LV.inc" role="data"></file>
            <file name="localization/nb_NO.inc" role="data"></file>
            <file name="localization/nl_NL.inc" role="data"></file>
            <file name="localization/pl_PL.inc" role="data"></file>
            <file name="localization/pt_BR.inc" role="data"></file>
            <file name="localization/pt_PT.inc" role="data"></file>
            <file name="localization/ru_RU.inc" role="data"></file>
            <file name="localization/sk_SK.inc" role="data"></file>
            <file name="localization/sl_SI.inc" role="data"></file>
            <file name="localization/sv_SE.inc" role="data"></file>
            <file name="localization/uk_UA.inc" role="data"></file>
            <file name="localization/zh_CN.inc" role="data"></file>
            <file name="localization/zh_TW.inc" role="data"></file>
            <file name="skins/default/managesieve.css" role="data"></file>
            <file name="skins/default/managesieve_mail.css" role="data"></file>
            <file name="skins/default/templates/filteredit.html" role="data"></file>
            <file name="skins/default/templates/managesieve.html" role="data"></file>
            <file name="skins/default/templates/setedit.html" role="data"></file>
            <file name="skins/default/images/add.png" role="data"></file>
            <file name="skins/default/images/del.png" role="data"></file>
            <file name="skins/default/images/down_small.gif" role="data"></file>
            <file name="skins/default/images/filter.png" role="data"></file>
            <file name="skins/default/images/up_small.gif" role="data"></file>
            <file name="managesieve.php" role="php"></file>
            <file name="lib/rcube_sieve.php" role="php"></file>
            <file name="lib/rcube_sieve_script.php" role="php"></file>
            <file name="lib/Net/Sieve.php" role="php"></file>
            <file name="config.inc.php.dist" role="data"></file>
        </dir>
        <!-- / -->
    </contents>
    <dependencies>
        <required>
            <php>
                <min>5.2.1</min>
            </php>
            <pearinstaller>
                <min>1.7.0</min>
            </pearinstaller>
        </required>
    </dependencies>
    <phprelease/>
</package>
plugins/managesieve/skins/default/images/down_small.gif
plugins/managesieve/skins/default/images/toolbar.png
Binary files differ
plugins/managesieve/skins/default/images/up_small.gif
plugins/managesieve/skins/default/managesieve.css
@@ -149,6 +149,35 @@
  min-width: 620px;
}
td
{
  vertical-align: top;
}
td.advbutton
{
  width: 1%;
}
td.advbutton a
{
  display: block;
  padding-top: 14px;
  height: 6px;
  width: 12px;
  text-decoration: none;
}
td.advbutton a.show
{
  background: url(images/down_small.gif) center no-repeat;
}
td.advbutton a.hide
{
  background: url(images/up_small.gif) center no-repeat;
}
td.rowbuttons
{
  text-align: right;
@@ -169,6 +198,11 @@
  padding-left: 3px;
}
td.rowtargets div.adv
{
  padding-top: 3px;
}
input.disabled, input.disabled:hover
{
  color: #999999;
@@ -183,6 +217,7 @@
input.radio
{
  border: 0;
  margin-top: 0;
}
select.operator_selector
@@ -190,6 +225,7 @@
  width: 200px;
}
td.rowtargets span,
span.label
{
  color: #666666;
@@ -243,7 +279,7 @@
  background: url(images/add.png) no-repeat;
  width: 30px;
  height: 20px;
  margin-right: 4px;
  margin-right: 4px;
  display: inline-block;
}
plugins/managesieve/tests/parser.phpt
@@ -6,7 +6,7 @@
include '../lib/rcube_sieve_script.php';
$txt = '
require ["fileinto","vacation","reject","relational","comparator-i;ascii-numeric","imapflags"];
require ["fileinto","reject","envelope"];
# rule:[spam]
if anyof (header :contains "X-DSPAM-Result" "Spam")
{
@@ -14,26 +14,15 @@
    stop;
}
# rule:[test1]
if anyof (header :contains ["From","To"] "test@domain.tld")
if anyof (header :comparator "i;ascii-casemap" :contains ["From","To"] "test@domain.tld")
{
    discard;
    stop;
}
# rule:[test2]
if anyof (not header :contains ["Subject"] "[test]", header :contains "Subject" "[test2]")
if anyof (not header :comparator "i;octet" :contains ["Subject"] "[test]", header :contains "Subject" "[test2]")
{
    fileinto "test";
    stop;
}
# rule:[test-vacation]
if anyof (header :contains "Subject" "vacation")
{
    vacation :days 1 text:
# test
test test /* test */
test
.
;
    stop;
}
# rule:[comments]
@@ -44,24 +33,40 @@
}
# rule:[reject]
if size :over 5000K {
    reject "Message over 5MB size limit. Please contact me before sending this.";
    reject "Message over 5MB size limit. Please contact me before sending this.";
}
# rule:[redirect]
if header :value "ge" :comparator "i;ascii-numeric"
    ["X-Spam-score"] ["14"] {redirect "test@test.tld";}
# rule:[imapflags]
if header :matches "Subject" "^Test$" {
    setflag "\\\\Seen";
    addflag ["\\\\Answered","\\\\Deleted"];
# rule:[false]
if false # size :over 5000K
{
    stop; /* rule disabled */
}
# rule:[true]
if true
{
    stop;
}
fileinto "Test";
# rule:[address test]
if address :all :is "From" "nagios@domain.tld"
{
    fileinto "domain.tld";
    stop;
}
# rule:[envelope test]
if envelope :domain :is "From" "domain.tld"
{
    fileinto "domain.tld";
    stop;
}
';
$s = new rcube_sieve_script($txt);
echo $s->as_text();
// -------------------------------------------------------------------------------
?>
--EXPECT--
require ["fileinto","vacation","reject","relational","comparator-i;ascii-numeric","imapflags"];
require ["fileinto","reject","envelope"];
# rule:[spam]
if header :contains "X-DSPAM-Result" "Spam"
{
@@ -75,20 +80,9 @@
    stop;
}
# rule:[test2]
if anyof (not header :contains "Subject" "[test]", header :contains "Subject" "[test2]")
if anyof (not header :comparator "i;octet" :contains "Subject" "[test]", header :contains "Subject" "[test2]")
{
    fileinto "test";
    stop;
}
# rule:[test-vacation]
if header :contains "Subject" "vacation"
{
    vacation :days 1 text:
# test
test test /* test */
test
.
;
    stop;
}
# rule:[comments]
@@ -101,14 +95,26 @@
{
    reject "Message over 5MB size limit. Please contact me before sending this.";
}
# rule:[redirect]
if header :value "ge" :comparator "i;ascii-numeric" "X-Spam-score" "14"
# rule:[false]
if false # size :over 5000K
{
    redirect "test@test.tld";
    stop;
}
# rule:[imapflags]
if header :matches "Subject" "^Test$"
# rule:[true]
if true
{
    setflag "\\Seen";
    addflag ["\\Answered","\\Deleted"];
    stop;
}
fileinto "Test";
# rule:[address test]
if address :all :is "From" "nagios@domain.tld"
{
    fileinto "domain.tld";
    stop;
}
# rule:[envelope test]
if envelope :domain :is "From" "domain.tld"
{
    fileinto "domain.tld";
    stop;
}
plugins/managesieve/tests/parser_body.phpt
New file
@@ -0,0 +1,49 @@
--TEST--
Test of Sieve body extension (RFC5173)
--SKIPIF--
--FILE--
<?php
include '../lib/rcube_sieve_script.php';
$txt = '
require ["body","fileinto"];
if body :raw :contains "MAKE MONEY FAST"
{
    stop;
}
if body :content "text" :contains ["missile","coordinates"]
{
    fileinto "secrets";
}
if body :content "audio/mp3" :contains ""
{
    fileinto "jukebox";
}
if body :text :contains "project schedule"
{
    fileinto "project/schedule";
}
';
$s = new rcube_sieve_script($txt);
echo $s->as_text();
?>
--EXPECT--
require ["body","fileinto"];
if body :raw :contains "MAKE MONEY FAST"
{
    stop;
}
if body :content "text" :contains ["missile","coordinates"]
{
    fileinto "secrets";
}
if body :content "audio/mp3" :contains ""
{
    fileinto "jukebox";
}
if body :text :contains "project schedule"
{
    fileinto "project/schedule";
}
plugins/managesieve/tests/parser_imapflags.phpt
New file
@@ -0,0 +1,28 @@
--TEST--
Test of Sieve vacation extension (RFC5232)
--SKIPIF--
--FILE--
<?php
include '../lib/rcube_sieve_script.php';
$txt = '
require ["imapflags"];
# rule:[imapflags]
if header :matches "Subject" "^Test$" {
    setflag "\\\\Seen";
    addflag ["\\\\Answered","\\\\Deleted"];
}
';
$s = new rcube_sieve_script($txt, array('imapflags'));
echo $s->as_text();
?>
--EXPECT--
require ["imapflags"];
# rule:[imapflags]
if header :matches "Subject" "^Test$"
{
    setflag "\\Seen";
    addflag ["\\Answered","\\Deleted"];
}
plugins/managesieve/tests/parser_include.phpt
New file
@@ -0,0 +1,30 @@
--TEST--
Test of Sieve include extension
--SKIPIF--
--FILE--
<?php
include '../lib/rcube_sieve_script.php';
$txt = '
require ["include"];
include "script.sieve";
# rule:[two]
if true
{
    include :optional "second.sieve";
}
';
$s = new rcube_sieve_script($txt, array(), array('variables'));
echo $s->as_text();
?>
--EXPECT--
require ["include"];
include "script.sieve";
# rule:[two]
if true
{
    include :optional "second.sieve";
}
plugins/managesieve/tests/parser_kep14.phpt
@@ -10,7 +10,7 @@
# EDITOR_VERSION 123
';
$s = new rcube_sieve_script($txt, array());
$s = new rcube_sieve_script($txt, array('body'));
echo $s->as_text();
?>
plugins/managesieve/tests/parser_prefix.phpt
New file
@@ -0,0 +1,25 @@
--TEST--
Test of prefix comments handling
--SKIPIF--
--FILE--
<?php
include '../lib/rcube_sieve_script.php';
$txt = '
# this is a comment
# and the second line
require ["variables"];
set "b" "c";
';
$s = new rcube_sieve_script($txt, array(), array('variables'));
echo $s->as_text();
?>
--EXPECT--
# this is a comment
# and the second line
require ["variables"];
set "b" "c";
plugins/managesieve/tests/parser_relational.phpt
New file
@@ -0,0 +1,25 @@
--TEST--
Test of Sieve relational extension (RFC5231)
--SKIPIF--
--FILE--
<?php
include '../lib/rcube_sieve_script.php';
$txt = '
require ["relational","comparator-i;ascii-numeric"];
# rule:[redirect]
if header :value "ge" :comparator "i;ascii-numeric"
    ["X-Spam-score"] ["14"] {redirect "test@test.tld";}
';
$s = new rcube_sieve_script($txt);
echo $s->as_text();
?>
--EXPECT--
require ["relational","comparator-i;ascii-numeric"];
# rule:[redirect]
if header :value "ge" :comparator "i;ascii-numeric" "X-Spam-score" "14"
{
    redirect "test@test.tld";
}
plugins/managesieve/tests/parser_vacation.phpt
New file
@@ -0,0 +1,39 @@
--TEST--
Test of Sieve vacation extension (RFC5230)
--SKIPIF--
--FILE--
<?php
include '../lib/rcube_sieve_script.php';
$txt = '
require ["vacation"];
# rule:[test-vacation]
if anyof (header :contains "Subject" "vacation")
{
    vacation :days 1 text:
# test
test test /* test */
test
.
;
    stop;
}
';
$s = new rcube_sieve_script($txt);
echo $s->as_text();
?>
--EXPECT--
require ["vacation"];
# rule:[test-vacation]
if header :contains "Subject" "vacation"
{
    vacation :days 1 text:
# test
test test /* test */
test
.
;
    stop;
}
plugins/managesieve/tests/parser_variables.phpt
New file
@@ -0,0 +1,39 @@
--TEST--
Test of Sieve variables extension
--SKIPIF--
--FILE--
<?php
include '../lib/rcube_sieve_script.php';
$txt = '
require ["variables"];
set "honorific" "Mr";
set "vacation" text:
Dear ${HONORIFIC} ${last_name},
I am out, please leave a message after the meep.
.
;
set :length "b" "${a}";
set :lower "b" "${a}";
set :upperfirst "b" "${a}";
set :upperfirst :lower "b" "${a}";
set :quotewildcard "b" "Rock*";
';
$s = new rcube_sieve_script($txt, array(), array('variables'));
echo $s->as_text();
?>
--EXPECT--
require ["variables"];
set "honorific" "Mr";
set "vacation" text:
Dear ${HONORIFIC} ${last_name},
I am out, please leave a message after the meep.
.
;
set :length "b" "${a}";
set :lower "b" "${a}";
set :upperfirst "b" "${a}";
set :upperfirst :lower "b" "${a}";
set :quotewildcard "b" "Rock*";
plugins/managesieve/tests/parset_subaddress.phpt
New file
@@ -0,0 +1,38 @@
--TEST--
Test of Sieve subaddress extension (RFC5233)
--SKIPIF--
--FILE--
<?php
include '../lib/rcube_sieve_script.php';
$txt = '
require ["envelope","subaddress","fileinto"];
if envelope :user "To" "postmaster"
{
    fileinto "postmaster";
    stop;
}
if envelope :detail :is "To" "mta-filters"
{
    fileinto "mta-filters";
    stop;
}
';
$s = new rcube_sieve_script($txt);
echo $s->as_text();
// -------------------------------------------------------------------------------
?>
--EXPECT--
require ["envelope","subaddress","fileinto"];
if envelope :user "To" "postmaster"
{
    fileinto "postmaster";
    stop;
}
if envelope :detail :is "To" "mta-filters"
{
    fileinto "mta-filters";
    stop;
}
plugins/newmail_notifier/newmail_notifier.php
@@ -132,7 +132,7 @@
     */
    function notify($args)
    {
        if ($this->notified) {
        if ($this->notified || !empty($_GET['_refresh'])) {
            return $args;
        }
program/include/rcube_imap.php
@@ -3178,7 +3178,7 @@
                if (is_array($ns)) {
                    foreach ($ns as $ns_data) {
                        if (strlen($ns_data[0])) {
                            $search = $ns_data[0];
                            $search[] = $ns_data[0];
                        }
                    }
                }
program/js/app.js
@@ -5738,7 +5738,7 @@
  this.plain2html = function(plainText, id)
  {
    var lock = this.set_busy(true, 'converting');
    $(document.getElementById(id)).val('<pre>'+plainText+'</pre>');
    $(document.getElementById(id)).val(plainText ? '<pre>'+plainText+'</pre>' : '');
    this.set_busy(false, null, lock);
  };
program/js/googiespell.js
@@ -1,6 +1,8 @@
/*
 SpellCheck
    jQuery'fied spell checker based on GoogieSpell 4.0
      (which was published under GPL "version 2 or any later version")
 Copyright (C) 2006 Amir Salihefendic
 Copyright (C) 2009 Aleksander Machniak
 Copyright (C) 2011 Kolab Systems AG