alecpl
2010-03-18 98c489e9f49de6a4442821614d5e69b36173eeab
- Managesieve: Added import from Horde-INGO
- Managesieve: Support for more than one match using if+stop instead of if+elsif structures (#1486078)
- Managesieve: Support for selectively disabling rules within a single sieve script (#1485882)
- Managesieve: Added vertical splitter


10 files modified
972 ■■■■ changed files
CHANGELOG 3 ●●●●● patch | view | raw | blame | history
plugins/managesieve/Changelog 7 ●●●●● patch | view | raw | blame | history
plugins/managesieve/lib/rcube_sieve.php 827 ●●●● patch | view | raw | blame | history
plugins/managesieve/localization/en_US.inc 1 ●●●● patch | view | raw | blame | history
plugins/managesieve/localization/pl_PL.inc 1 ●●●● patch | view | raw | blame | history
plugins/managesieve/managesieve.js 39 ●●●● patch | view | raw | blame | history
plugins/managesieve/managesieve.php 32 ●●●●● patch | view | raw | blame | history
plugins/managesieve/skins/default/managesieve.css 38 ●●●● patch | view | raw | blame | history
plugins/managesieve/skins/default/templates/filteredit.html 10 ●●●● patch | view | raw | blame | history
plugins/managesieve/skins/default/templates/managesieve.html 14 ●●●●● patch | view | raw | blame | history
CHANGELOG
@@ -1,6 +1,9 @@
CHANGELOG RoundCube Webmail
===========================
- Managesieve: import from Horde-INGO
- Managesieve: support for more than one match (#1486078)
- Managesieve: support for selectively disabling rules within a single sieve script (#1485882)
- Threaded message listing now available
- Added sorting by ARRIVAL and CC
- Message list columns configurable by the user
plugins/managesieve/Changelog
@@ -1,3 +1,10 @@
* version 2.3 [2010-03-18]
-----------------------------------------------------------
- Added import from Horde-INGO
- Support for more than one match using if+stop instead of if+elsif structures (#1486078)
- Support for selectively disabling rules within a single sieve script (#1485882)
- Added vertical splitter
* version 2.2 [2010-02-06]
-----------------------------------------------------------
- Fix handling of "<>" characters in filter names (#1486477)
plugins/managesieve/lib/rcube_sieve.php
@@ -58,6 +58,10 @@
        $this->disabled = $disabled;
    }
    public function __destruct() {
        $this->sieve->disconnect();
    }
    /**
    * Getter for error code
    */
@@ -221,14 +225,24 @@
    // try to parse from Roundcube format
        $this->script = new rcube_sieve_script($script, $this->disabled);
        // ... else try Squirrelmail format
        if (empty($this->script->content) && $name == 'phpscript') {
            $script = $this->sieve->getScript('phpscript');
            $script = $this->_convert_from_squirrel_rules($script);
        // ... else try to import from different formats
        if (empty($this->script->content)) {
            $script = $this->_import_rules($script);
            $this->script = new rcube_sieve_script($script, $this->disabled);
        }
        // replace all elsif with if+stop, we support only ifs
        foreach ($this->script->content as $idx => $rule) {
            if (!isset($this->script->content[$idx+1])
                || preg_match('/^else|elsif$/', $this->script->content[$idx+1]['type'])) {
                // 'stop' not found?
                if (!preg_match('/^(stop|vacation)$/', $rule['actions'][count($rule['actions'])-1]['type'])) {
                    $this->script->content[$idx]['actions'][] = array(
                        'type' => 'stop'
                    );
                }
            }
        }
        $this->current = $name;
@@ -254,23 +268,38 @@
    }
    private function _convert_from_squirrel_rules($script)
    private function _import_rules($script)
    {
        $i = 0;
        $name = array();
        // tokenize rules
        if ($tokens = preg_split('/(#START_SIEVE_RULE.*END_SIEVE_RULE)\n/', $script, -1, PREG_SPLIT_DELIM_CAPTURE))
        // Squirrelmail (Avelsieve)
        if ($tokens = preg_split('/(#START_SIEVE_RULE.*END_SIEVE_RULE)\n/', $script, -1, PREG_SPLIT_DELIM_CAPTURE)) {
            foreach($tokens as $token) {
            if (preg_match('/^#START_SIEVE_RULE.*/', $token, $matches)) {
                $name[$i] = "unnamed rule ".($i+1);
                    $content .= "# rule:[".$name[$i]."]\n";
                    $content .= "# rule:[".$name[$i]."]\n";
                }
            elseif (isset($name[$i])) {
                $content .= "if $token\n";
            $i++;
                }
            }
        }
        // Horde (INGO)
        else if ($tokens = preg_split('/(# .+)\r?\n/i', $script, -1, PREG_SPLIT_DELIM_CAPTURE)) {
            foreach($tokens as $token) {
                if (preg_match('/^# (.+)/i', $token, $matches)) {
                    $name[$i] = $matches[1];
                    $content .= "# rule:[" . $name[$i] . "]\n";
                }
                elseif (isset($name[$i])) {
                    $token = str_replace(":comparator \"i;ascii-casemap\" ", "", $token);
                    $content .= $token . "\n";
                    $i++;
                }
            }
        }
        return $content;
    }
@@ -293,563 +322,553 @@
class rcube_sieve_script
{
  public $content = array();    // script rules array
    public $content = array();    // script rules array
  private $supported = array(    // extensions supported by class
    'fileinto',
    'reject',
    'ereject',
    'vacation',     // RFC5230
    private $supported = array(    // extensions supported by class
        'fileinto',
        'reject',
        'ereject',
        'vacation',     // RFC5230
    // TODO: (most wanted first) body, imapflags, notify, regex
    );
  
  /**
    /**
    * Object constructor
    *
    * @param  string  Script's text content
    * @param  array   Disabled extensions
    */
  public function __construct($script, $disabled=NULL)
    public function __construct($script, $disabled=NULL)
    {
      if (!empty($disabled))
        foreach ($disabled as $ext)
          if (($idx = array_search($ext, $this->supported)) !== false)
        unset($this->supported[$idx]);
        if (!empty($disabled))
            foreach ($disabled as $ext)
                if (($idx = array_search($ext, $this->supported)) !== false)
                unset($this->supported[$idx]);
      $this->content = $this->_parse_text($script);
        $this->content = $this->_parse_text($script);
    }
  /**
    /**
    * Adds script contents as text to the script array (at the end)
    *
    * @param    string    Text script contents
    */
  public function add_text($script)
    public function add_text($script)
    {
      $content = $this->_parse_text($script);
      $result = false;
      // check existsing script rules names
      foreach ($this->content as $idx => $elem)
        $names[$elem['name']] = $idx;
      foreach ($content as $elem)
        if (!isset($names[$elem['name']]))
      {
            array_push($this->content, $elem);
        $result = true;
      }
        $content = $this->_parse_text($script);
        $result = false;
      return $result;
        // check existsing script rules names
        foreach ($this->content as $idx => $elem) {
            $names[$elem['name']] = $idx;
        }
        foreach ($content as $elem) {
            if (!isset($names[$elem['name']])) {
                array_push($this->content, $elem);
            $result = true;
        }
        }
        return $result;
    }
  /**
    /**
    * Adds rule to the script (at the end)
    *
    * @param    string    Rule name
    * @param    array    Rule content (as array)
    */
  public function add_rule($content)
    public function add_rule($content)
    {
      // TODO: check this->supported
      array_push($this->content, $content);
      return sizeof($this->content)-1;
        // TODO: check this->supported
        array_push($this->content, $content);
        return sizeof($this->content)-1;
    }
  public function delete_rule($index)
    public function delete_rule($index)
    {
      if(isset($this->content[$index]))
        {
          unset($this->content[$index]);
      return true;
        if(isset($this->content[$index])) {
            unset($this->content[$index]);
        return true;
    }
      return false;
        return false;
    }
  public function size()
    public function size()
    {
      return sizeof($this->content);
        return sizeof($this->content);
    }
  public function update_rule($index, $content)
    public function update_rule($index, $content)
    {
      // TODO: check this->supported
      if ($this->content[$index])
        {
      $this->content[$index] = $content;
      return $index;
        // TODO: check this->supported
        if ($this->content[$index]) {
        $this->content[$index] = $content;
        return $index;
    }
      return false;
        return false;
    }
  /**
    /**
    * Returns script as text
    */
  public function as_text()
    public function as_text()
    {
      $script = '';
      $exts = array();
      $idx = 0;
      // rules
      foreach ($this->content as $rule)
        {
      $extension = '';
      $tests = array();
      $i = 0;
      // header
      $script .= '# rule:[' . $rule['name'] . "]\n";
        $script = '';
        $exts = array();
        $idx = 0;
        // rules
        foreach ($this->content as $rule) {
        $extension = '';
        $tests = array();
        $i = 0;
        // header
        $script .= '# rule:[' . $rule['name'] . "]\n";
  
      // constraints expressions
      foreach ($rule['tests'] as $test)
        {
          $tests[$i] = '';
          switch ($test['test'])
            {
          case 'size':
            $tests[$i] .= ($test['not'] ? 'not ' : '');
            $tests[$i] .= 'size :' . ($test['type']=='under' ? 'under ' : 'over ') . $test['arg'];
          break;
          case 'true':
            $tests[$i] .= ($test['not'] ? 'not true' : 'true');
          break;
          case 'exists':
            $tests[$i] .= ($test['not'] ? 'not ' : '');
            if (is_array($test['arg']))
            $tests[$i] .= 'exists ["' . implode('", "', $this->_escape_string($test['arg'])) . '"]';
            else
            $tests[$i] .= 'exists "' . $this->_escape_string($test['arg']) . '"';
          break;
          case 'header':
            $tests[$i] .= ($test['not'] ? 'not ' : '');
            $tests[$i] .= 'header :' . $test['type'];
            if (is_array($test['arg1']))
            $tests[$i] .= ' ["' . implode('", "', $this->_escape_string($test['arg1'])) . '"]';
            else
            $tests[$i] .= ' "' . $this->_escape_string($test['arg1']) . '"';
            if (is_array($test['arg2']))
            $tests[$i] .= ' ["' . implode('", "', $this->_escape_string($test['arg2'])) . '"]';
            else
            $tests[$i] .= ' "' . $this->_escape_string($test['arg2']) . '"';
          break;
        // constraints expressions
        foreach ($rule['tests'] as $test) {
            $tests[$i] = '';
            switch ($test['test']) {
            case 'size':
                $tests[$i] .= ($test['not'] ? 'not ' : '');
                $tests[$i] .= 'size :' . ($test['type']=='under' ? 'under ' : 'over ') . $test['arg'];
            break;
            case 'true':
                $tests[$i] .= ($test['not'] ? 'not true' : 'true');
            break;
            case 'exists':
                $tests[$i] .= ($test['not'] ? 'not ' : '');
                if (is_array($test['arg']))
                $tests[$i] .= 'exists ["' . implode('", "', $this->_escape_string($test['arg'])) . '"]';
                else
                $tests[$i] .= 'exists "' . $this->_escape_string($test['arg']) . '"';
            break;
            case 'header':
                $tests[$i] .= ($test['not'] ? 'not ' : '');
                $tests[$i] .= 'header :' . $test['type'];
                if (is_array($test['arg1']))
                $tests[$i] .= ' ["' . implode('", "', $this->_escape_string($test['arg1'])) . '"]';
                else
                $tests[$i] .= ' "' . $this->_escape_string($test['arg1']) . '"';
                if (is_array($test['arg2']))
                $tests[$i] .= ' ["' . implode('", "', $this->_escape_string($test['arg2'])) . '"]';
                else
                $tests[$i] .= ' "' . $this->_escape_string($test['arg2']) . '"';
            break;
        }
          $i++;
        }
      $script .= ($idx>0 ? 'els' : '').($rule['join'] ? 'if allof (' : 'if anyof (');
      if (sizeof($tests) > 1)
        $script .= implode(",\n\t", $tests);
      elseif (sizeof($tests))
        $script .= $tests[0];
      else
        $script .= 'true';
      $script .= ")\n{\n";
      // action(s)
      foreach ($rule['actions'] as $action)
          {
        switch ($action['type'])
        {
          case 'fileinto':
            $extension = 'fileinto';
        $script .= "\tfileinto \"" . $this->_escape_string($action['target']) . "\";\n";
          break;
          case 'redirect':
        $script .= "\tredirect \"" . $this->_escape_string($action['target']) . "\";\n";
          break;
          case 'reject':
          case 'ereject':
            $extension = $action['type'];
        if (strpos($action['target'], "\n")!==false)
          $script .= "\t".$action['type']." text:\n" . $action['target'] . "\n.\n;\n";
        else
          $script .= "\t".$action['type']." \"" . $this->_escape_string($action['target']) . "\";\n";
          break;
          case 'keep':
          case 'discard':
          case 'stop':
            $script .= "\t" . $action['type'] .";\n";
          break;
          case 'vacation':
                $extension = 'vacation';
                $script .= "\tvacation";
        if ($action['days'])
          $script .= " :days " . $action['days'];
        if ($action['addresses'])
          $script .= " :addresses " . $this->_print_list($action['addresses']);
        if ($action['subject'])
          $script .= " :subject \"" . $this->_escape_string($action['subject']) . "\"";
        if ($action['handle'])
          $script .= " :handle \"" . $this->_escape_string($action['handle']) . "\"";
        if ($action['from'])
          $script .= " :from \"" . $this->_escape_string($action['from']) . "\"";
        if ($action['mime'])
          $script .= " :mime";
        if (strpos($action['reason'], "\n")!==false)
          $script .= " text:\n" . $action['reason'] . "\n.\n;\n";
        else
          $script .= " \"" . $this->_escape_string($action['reason']) . "\";\n";
          break;
            $i++;
        }
        if ($extension && !isset($exts[$extension]))
          $exts[$extension] = $extension;
      }
//            $script .= ($idx>0 ? 'els' : '').($rule['join'] ? 'if allof (' : 'if anyof (');
            // disabled rule: if false #....
            $script .= 'if' . ($rule['disabled'] ? ' false #' : '');
        $script .= $rule['join'] ? ' allof (' : ' anyof (';
        if (sizeof($tests) > 1)
            $script .= implode(",\n\t", $tests);
        else if (sizeof($tests))
            $script .= $tests[0];
        else
            $script .= 'true';
        $script .= ")\n{\n";
        // action(s)
        foreach ($rule['actions'] as $action) {
            switch ($action['type']) {
                case 'fileinto':
                    $extension = 'fileinto';
                $script .= "\tfileinto \"" . $this->_escape_string($action['target']) . "\";\n";
                break;
                case 'redirect':
                $script .= "\tredirect \"" . $this->_escape_string($action['target']) . "\";\n";
                break;
                case 'reject':
                case 'ereject':
                    $extension = $action['type'];
                if (strpos($action['target'], "\n")!==false)
                    $script .= "\t".$action['type']." text:\n" . $action['target'] . "\n.\n;\n";
                else
                    $script .= "\t".$action['type']." \"" . $this->_escape_string($action['target']) . "\";\n";
                break;
                case 'keep':
                case 'discard':
                case 'stop':
                    $script .= "\t" . $action['type'] .";\n";
                break;
                case 'vacation':
                        $extension = 'vacation';
                        $script .= "\tvacation";
                if ($action['days'])
                    $script .= " :days " . $action['days'];
                if ($action['addresses'])
                    $script .= " :addresses " . $this->_print_list($action['addresses']);
                if ($action['subject'])
                    $script .= " :subject \"" . $this->_escape_string($action['subject']) . "\"";
                if ($action['handle'])
                    $script .= " :handle \"" . $this->_escape_string($action['handle']) . "\"";
                if ($action['from'])
                    $script .= " :from \"" . $this->_escape_string($action['from']) . "\"";
                if ($action['mime'])
                    $script .= " :mime";
                if (strpos($action['reason'], "\n")!==false)
                    $script .= " text:\n" . $action['reason'] . "\n.\n;\n";
                else
                    $script .= " \"" . $this->_escape_string($action['reason']) . "\";\n";
                break;
            }
      $script .= "}\n";
      $idx++;
            if ($extension && !isset($exts[$extension]))
                $exts[$extension] = $extension;
        }
        $script .= "}\n";
        $idx++;
    }
      
      // requires
      if (sizeof($exts))
        $script = 'require ["' . implode('","', $exts) . "\"];\n" . $script;
        // requires
        if (sizeof($exts))
            $script = 'require ["' . implode('","', $exts) . "\"];\n" . $script;
      return $script;
        return $script;
    }
  /**
    /**
    * Returns script object
    *
    */
  public function as_array()
    public function as_array()
    {
      return $this->content;
        return $this->content;
    }
  /**
    /**
    * Returns array of supported extensions
    *
    */
  public function get_extensions()
    public function get_extensions()
    {
      return array_values($this->supported);
        return array_values($this->supported);
    }
  /**
    /**
    * Converts text script to rules array
    *
    * @param    string    Text script
    */
  private function _parse_text($script)
    private function _parse_text($script)
    {
      $i = 0;
      $content = array();
        $i = 0;
        $content = array();
      // remove C comments
      $script = preg_replace('|/\*.*?\*/|sm', '', $script);
        // remove C comments
        $script = preg_replace('|/\*.*?\*/|sm', '', $script);
      // tokenize rules
      if ($tokens = preg_split('/(# rule:\[.*\])\r?\n/', $script, -1, PREG_SPLIT_DELIM_CAPTURE))
        foreach($tokens as $token)
      {
        if (preg_match('/^# rule:\[(.*)\]/', $token, $matches))
          {
            $content[$i]['name'] = $matches[1];
          }
        elseif (isset($content[$i]['name']) && sizeof($content[$i]) == 1)
          {
            if ($rule = $this->_tokenize_rule($token))
          {
                $content[$i] = array_merge($content[$i], $rule);
                $i++;
          }
        else // unknown rule format
            unset($content[$i]);
          }
      }
        // tokenize rules
        if ($tokens = preg_split('/(# rule:\[.*\])\r?\n/', $script, -1, PREG_SPLIT_DELIM_CAPTURE)) {
            foreach($tokens as $token) {
            if (preg_match('/^# rule:\[(.*)\]/', $token, $matches)) {
                $content[$i]['name'] = $matches[1];
            }
            elseif (isset($content[$i]['name']) && sizeof($content[$i]) == 1) {
                if ($rule = $this->_tokenize_rule($token)) {
                    $content[$i] = array_merge($content[$i], $rule);
                    $i++;
            }
            else // unknown rule format
                unset($content[$i]);
            }
        }
        }
      return $content;
        return $content;
    }
  /**
    /**
    * Convert text script fragment to rule object
    *
    * @param    string    Text rule
    */
  private function _tokenize_rule($content)
    private function _tokenize_rule($content)
    {
      $result = NULL;
        $result = NULL;
    
      if (preg_match('/^(if|elsif|else)\s+((true|not\s+true|allof|anyof|exists|header|not|size)(.*))\s+\{(.*)\}$/sm', trim($content), $matches))
        {
      list($tests, $join) = $this->_parse_tests(trim($matches[2]));
      $actions = $this->_parse_actions(trim($matches[5]));
        if (preg_match('/^(if|elsif|else)\s+((true|false|not\s+true|allof|anyof|exists|header|not|size)(.*))\s+\{(.*)\}$/sm',
            trim($content), $matches)) {
      if ($tests && $actions)
        $result = array(
            $tests = trim($matches[2]);
            // disabled rule (false + comment): if false #.....
            if ($matches[3] == 'false') {
                $tests = preg_replace('/^false\s+#\s+/', '', $tests);
                $disabled = true;
            }
            else
                $disabled = false;
        list($tests, $join) = $this->_parse_tests($tests);
        $actions = $this->_parse_actions(trim($matches[5]));
        if ($tests && $actions)
            $result = array(
                    'type' => $matches[1],
            'tests' => $tests,
            'actions' => $actions,
            'join' => $join,
        );
                    'disabled' => $disabled,
            );
    }
      return $result;
        return $result;
    }    
  /**
    /**
    * Parse body of actions section
    *
    * @param    string    Text body
    * @return    array    Array of parsed action type/target pairs
    */
  private function _parse_actions($content)
    private function _parse_actions($content)
    {
      $result = NULL;
        $result = NULL;
      // supported actions
      $patterns[] = '^\s*discard;';
      $patterns[] = '^\s*keep;';
      $patterns[] = '^\s*stop;';
      $patterns[] = '^\s*redirect\s+(.*?[^\\\]);';
      if (in_array('fileinto', $this->supported))
        $patterns[] = '^\s*fileinto\s+(.*?[^\\\]);';
      if (in_array('reject', $this->supported)) {
        $patterns[] = '^\s*reject\s+text:(.*)\n\.\n;';
        $patterns[] = '^\s*reject\s+(.*?[^\\\]);';
        $patterns[] = '^\s*ereject\s+text:(.*)\n\.\n;';
        $patterns[] = '^\s*ereject\s+(.*?[^\\\]);';
      }
      if (in_array('vacation', $this->supported))
        $patterns[] = '^\s*vacation\s+(.*?[^\\\]);';
        // supported actions
        $patterns[] = '^\s*discard;';
        $patterns[] = '^\s*keep;';
        $patterns[] = '^\s*stop;';
        $patterns[] = '^\s*redirect\s+(.*?[^\\\]);';
        if (in_array('fileinto', $this->supported))
            $patterns[] = '^\s*fileinto\s+(.*?[^\\\]);';
        if (in_array('reject', $this->supported)) {
            $patterns[] = '^\s*reject\s+text:(.*)\n\.\n;';
            $patterns[] = '^\s*reject\s+(.*?[^\\\]);';
            $patterns[] = '^\s*ereject\s+text:(.*)\n\.\n;';
            $patterns[] = '^\s*ereject\s+(.*?[^\\\]);';
        }
        if (in_array('vacation', $this->supported))
            $patterns[] = '^\s*vacation\s+(.*?[^\\\]);';
      $pattern = '/(' . implode('$)|(', $patterns) . '$)/ms';
        $pattern = '/(' . implode('$)|(', $patterns) . '$)/ms';
      // parse actions body
      if (preg_match_all($pattern, $content, $mm, PREG_SET_ORDER))
      {
        foreach ($mm as $m)
    {
      $content = trim($m[0]);
        // parse actions body
        if (preg_match_all($pattern, $content, $mm, PREG_SET_ORDER)) {
            foreach ($mm as $m) {
            $content = trim($m[0]);
      
          if(preg_match('/^(discard|keep|stop)/', $content, $matches))
            {
          $result[] = array('type' => $matches[1]);
        }
          elseif(preg_match('/^fileinto/', $content))
            {
          $result[] = array('type' => 'fileinto', 'target' => $this->_parse_string($m[sizeof($m)-1]));
        }
          elseif(preg_match('/^redirect/', $content))
            {
          $result[] = array('type' => 'redirect', 'target' => $this->_parse_string($m[sizeof($m)-1]));
        }
          elseif(preg_match('/^(reject|ereject)\s+(.*);$/sm', $content, $matches))
            {
          $result[] = array('type' => $matches[1], 'target' => $this->_parse_string($matches[2]));
        }
          elseif(preg_match('/^vacation\s+(.*);$/sm', $content, $matches))
            {
          $vacation = array('type' => 'vacation');
                if(preg_match('/^(discard|keep|stop)/', $content, $matches)) {
                $result[] = array('type' => $matches[1]);
            }
                elseif(preg_match('/^fileinto/', $content)) {
                $result[] = array('type' => 'fileinto', 'target' => $this->_parse_string($m[sizeof($m)-1]));
            }
                elseif(preg_match('/^redirect/', $content)) {
                $result[] = array('type' => 'redirect', 'target' => $this->_parse_string($m[sizeof($m)-1]));
            }
                elseif(preg_match('/^(reject|ereject)\s+(.*);$/sm', $content, $matches)) {
                $result[] = array('type' => $matches[1], 'target' => $this->_parse_string($matches[2]));
            }
                elseif(preg_match('/^vacation\s+(.*);$/sm', $content, $matches)) {
                $vacation = array('type' => 'vacation');
              if (preg_match('/:(days)\s+([0-9]+)/', $content, $vm)) {
            $vacation['days'] = $vm[2];
        $content = preg_replace('/:(days)\s+([0-9]+)/', '', $content);
          }
              if (preg_match('/:(subject)\s+(".*?[^\\\]")/', $content, $vm)) {
            $vacation['subject'] = $vm[2];
        $content = preg_replace('/:(subject)\s+(".*?[^\\\]")/', '', $content);
          }
              if (preg_match('/:(addresses)\s+\[(.*?[^\\\])\]/', $content, $vm)) {
            $vacation['addresses'] = $this->_parse_list($vm[2]);
        $content = preg_replace('/:(addresses)\s+\[(.*?[^\\\])\]/', '', $content);
          }
              if (preg_match('/:(handle)\s+(".*?[^\\\]")/', $content, $vm)) {
            $vacation['handle'] = $vm[2];
        $content = preg_replace('/:(handle)\s+(".*?[^\\\]")/', '', $content);
          }
              if (preg_match('/:(from)\s+(".*?[^\\\]")/', $content, $vm)) {
            $vacation['from'] = $vm[2];
        $content = preg_replace('/:(from)\s+(".*?[^\\\]")/', '', $content);
          }
          $content = preg_replace('/^vacation/', '', $content);
          $content = preg_replace('/;$/', '', $content);
          $content = trim($content);
              if (preg_match('/^:(mime)/', $content, $vm)) {
            $vacation['mime'] = true;
        $content = preg_replace('/^:mime/', '', $content);
          }
                    if (preg_match('/:(days)\s+([0-9]+)/', $content, $vm)) {
                    $vacation['days'] = $vm[2];
                $content = preg_replace('/:(days)\s+([0-9]+)/', '', $content);
                }
                    if (preg_match('/:(subject)\s+(".*?[^\\\]")/', $content, $vm)) {
                    $vacation['subject'] = $vm[2];
                    $content = preg_replace('/:(subject)\s+(".*?[^\\\]")/', '', $content);
                    }
                    if (preg_match('/:(addresses)\s+\[(.*?[^\\\])\]/', $content, $vm)) {
                    $vacation['addresses'] = $this->_parse_list($vm[2]);
                $content = preg_replace('/:(addresses)\s+\[(.*?[^\\\])\]/', '', $content);
                }
                    if (preg_match('/:(handle)\s+(".*?[^\\\]")/', $content, $vm)) {
                    $vacation['handle'] = $vm[2];
                $content = preg_replace('/:(handle)\s+(".*?[^\\\]")/', '', $content);
                }
                    if (preg_match('/:(from)\s+(".*?[^\\\]")/', $content, $vm)) {
                    $vacation['from'] = $vm[2];
                $content = preg_replace('/:(from)\s+(".*?[^\\\]")/', '', $content);
                }
          $vacation['reason'] = $this->_parse_string($content);
                $content = preg_replace('/^vacation/', '', $content);
                $content = preg_replace('/;$/', '', $content);
                $content = trim($content);
              $result[] = $vacation;
                    if (preg_match('/^:(mime)/', $content, $vm)) {
                    $vacation['mime'] = true;
                $content = preg_replace('/^:mime/', '', $content);
                }
                $vacation['reason'] = $this->_parse_string($content);
                    $result[] = $vacation;
                }
            }
        }
      }
      return $result;
        return $result;
    }    
    
   /**
    /**
    * Parse test/conditions section
    *
    * @param    string    Text 
    */
  private function _parse_tests($content)
    private function _parse_tests($content)
    {
      $result = NULL;
        $result = NULL;
      // lists
      if (preg_match('/^(allof|anyof)\s+\((.*)\)$/sm', $content, $matches))
    {
      $content = $matches[2];
      $join = $matches[1]=='allof' ? true : false;
        // lists
        if (preg_match('/^(allof|anyof)\s+\((.*)\)$/sm', $content, $matches)) {
        $content = $matches[2];
        $join = $matches[1]=='allof' ? true : false;
    }
      else
      $join = false;
        else
        $join = false;
      
      // supported tests regular expressions
      // TODO: comparators, envelope
      $patterns[] = '(not\s+)?(exists)\s+\[(.*?[^\\\])\]';
      $patterns[] = '(not\s+)?(exists)\s+(".*?[^\\\]")';
      $patterns[] = '(not\s+)?(true)';
      $patterns[] = '(not\s+)?(size)\s+:(under|over)\s+([0-9]+[KGM]{0,1})';
      $patterns[] = '(not\s+)?(header)\s+:(contains|is|matches)\s+\[(.*?[^\\\]")\]\s+\[(.*?[^\\\]")\]';
      $patterns[] = '(not\s+)?(header)\s+:(contains|is|matches)\s+(".*?[^\\\]")\s+(".*?[^\\\]")';
      $patterns[] = '(not\s+)?(header)\s+:(contains|is|matches)\s+\[(.*?[^\\\]")\]\s+(".*?[^\\\]")';
      $patterns[] = '(not\s+)?(header)\s+:(contains|is|matches)\s+(".*?[^\\\]")\s+\[(.*?[^\\\]")\]';
        // supported tests regular expressions
        // TODO: comparators, envelope
        $patterns[] = '(not\s+)?(exists)\s+\[(.*?[^\\\])\]';
        $patterns[] = '(not\s+)?(exists)\s+(".*?[^\\\]")';
        $patterns[] = '(not\s+)?(true)';
        $patterns[] = '(not\s+)?(size)\s+:(under|over)\s+([0-9]+[KGM]{0,1})';
        $patterns[] = '(not\s+)?(header)\s+:(contains|is|matches)\s+\[(.*?[^\\\]")\]\s+\[(.*?[^\\\]")\]';
        $patterns[] = '(not\s+)?(header)\s+:(contains|is|matches)\s+(".*?[^\\\]")\s+(".*?[^\\\]")';
        $patterns[] = '(not\s+)?(header)\s+:(contains|is|matches)\s+\[(.*?[^\\\]")\]\s+(".*?[^\\\]")';
        $patterns[] = '(not\s+)?(header)\s+:(contains|is|matches)\s+(".*?[^\\\]")\s+\[(.*?[^\\\]")\]';
      
      // join patterns...
      $pattern = '/(' . implode(')|(', $patterns) . ')/';
        // join patterns...
        $pattern = '/(' . implode(')|(', $patterns) . ')/';
      // ...and parse tests list
      if (preg_match_all($pattern, $content, $matches, PREG_SET_ORDER))
        {
      foreach ($matches as $match)
        {
          $size = sizeof($match);
        // ...and parse tests list
        if (preg_match_all($pattern, $content, $matches, PREG_SET_ORDER)) {
        foreach ($matches as $match) {
            $size = sizeof($match);
          
          if (preg_match('/^(not\s+)?size/', $match[0]))
            {
          $result[] = array(
            'test'     => 'size',
            'not'     => $match[$size-4] ? true : false,
            'type'     => $match[$size-2], // under/over
            'arg'    => $match[$size-1], // value
          );
            if (preg_match('/^(not\s+)?size/', $match[0])) {
            $result[] = array(
                'test'     => 'size',
                'not'     => $match[$size-4] ? true : false,
                'type'     => $match[$size-2], // under/over
                'arg'    => $match[$size-1], // value
            );
        }
          elseif (preg_match('/^(not\s+)?header/', $match[0]))
            {
          $result[] = array(
            'test'    => 'header',
            'not'     => $match[$size-5] ? true : false,
            'type'    => $match[$size-3], // is/contains/matches
            'arg1'     => $this->_parse_list($match[$size-2]), // header(s)
            'arg2'    => $this->_parse_list($match[$size-1]), // string(s)
          );
            elseif (preg_match('/^(not\s+)?header/', $match[0])) {
            $result[] = array(
                'test'    => 'header',
                'not'     => $match[$size-5] ? true : false,
                'type'    => $match[$size-3], // is/contains/matches
                'arg1'     => $this->_parse_list($match[$size-2]), // header(s)
                'arg2'    => $this->_parse_list($match[$size-1]), // string(s)
            );
        }
          elseif (preg_match('/^(not\s+)?exists/', $match[0]))
        {
          $result[] = array(
            'test'     => 'exists',
            'not'     => $match[$size-3] ? true : false,
            'arg'     => $this->_parse_list($match[$size-1]), // header(s)
          );
            elseif (preg_match('/^(not\s+)?exists/', $match[0])) {
            $result[] = array(
                'test'     => 'exists',
                'not'     => $match[$size-3] ? true : false,
                'arg'     => $this->_parse_list($match[$size-1]), // header(s)
            );
            }
          elseif (preg_match('/^(not\s+)?true/', $match[0]))
        {
          $result[] = array(
            'test'     => 'true',
            'not'     => $match[$size-2] ? true : false,
          );
            elseif (preg_match('/^(not\s+)?true/', $match[0])) {
            $result[] = array(
                'test'     => 'true',
                'not'     => $match[$size-2] ? true : false,
            );
            }
        }
    }
      return array($result, $join);
        return array($result, $join);
    }    
   /**
    /**
    * Parse string value
    *
    * @param    string    Text 
    */
  private function _parse_string($content)
    private function _parse_string($content)
    {
      $text = '';
      $content = trim($content);
        $text = '';
        $content = trim($content);
      if (preg_match('/^text:(.*)\.$/sm', $content, $matches))
        $text = trim($matches[1]);
      elseif (preg_match('/^"(.*)"$/', $content, $matches))
        $text = str_replace('\"', '"', $matches[1]);
        if (preg_match('/^text:(.*)\.$/sm', $content, $matches))
            $text = trim($matches[1]);
        elseif (preg_match('/^"(.*)"$/', $content, $matches))
            $text = str_replace('\"', '"', $matches[1]);
      return $text;
        return $text;
    }    
   /**
    /**
    * Escape special chars in string value
    *
    * @param    string    Text 
    */
  private function _escape_string($content)
    private function _escape_string($content)
    {
      $replace['/"/'] = '\\"';
        $replace['/"/'] = '\\"';
      
      if (is_array($content))
        {
      for ($x=0, $y=sizeof($content); $x<$y; $x++)
        $content[$x] = preg_replace(array_keys($replace), array_values($replace), $content[$x]);
        if (is_array($content)) {
        for ($x=0, $y=sizeof($content); $x<$y; $x++)
            $content[$x] = preg_replace(array_keys($replace), array_values($replace), $content[$x]);
        
      return $content;
        return $content;
    }
      else
        return preg_replace(array_keys($replace), array_values($replace), $content);
        else
            return preg_replace(array_keys($replace), array_values($replace), $content);
    }
   /**
    /**
    * Parse string or list of strings to string or array of strings
    *
    * @param    string    Text 
    */
  private function _parse_list($content)
    private function _parse_list($content)
    {
      $result = array();
        $result = array();
      
      for ($x=0, $len=strlen($content); $x<$len; $x++)
        {
      switch ($content[$x])
        {
          case '\\':
            $str .= $content[++$x];
              break;
          case '"':
            if (isset($str))
              {
            $result[] = $str;
            unset($str);
          }
            else
              $str = '';
          break;
          default:
            if(isset($str))
              $str .= $content[$x];
          break;
        for ($x=0, $len=strlen($content); $x<$len; $x++) {
        switch ($content[$x]) {
            case '\\':
                $str .= $content[++$x];
                break;
            case '"':
                if (isset($str)) {
                $result[] = $str;
                unset($str);
            }
                else
                    $str = '';
            break;
            default:
                if(isset($str))
                    $str .= $content[$x];
            break;
        }
    }
      
      if (sizeof($result)>1)
        return $result;
      elseif (sizeof($result) == 1)
    return $result[0];
      else
        return NULL;
        if (sizeof($result)>1)
            return $result;
        elseif (sizeof($result) == 1)
        return $result[0];
        else
            return NULL;
    }    
   /**
    /**
    * Convert array of elements to list of strings
    *
    * @param    string    Text 
    */
  private function _print_list($list)
    private function _print_list($list)
    {
      $list = (array) $list;
      foreach($list as $idx => $val)
        $list[$idx] = $this->_escape_string($val);
        $list = (array) $list;
        foreach($list as $idx => $val)
            $list[$idx] = $this->_escape_string($val);
    
      return '["' . implode('","', $list) . '"]';
        return '["' . implode('","', $list) . '"]';
    }
}
plugins/managesieve/localization/en_US.inc
@@ -46,6 +46,7 @@
$labels['active'] = 'active';
$labels['copyfromset'] = 'Copy filters from set';
$labels['none'] = '- none -';
$labels['filterdisabled'] = 'Filter disabled';
$messages = array();
$messages['filterunknownerror'] = 'Unknown server error';
plugins/managesieve/localization/pl_PL.inc
@@ -47,6 +47,7 @@
$labels['active'] = 'aktywny';
$labels['copyfromset'] = 'Skopiuj filtry ze zbioru';
$labels['none'] = '- brak -';
$labels['filterdisabled'] = 'Filtr wyÅ‚Ä…czony';
$messages = array();
$messages['filterunknownerror'] = 'Nieznany bÅ‚Ä…d serwera';
plugins/managesieve/managesieve.js
@@ -26,9 +26,10 @@
    if (rcmail.env.action == 'plugin.managesieve')
      {
    if (rcmail.gui_objects.sieveform)
    if (rcmail.gui_objects.sieveform) {
      rcmail.enable_command('plugin.managesieve-save', true);
    else {
    }
        else {
      rcmail.enable_command('plugin.managesieve-del', 'plugin.managesieve-up',
        'plugin.managesieve-down', false);
          rcmail.enable_command('plugin.managesieve-add', 'plugin.managesieve-setadd', !rcmail.env.sieveconnerror);
@@ -47,6 +48,8 @@
            rcmail.filters_list.focus();
      }
      }
    if (rcmail.gui_objects.sieveform && rcmail.env.rule_disabled)
      $('#disabled').attr('checked', true);
  });
  /*********************************************************/
@@ -91,7 +94,7 @@
    return i;
    }
  rcube_webmail.prototype.managesieve_updatelist = function(action, name, id)
  rcube_webmail.prototype.managesieve_updatelist = function(action, name, id, disabled)
    {
    this.set_busy(true);
@@ -114,7 +117,7 @@
      case 'down':
        var rows = this.filters_list.rows;
    var from;
    var from, fromstatus, status;
    // we need only to replace filter names...
        for (var i=0; i<rows.length; i++)
@@ -122,11 +125,15 @@
      if (rows[i]==null) { // removed row
        continue;
          } else if (rows[i].uid == id) {
        from = rows[i].obj.cells[0];
        from = rows[i].obj;
            fromstatus = $(from).hasClass('disabled');
      } else if (rows[i].uid == id+1){
        name = rows[i].obj.cells[0].innerHTML;
        rows[i].obj.cells[0].innerHTML = from.innerHTML;
        from.innerHTML = name;
            status = $(rows[i].obj).hasClass('disabled');
        rows[i].obj.cells[0].innerHTML = from.cells[0].innerHTML;
        from.cells[0].innerHTML = name;
            $(from)[status?'addClass':'removeClass']('disabled');
            $(rows[i].obj)[fromstatus?'addClass':'removeClass']('disabled');
        this.filters_list.highlight_row(i);
        break;
      }
@@ -137,7 +144,7 @@
      case 'up':
        var rows = this.filters_list.rows;
    var from;
    var from, status, fromstatus;
    // we need only to replace filter names...
        for (var i=0; i<rows.length; i++)
@@ -145,12 +152,16 @@
      if (rows[i]==null) { // removed row
        continue;
          } else if (rows[i].uid == id-1) {
        from = rows[i].obj.cells[0];
        from = rows[i].obj;
            fromstatus = $(from).hasClass('disabled');
        this.filters_list.highlight_row(i);
      } else if (rows[i].uid == id) {
        name = rows[i].obj.cells[0].innerHTML;
        rows[i].obj.cells[0].innerHTML = from.innerHTML;
        from.innerHTML = name;
            status = $(rows[i].obj).hasClass('disabled');
        rows[i].obj.cells[0].innerHTML = from.cells[0].innerHTML;
        from.cells[0].innerHTML = name;
            $(from)[status?'addClass':'removeClass']('disabled');
            $(rows[i].obj)[fromstatus?'addClass':'removeClass']('disabled');
        break;
      }
    }
@@ -164,6 +175,10 @@
      if (rows[i] && rows[i].uid == id)
        {
        rows[i].obj.cells[0].innerHTML = name;
            if (disabled)
              $(rows[i].obj).addClass('disabled');
            else
              $(rows[i].obj).removeClass('disabled');
        break;
        }
        break;
@@ -186,6 +201,8 @@
            td = parent.document.createElement('td');
            new_row.appendChild(td);
        list.insert_row(new_row, false);
            if (disabled)
              $(new_row).addClass('disabled');
            if (row.cells[0].className)
              td.className = row.cells[0].className;
plugins/managesieve/managesieve.php
@@ -7,7 +7,7 @@
 * It's clickable interface which operates on text scripts and communicates
 * with server using managesieve protocol. Adds Filters tab in Settings.
 *
 * @version 2.2
 * @version 2.3
 * @author Aleksander 'A.L.E.C' Machniak <alec@alec.pl>
 *
 * Configuration (see config.inc.php.dist)
@@ -46,9 +46,7 @@
  
  function managesieve_start()
  {
    $rcmail = rcmail::get_instance();
    $this->rc = &$rcmail;
    $this->rc = rcmail::get_instance();
    $this->load_config();
    // register UI objects
@@ -302,6 +300,7 @@
      foreach ($sizeitems as $item)
    $items[] = $item;
      $this->form['disabled'] = $_POST['_disabled'] ? true : false;
      $this->form['join'] = $join=='allof' ? true : false;
      $this->form['name'] = $name;
      $this->form['tests'] = array();
@@ -486,8 +485,10 @@
    if ($save && $fid !== false)
    {
      $this->rc->output->show_message('managesieve.filtersaved', 'confirmation');
      $this->rc->output->add_script(sprintf("rcmail.managesieve_updatelist('%s', '%s', %d);",
        isset($new) ? 'add' : 'update', Q($this->form['name']), $fid), 'foot');
      $this->rc->output->add_script(
            sprintf("rcmail.managesieve_updatelist('%s', '%s', %d, %d);",
          isset($new) ? 'add' : 'update', Q($this->form['name']), $fid, $this->form['disabled']),
              'foot');
    }
    else
    {
@@ -504,10 +505,12 @@
  {
    // Handle form action 
    if (isset($_GET['_framed']) || isset($_POST['_framed'])) {
      if (isset($_GET['_newset']) || isset($_POST['_newset']))
      if (isset($_GET['_newset']) || isset($_POST['_newset'])) {
        $this->rc->output->send('managesieve.setedit');
      else
      }
      else {
        $this->rc->output->send('managesieve.filteredit');
      }
    } else {
      $this->rc->output->set_pagetitle($this->gettext('filters'));
      $this->rc->output->send('managesieve.managesieve');
@@ -525,8 +528,12 @@
    $a_show_cols = array('managesieve.filtername');
    foreach($this->script as $idx => $filter)
      $result[] = array('managesieve.filtername' => $filter['name'], 'id' => $idx);
      $result[] = array(
        'managesieve.filtername' => $filter['name'],
        'id' => $idx,
        'class' => $filter['disabled'] ? 'disabled' : '',
      );
    // create XHTML table
    $out = rcube_table_output($attrib, $result, $a_show_cols, 'id');
@@ -722,7 +729,10 @@
    $out .= "</div>\n";
    $out .= "</fieldset>\n";
    if ($scr['disabled']) {
      $this->rc->output->set_env('rule_disabled', true);
    }
    $this->rc->output->add_label('managesieve.ruledeleteconfirm');
    $this->rc->output->add_label('managesieve.actiondeleteconfirm');
    $this->rc->output->add_gui_object('sieveform', 'filterform');
plugins/managesieve/skins/default/managesieve.css
@@ -5,7 +5,6 @@
{
  position: absolute;
  left: 20px;
  width: 220px;
  top: 120px;
  bottom: 30px;
  border: 1px solid #999999;
@@ -28,6 +27,11 @@
  cursor: pointer;
}
#filters-table tbody tr.disabled td
{
  color: #999999;
}
#filtersbuttons
{
  position: absolute;
@@ -38,7 +42,7 @@
#filtersetsbuttons
{
  position: absolute;
  left: 250px;
  left: 230px;
  top: 85px;
}
@@ -131,7 +135,7 @@
#filtersetselect
{
  position: absolute;
  left: 380px;
  left: 360px;
  top: 90px;
}
@@ -139,7 +143,6 @@
{
  position: absolute;
  top: 120px;
  left: 250px;
  right: 20px;
  bottom: 30px;
  border: 1px solid #999999;
@@ -168,12 +171,6 @@
  white-space: nowrap;
  background-color: #F9F9F9;
  padding: 20px 10px 10px 10px;
}
#filter-form input, select
{
  font-size: 10pt;
  font-family: inherit;
}
fieldset
@@ -248,3 +245,24 @@
  font-size: 10px;
  white-space: nowrap;
}
#footer
{
  padding-top: 5px;
  width: 100%;
}
#footer .footerleft
{
  padding-left: 2px;
  white-space: nowrap;
  float: left;
}
#footer .footerright
{
  padding-right: 2px;
  white-space: nowrap;
  text-align: right;
  float: right;
}
plugins/managesieve/skins/default/templates/filteredit.html
@@ -99,9 +99,15 @@
<div id="filter-form">
<roundcube:object name="filterform" />
<p>
<div id="footer">
<div class="footerleft">
<roundcube:button command="plugin.managesieve-save" type="input" class="button mainaction" label="save" />
</p>
</div>
<div class="footerright">
<label for="disabled"><roundcube:label name="managesieve.filterdisabled" /></label>
<input type="checkbox" id="disabled" name="_disabled" value="1" />
</div>
</div>
</form>
</div>
plugins/managesieve/skins/default/templates/managesieve.html
@@ -5,6 +5,15 @@
<roundcube:include file="/includes/links.html" />
<link rel="stylesheet" type="text/css" href="/this/managesieve.css" />
<script type="text/javascript" src="/functions.js"></script>
<script type="text/javascript" src="/splitter.js"></script>
<style type="text/css">
#filterslist { width: <roundcube:exp expression="!empty(cookie:sieveviewsplitter) ? cookie:sieveviewsplitter-5 : 210" />px; }
#filter-box { left: <roundcube:exp expression="!empty(cookie:sieveviewsplitter) ? cookie:sieveviewsplitter+5 : 220" />px;
<roundcube:exp expression="browser:ie ? ('width:expression((parseInt(this.parentNode.offsetWidth)-'.(!empty(cookie:sieveviewsplitter) ? cookie:sieveviewsplitter+5 : 220).')+\\'px\\');') : ''" />
}
</style>
</head>
<body>
@@ -32,7 +41,10 @@
<div id="filterslist">
<roundcube:object name="filterslist" id="filters-table" class="records-table" cellspacing="0" summary="Filters list" />
</div>
<script type="text/javascript">
  var sieveviewsplit = new rcube_splitter({id:'sieveviewsplitter', p1: 'filterslist', p2: 'filter-box', orientation: 'v', relative: true, start: 215});
  rcmail.add_onload('sieveviewsplit.init()');
</script>
<div id="filter-box">
<roundcube:object name="filterframe" id="filter-frame" width="100%" height="100%" frameborder="0" src="/watermark.html" />
</div>