diff options
Diffstat (limited to 'plugins/managesieve/lib')
3 files changed, 646 insertions, 164 deletions
| diff --git a/plugins/managesieve/lib/Roundcube/rcube_sieve.php b/plugins/managesieve/lib/Roundcube/rcube_sieve.php index 3bd2978da..389c85012 100644 --- a/plugins/managesieve/lib/Roundcube/rcube_sieve.php +++ b/plugins/managesieve/lib/Roundcube/rcube_sieve.php @@ -22,17 +22,6 @@  // Managesieve Protocol: RFC5804 -define('SIEVE_ERROR_CONNECTION', 1); -define('SIEVE_ERROR_LOGIN', 2); -define('SIEVE_ERROR_NOT_EXISTS', 3);    // script not exists -define('SIEVE_ERROR_INSTALL', 4);       // script installation -define('SIEVE_ERROR_ACTIVATE', 5);      // script activation -define('SIEVE_ERROR_DELETE', 6);        // script deletion -define('SIEVE_ERROR_INTERNAL', 7);      // internal error -define('SIEVE_ERROR_DEACTIVATE', 8);    // script activation -define('SIEVE_ERROR_OTHER', 255);       // other/unknown error - -  class rcube_sieve  {      private $sieve;                 // Net_Sieve object @@ -43,6 +32,16 @@ class rcube_sieve      public $current;                // name of currently loaded script      private $exts;                  // array of supported extensions +    const ERROR_CONNECTION = 1; +    const ERROR_LOGIN      = 2; +    const ERROR_NOT_EXISTS = 3;    // script not exists +    const ERROR_INSTALL    = 4;    // script installation +    const ERROR_ACTIVATE   = 5;    // script activation +    const ERROR_DELETE     = 6;    // script deletion +    const ERROR_INTERNAL   = 7;    // internal error +    const ERROR_DEACTIVATE = 8;    // script activation +    const ERROR_OTHER      = 255;  // other/unknown error +      /**       * Object constructor @@ -57,10 +56,11 @@ class rcube_sieve       * @param boolean Enable/disable debugging       * @param string  Proxy authentication identifier       * @param string  Proxy authentication password +     * @param array   List of options to pass to stream_context_create().       */      public function __construct($username, $password='', $host='localhost', $port=2000,          $auth_type=null, $usetls=true, $disabled=array(), $debug=false, -        $auth_cid=null, $auth_pw=null) +        $auth_cid=null, $auth_pw=null, $options=array())      {          $this->sieve = new Net_Sieve(); @@ -68,8 +68,8 @@ class rcube_sieve              $this->sieve->setDebug(true, array($this, 'debug_handler'));          } -        if (PEAR::isError($this->sieve->connect($host, $port, null, $usetls))) { -            return $this->_set_error(SIEVE_ERROR_CONNECTION); +        if (PEAR::isError($this->sieve->connect($host, $port, $options, $usetls))) { +            return $this->_set_error(self::ERROR_CONNECTION);          }          if (!empty($auth_cid)) { @@ -81,7 +81,7 @@ class rcube_sieve          if (PEAR::isError($this->sieve->login($username, $password,              $auth_type ? strtoupper($auth_type) : null, $authz))          ) { -            return $this->_set_error(SIEVE_ERROR_LOGIN); +            return $this->_set_error(self::ERROR_LOGIN);          }          $this->exts = $this->get_extensions(); @@ -116,10 +116,10 @@ class rcube_sieve      public function save($name = null)      {          if (!$this->sieve) -            return $this->_set_error(SIEVE_ERROR_INTERNAL); +            return $this->_set_error(self::ERROR_INTERNAL);          if (!$this->script) -            return $this->_set_error(SIEVE_ERROR_INTERNAL); +            return $this->_set_error(self::ERROR_INTERNAL);          if (!$name)              $name = $this->current; @@ -130,7 +130,7 @@ class rcube_sieve              $script = '/* empty script */';          if (PEAR::isError($this->sieve->installScript($name, $script))) -            return $this->_set_error(SIEVE_ERROR_INSTALL); +            return $this->_set_error(self::ERROR_INSTALL);          return true;      } @@ -141,13 +141,13 @@ class rcube_sieve      public function save_script($name, $content = null)      {          if (!$this->sieve) -            return $this->_set_error(SIEVE_ERROR_INTERNAL); +            return $this->_set_error(self::ERROR_INTERNAL);          if (!$content)              $content = '/* empty script */';          if (PEAR::isError($this->sieve->installScript($name, $content))) -            return $this->_set_error(SIEVE_ERROR_INSTALL); +            return $this->_set_error(self::ERROR_INSTALL);          return true;      } @@ -158,13 +158,13 @@ class rcube_sieve      public function activate($name = null)      {          if (!$this->sieve) -            return $this->_set_error(SIEVE_ERROR_INTERNAL); +            return $this->_set_error(self::ERROR_INTERNAL);          if (!$name)              $name = $this->current;          if (PEAR::isError($this->sieve->setActive($name))) -            return $this->_set_error(SIEVE_ERROR_ACTIVATE); +            return $this->_set_error(self::ERROR_ACTIVATE);          return true;      } @@ -175,10 +175,10 @@ class rcube_sieve      public function deactivate()      {          if (!$this->sieve) -            return $this->_set_error(SIEVE_ERROR_INTERNAL); +            return $this->_set_error(self::ERROR_INTERNAL);          if (PEAR::isError($this->sieve->setActive(''))) -            return $this->_set_error(SIEVE_ERROR_DEACTIVATE); +            return $this->_set_error(self::ERROR_DEACTIVATE);          return true;      } @@ -189,7 +189,7 @@ class rcube_sieve      public function remove($name = null)      {          if (!$this->sieve) -            return $this->_set_error(SIEVE_ERROR_INTERNAL); +            return $this->_set_error(self::ERROR_INTERNAL);          if (!$name)              $name = $this->current; @@ -197,10 +197,10 @@ class rcube_sieve          // script must be deactivated first          if ($name == $this->sieve->getActive())              if (PEAR::isError($this->sieve->setActive(''))) -                return $this->_set_error(SIEVE_ERROR_DELETE); +                return $this->_set_error(self::ERROR_DELETE);          if (PEAR::isError($this->sieve->removeScript($name))) -            return $this->_set_error(SIEVE_ERROR_DELETE); +            return $this->_set_error(self::ERROR_DELETE);          if ($name == $this->current)              $this->current = null; @@ -217,9 +217,14 @@ class rcube_sieve              return $this->exts;          if (!$this->sieve) -            return $this->_set_error(SIEVE_ERROR_INTERNAL); +            return $this->_set_error(self::ERROR_INTERNAL);          $ext = $this->sieve->getExtensions(); + +        if (PEAR::isError($ext)) { +            return array(); +        } +          // we're working on lower-cased names          $ext = array_map('strtolower', (array) $ext); @@ -241,12 +246,12 @@ class rcube_sieve          if (!$this->list) {              if (!$this->sieve) -                return $this->_set_error(SIEVE_ERROR_INTERNAL); +                return $this->_set_error(self::ERROR_INTERNAL);              $list = $this->sieve->listScripts();              if (PEAR::isError($list)) -                return $this->_set_error(SIEVE_ERROR_OTHER); +                return $this->_set_error(self::ERROR_OTHER);              $this->list = $list;          } @@ -260,7 +265,7 @@ class rcube_sieve      public function get_active()      {          if (!$this->sieve) -            return $this->_set_error(SIEVE_ERROR_INTERNAL); +            return $this->_set_error(self::ERROR_INTERNAL);          return $this->sieve->getActive();      } @@ -271,7 +276,7 @@ class rcube_sieve      public function load($name)      {          if (!$this->sieve) -            return $this->_set_error(SIEVE_ERROR_INTERNAL); +            return $this->_set_error(self::ERROR_INTERNAL);          if ($this->current == $name)              return true; @@ -279,7 +284,7 @@ class rcube_sieve          $script = $this->sieve->getScript($name);          if (PEAR::isError($script)) -            return $this->_set_error(SIEVE_ERROR_OTHER); +            return $this->_set_error(self::ERROR_OTHER);          // try to parse from Roundcube format          $this->script = $this->_parse($script); @@ -295,7 +300,7 @@ class rcube_sieve      public function load_script($script)      {          if (!$this->sieve) -            return $this->_set_error(SIEVE_ERROR_INTERNAL); +            return $this->_set_error(self::ERROR_INTERNAL);          // try to parse from Roundcube format          $this->script = $this->_parse($script); @@ -340,12 +345,12 @@ class rcube_sieve      public function get_script($name)      {          if (!$this->sieve) -            return $this->_set_error(SIEVE_ERROR_INTERNAL); +            return $this->_set_error(self::ERROR_INTERNAL);          $content = $this->sieve->getScript($name);          if (PEAR::isError($content)) -            return $this->_set_error(SIEVE_ERROR_OTHER); +            return $this->_set_error(self::ERROR_OTHER);          return $content;      } @@ -356,13 +361,13 @@ class rcube_sieve      public function copy($name, $copy)      {          if (!$this->sieve) -            return $this->_set_error(SIEVE_ERROR_INTERNAL); +            return $this->_set_error(self::ERROR_INTERNAL);          if ($copy) {              $content = $this->sieve->getScript($copy);              if (PEAR::isError($content)) -                return $this->_set_error(SIEVE_ERROR_OTHER); +                return $this->_set_error(self::ERROR_OTHER);          }          return $this->save_script($name, $content); diff --git a/plugins/managesieve/lib/Roundcube/rcube_sieve_engine.php b/plugins/managesieve/lib/Roundcube/rcube_sieve_engine.php index 9900f16b5..302c7c7a1 100644 --- a/plugins/managesieve/lib/Roundcube/rcube_sieve_engine.php +++ b/plugins/managesieve/lib/Roundcube/rcube_sieve_engine.php @@ -73,7 +73,7 @@ class rcube_sieve_engine       */      function __construct($plugin)      { -        $this->rc     = rcmail::get_instance(); +        $this->rc     = rcube::get_instance();          $this->plugin = $plugin;      } @@ -91,6 +91,76 @@ class rcube_sieve_engine              'filtersetform'  => array($this, 'filterset_form'),          )); +        // connect to managesieve server +        $error = $this->connect($_SESSION['username'], $this->rc->decrypt($_SESSION['password'])); + +        // load current/active script +        if (!$error) { +            // Get list of scripts +            $list = $this->list_scripts(); + +            // reset current script when entering filters UI (#1489412) +            if ($this->rc->action == 'plugin.managesieve') { +                $this->rc->session->remove('managesieve_current'); +            } + +            if ($mode != 'vacation') { +                if (!empty($_GET['_set']) || !empty($_POST['_set'])) { +                    $script_name = rcube_utils::get_input_value('_set', rcube_utils::INPUT_GPC, true); +                } +                else if (!empty($_SESSION['managesieve_current'])) { +                    $script_name = $_SESSION['managesieve_current']; +                } +            } + +            $error = $this->load_script($script_name); +        } + +        // finally set script objects +        if ($error) { +            switch ($error) { +                case rcube_sieve::ERROR_CONNECTION: +                case rcube_sieve::ERROR_LOGIN: +                    $this->rc->output->show_message('managesieve.filterconnerror', 'error'); +                    rcube::raise_error(array('code' => 403, 'type' => 'php', +                        'file' => __FILE__, 'line' => __LINE__, +                        'message' => "Unable to connect to managesieve on $host:$port"), true, false); +                    break; + +                default: +                    $this->rc->output->show_message('managesieve.filterunknownerror', 'error'); +                    break; +            } + +            // reload interface in case of possible error when specified script wasn't found (#1489412) +            if ($script_name !== null && !empty($list) && !in_array($script_name, $list)) { +                $this->rc->output->command('reload', 500); +            } + +            // to disable 'Add filter' button set env variable +            $this->rc->output->set_env('filterconnerror', true); +            $this->script = array(); +        } +        else { +            $this->exts = $this->sieve->get_extensions(); +            $this->init_script(); +            $this->rc->output->set_env('currentset', $this->sieve->current); +            $_SESSION['managesieve_current'] = $this->sieve->current; +        } + +        return $error; +    } + +    /** +     * Connect to configured managesieve server +     * +     * @param string $username User login +     * @param string $password User password +     * +     * @return int Connection status: 0 on success, >0 on failure +     */ +    public function connect($username, $password) +    {          // Get connection parameters          $host = $this->rc->config->get('managesieve_host', 'localhost');          $port = $this->rc->config->get('managesieve_port'); @@ -112,8 +182,8 @@ class rcube_sieve_engine          }          $plugin = $this->rc->plugins->exec_hook('managesieve_connect', array( -            'user'      => $_SESSION['username'], -            'password'  => $this->rc->decrypt($_SESSION['password']), +            'user'      => $username, +            'password'  => $password,              'host'      => $host,              'port'      => $port,              'usetls'    => $tls, @@ -122,6 +192,7 @@ class rcube_sieve_engine              'debug'     => $this->rc->config->get('managesieve_debug', false),              'auth_cid'  => $this->rc->config->get('managesieve_auth_cid'),              'auth_pw'   => $this->rc->config->get('managesieve_auth_pw'), +            'socket_options' => $this->rc->config->get('managesieve_conn_options'),          ));          // try to connect to managesieve server and to fetch the script @@ -135,97 +206,65 @@ class rcube_sieve_engine              $plugin['disabled'],              $plugin['debug'],              $plugin['auth_cid'], -            $plugin['auth_pw'] +            $plugin['auth_pw'], +            $plugin['socket_options']          ); -        if (!($error = $this->sieve->error())) { -            // Get list of scripts -            $list = $this->list_scripts(); +        return $this->sieve->error(); +    } -            // reset current script when entering filters UI (#1489412) -            if ($this->rc->action == 'plugin.managesieve') { -                $this->rc->session->remove('managesieve_current'); -            } +    /** +     * Load specified (or active) script +     * +     * @param string $script_name Optional script name +     * +     * @return int Connection status: 0 on success, >0 on failure +     */ +    public function load_script($script_name = null) +    { +        // Get list of scripts +        $list = $this->list_scripts(); -            if ($mode != 'vacation') { -                if (!empty($_GET['_set']) || !empty($_POST['_set'])) { -                    $script_name = rcube_utils::get_input_value('_set', rcube_utils::INPUT_GPC, true); -                } -                else if (!empty($_SESSION['managesieve_current'])) { -                    $script_name = $_SESSION['managesieve_current']; -                } +        if ($script_name === null || $script_name === '') { +            // get (first) active script +            if (!empty($this->active[0])) { +               $script_name = $this->active[0];              } +            else if ($list) { +                $script_name = $list[0]; +            } +            // create a new (initial) script +            else { +                // if script not exists build default script contents +                $script_file = $this->rc->config->get('managesieve_default'); +                $script_name = $this->rc->config->get('managesieve_script_name'); -            if ($script_name === null || $script_name === '') { -                // get (first) active script -                if (!empty($this->active[0])) { -                    $script_name = $this->active[0]; +                if (empty($script_name)) { +                    $script_name = 'roundcube';                  } -                else if ($list) { -                    $script_name = $list[0]; -                } -                // create a new (initial) script -                else { -                    // if script not exists build default script contents -                    $script_file = $this->rc->config->get('managesieve_default'); -                    $script_name = $this->rc->config->get('managesieve_script_name'); -                    if (empty($script_name)) -                        $script_name = 'roundcube'; - -                    if ($script_file && is_readable($script_file)) -                        $content = file_get_contents($script_file); - -                    // add script and set it active -                    if ($this->sieve->save_script($script_name, $content)) { -                        $this->activate_script($script_name); -                        $this->list[] = $script_name; -                    } +                if ($script_file && is_readable($script_file)) { +                    $content = file_get_contents($script_file);                  } -            } -            if ($script_name) { -                $this->sieve->load($script_name); +                // add script and set it active +                if ($this->sieve->save_script($script_name, $content)) { +                    $this->activate_script($script_name); +                    $this->list[] = $script_name; +                }              } - -            $error = $this->sieve->error();          } -        // finally set script objects -        if ($error) { -            switch ($error) { -                case SIEVE_ERROR_CONNECTION: -                case SIEVE_ERROR_LOGIN: -                    $this->rc->output->show_message('managesieve.filterconnerror', 'error'); -                    rcube::raise_error(array('code' => 403, 'type' => 'php', -                        'file' => __FILE__, 'line' => __LINE__, -                        'message' => "Unable to connect to managesieve on $host:$port"), true, false); -                    break; - -                default: -                    $this->rc->output->show_message('managesieve.filterunknownerror', 'error'); -                    break; -            } - -            // reload interface in case of possible error when specified script wasn't found (#1489412) -            if ($script_name !== null && !empty($list) && !in_array($script_name, $list)) { -                $this->rc->output->command('reload', 500); -            } - -            // to disable 'Add filter' button set env variable -            $this->rc->output->set_env('filterconnerror', true); -            $this->script = array(); -        } -        else { -            $this->exts = $this->sieve->get_extensions(); -            $this->init_script(); -            $this->rc->output->set_env('currentset', $this->sieve->current); -            $_SESSION['managesieve_current'] = $this->sieve->current; +        if ($script_name) { +            $this->sieve->load($script_name);          } -        return $error; +        return $this->sieve->error();      } +    /** +     * User interface actions handler +     */      function actions()      {          $error = $this->start(); @@ -1826,17 +1865,22 @@ class rcube_sieve_engine          $out .= '</div>';          // mailbox select -        if ($action['type'] == 'fileinto') +        if ($action['type'] == 'fileinto') {              $mailbox = $this->mod_mailbox($action['target'], 'out'); -        else +            // make sure non-existing (or unsubscribed) mailbox is listed (#1489956) +            $additional = array($mailbox); +        } +        else {              $mailbox = ''; +        }          $select = $this->rc->folder_selector(array( -            'realnames' => false, -            'maxlength' => 100, -            'id' => 'action_mailbox' . $id, -            'name' => "_action_mailbox[$id]", -            'style' => 'display:'.(empty($action['type']) || $action['type'] == 'fileinto' ? 'inline' : 'none') +            'realnames'  => false, +            'maxlength'  => 100, +            'id'         => 'action_mailbox' . $id, +            'name'       => "_action_mailbox[$id]", +            'style'      => 'display:'.(empty($action['type']) || $action['type'] == 'fileinto' ? 'inline' : 'none'), +            'additional' => $additional,          ));          $out .= $select->show($mailbox);          $out .= '</td>'; diff --git a/plugins/managesieve/lib/Roundcube/rcube_sieve_vacation.php b/plugins/managesieve/lib/Roundcube/rcube_sieve_vacation.php index 636b5fcc1..10aaea0e9 100644 --- a/plugins/managesieve/lib/Roundcube/rcube_sieve_vacation.php +++ b/plugins/managesieve/lib/Roundcube/rcube_sieve_vacation.php @@ -23,6 +23,8 @@  class rcube_sieve_vacation extends rcube_sieve_engine  { +    protected $error; +      function actions()      {          $error = $this->start('vacation'); @@ -32,7 +34,6 @@ class rcube_sieve_vacation extends rcube_sieve_engine              $this->vacation_rule();              $this->vacation_post();          } -          $this->plugin->add_label('vacation.saving');          $this->rc->output->add_handlers(array(              'vacationform' => array($this, 'vacation_form'), @@ -55,11 +56,23 @@ class rcube_sieve_vacation extends rcube_sieve_engine          // find (first) vacation rule          foreach ($this->script as $idx => $rule) {              if (empty($this->vacation) && !empty($rule['actions']) && $rule['actions'][0]['type'] == 'vacation') { +                foreach ($rule['actions'] as $act) { +                    if ($act['type'] == 'discard' || $act['type'] == 'keep') { +                        $action = $act['type']; +                    } +                    else if ($act['type'] == 'redirect') { +                        $action = $act['copy'] ? 'copy' : 'redirect'; +                        $target = $act['target']; +                    } +                } +                  $this->vacation = array_merge($rule['actions'][0], array(                          'idx'      => $idx,                          'disabled' => $rule['disabled'],                          'name'     => $rule['name'],                          'tests'    => $rule['tests'], +                        'action'   => $action ?: 'keep', +                        'target'   => $target,                  ));              }              else { @@ -76,6 +89,17 @@ class rcube_sieve_vacation extends rcube_sieve_engine              return;          } +        $date_extension  = in_array('date', $this->exts); +        $regex_extension = in_array('regex', $this->exts); + +        // set user's timezone +        try { +            $timezone = new DateTimeZone($this->rc->config->get('timezone', 'GMT')); +        } +        catch (Exception $e) { +            $timezone = new DateTimeZone('GMT'); +        } +          $status        = rcube_utils::get_input_value('vacation_status', rcube_utils::INPUT_POST);          $subject       = rcube_utils::get_input_value('vacation_subject', rcube_utils::INPUT_POST, true);          $reason        = rcube_utils::get_input_value('vacation_reason', rcube_utils::INPUT_POST, true); @@ -84,7 +108,12 @@ class rcube_sieve_vacation extends rcube_sieve_engine          $interval_type = rcube_utils::get_input_value('vacation_interval_type', rcube_utils::INPUT_POST);          $date_from     = rcube_utils::get_input_value('vacation_datefrom', rcube_utils::INPUT_POST);          $date_to       = rcube_utils::get_input_value('vacation_dateto', rcube_utils::INPUT_POST); +        $time_from     = rcube_utils::get_input_value('vacation_timefrom', rcube_utils::INPUT_POST); +        $time_to       = rcube_utils::get_input_value('vacation_timeto', rcube_utils::INPUT_POST);          $after         = rcube_utils::get_input_value('vacation_after', rcube_utils::INPUT_POST); +        $action        = rcube_utils::get_input_value('vacation_action', rcube_utils::INPUT_POST); +        $target        = rcube_utils::get_input_value('action_target', rcube_utils::INPUT_POST, true); +        $target_domain = rcube_utils::get_input_value('action_domain', rcube_utils::INPUT_POST);          $interval_type                   = $interval_type == 'seconds' ? 'seconds' : 'days';          $vacation_action['type']         = 'vacation'; @@ -107,33 +136,65 @@ class rcube_sieve_vacation extends rcube_sieve_engine          }          if ($vacation_action['reason'] == '') { -            $error = 'managesieve.cannotbeempty'; +            $error = 'managesieve.emptyvacationbody';          } +          if ($vacation_action[$interval_type] && !preg_match('/^[0-9]+$/', $vacation_action[$interval_type])) {              $error = 'managesieve.forbiddenchars';          } -        foreach (array('date_from', 'date_to') as $var) { -            $date = $$var; +        // find and remove existing date/regex/true rules +        foreach ((array) $vacation_tests as $idx => $t) { +            if ($t['test'] == 'currentdate' || $t['test'] == 'true' +                || ($t['test'] == 'header' && $t['type'] == 'regex' && $t['arg1'] == 'received') +            ) { +                unset($vacation_tests[$idx]); +            } +        } -            if ($date && ($dt = rcube_utils::anytodatetime($date))) { -                $type = 'value-' . ($var == 'date_from' ? 'ge' : 'le'); -                $test = array( -                    'test' => 'currentdate', -                    'part' => 'date', -                    'type' => $type, -                    'arg'  => $dt->format('Y-m-d'), -                ); +        if ($date_extension) { +            foreach (array('date_from', 'date_to') as $var) { +                $time = ${str_replace('date', 'time', $var)}; +                $date = trim($$var . ' ' . $time); -                // find existing date rule -                foreach ((array) $vacation_tests as $idx => $t) { -                    if ($t['test'] == 'currentdate' && $t['part'] == 'date' && $t['type'] == $type) { -                        $vacation_tests[$idx] = $test; -                        continue 2; +                if ($date && ($dt = rcube_utils::anytodatetime($date, $timezone))) { +                    if ($time) { +                        $vacation_tests[] = array( +                            'test' => 'currentdate', +                            'part' => 'iso8601', +                            'type' => 'value-' . ($var == 'date_from' ? 'ge' : 'le'), +                            'zone' => $dt->format('O'), +                            'arg'  => str_replace('+00:00', 'Z', strtoupper($dt->format('c'))), +                        ); +                    } +                    else { +                        $vacation_tests[] = array( +                            'test' => 'currentdate', +                            'part' => 'date', +                            'type' => 'value-' . ($var == 'date_from' ? 'ge' : 'le'), +                            'zone' => $dt->format('O'), +                            'arg'  => $dt->format('Y-m-d'), +                        );                      }                  } +            } +        } +        else if ($regex_extension) { +            // Add date range rules if range specified +            if ($date_from && $date_to) { +                if ($tests = self::build_regexp_tests($date_from, $date_to, $error)) { +                    $vacation_tests = array_merge($vacation_tests, $tests); +                } +            } +        } + +        if ($action == 'redirect' || $action == 'copy') { +            if ($target_domain) { +                $target .= '@' . $target_domain; +            } -                $vacation_tests[] = $test; +            if (empty($target) || !rcube_utils::check_email($target)) { +                $error = 'noemailwarning';              }          } @@ -148,9 +209,17 @@ class rcube_sieve_vacation extends rcube_sieve_engine              $rule['type']       = 'if';              $rule['name']       = $rule['name'] ?: $this->plugin->gettext('vacation');              $rule['disabled']   = $status == 'off'; -            $rule['actions'][0] = $vacation_action;              $rule['tests']      = $vacation_tests; -            $rule['join']       = count($vacation_tests) > 1; +            $rule['join']       = $date_extension ? count($vacation_tests) > 1 : false; +            $rule['actions']    = array($vacation_action); + +            if ($action && $action != 'keep') { +                $rule['actions'][] = array( +                    'type'   => $action == 'discard' ? 'discard' : 'redirect', +                    'copy'   => $action == 'copy', +                    'target' => $action != 'discard' ? $target : '', +                ); +            }              // reset original vacation rule              if (isset($this->vacation['idx'])) { @@ -202,6 +271,7 @@ class rcube_sieve_vacation extends rcube_sieve_engine      {          // check supported extensions          $date_extension    = in_array('date', $this->exts); +        $regex_extension   = in_array('regex', $this->exts);          $seconds_extension = in_array('vacation-seconds', $this->exts);          // build FORM tag @@ -216,18 +286,26 @@ class rcube_sieve_vacation extends rcube_sieve_engine              ) + $attrib);          // form elements -        $subject   = new html_inputfield(array('name' => 'vacation_subject', 'size' => 50)); -        $reason    = new html_textarea(array('name' => 'vacation_reason', 'cols' => 60, 'rows' => 8)); -        $interval  = new html_inputfield(array('name' => 'vacation_interval', 'size' => 5)); -        $addresses = '<textarea name="vacation_addresses" data-type="list" data-size="30" style="display: none">' +        $subject   = new html_inputfield(array('name' => 'vacation_subject', 'id' => 'vacation_subject', 'size' => 50)); +        $reason    = new html_textarea(array('name' => 'vacation_reason', 'id' => 'vacation_reason', 'cols' => 60, 'rows' => 8)); +        $interval  = new html_inputfield(array('name' => 'vacation_interval', 'id' => 'vacation_interval', 'size' => 5)); +        $addresses = '<textarea name="vacation_addresses" id="vacation_addresses" data-type="list" data-size="30" style="display: none">'              . rcube::Q(implode("\n", (array) $this->vacation['addresses']), 'strict', false) . '</textarea>'; -        $status    = new html_select(array('name' => 'vacation_status')); +        $status    = new html_select(array('name' => 'vacation_status', 'id' => 'vacation_status')); +        $action    = new html_select(array('name' => 'vacation_action', 'id' => 'vacation_action', 'onchange' => 'vacation_action_select()'));          $status->add($this->plugin->gettext('vacation.on'), 'on');          $status->add($this->plugin->gettext('vacation.off'), 'off'); +        $action->add($this->plugin->gettext('vacation.keep'), 'keep'); +        $action->add($this->plugin->gettext('vacation.discard'), 'discard'); +        $action->add($this->plugin->gettext('vacation.redirect'), 'redirect'); +        if (in_array('copy', $this->exts)) { +            $action->add($this->plugin->gettext('vacation.copy'), 'copy'); +        } +          if ($this->rc->config->get('managesieve_vacation') != 2 && count($this->vacation['list'])) { -            $after = new html_select(array('name' => 'vacation_after')); +            $after = new html_select(array('name' => 'vacation_after', 'id' => 'vacation_after'));              $after->add('', '');              foreach ($this->vacation['list'] as $idx => $rule) { @@ -246,19 +324,75 @@ class rcube_sieve_vacation extends rcube_sieve_engine              $interval_txt .= ' ' . $this->plugin->gettext('days');          } -        if ($date_extension) { -            $date_from   = new html_inputfield(array('name' => 'vacation_datefrom', 'class' => 'datepicker', 'size' => 12)); -            $date_to     = new html_inputfield(array('name' => 'vacation_dateto', 'class' => 'datepicker', 'size' => 12)); +        if ($date_extension || $regex_extension) { +            $date_from   = new html_inputfield(array('name' => 'vacation_datefrom', 'id' => 'vacation_datefrom', 'class' => 'datepicker', 'size' => 12)); +            $date_to     = new html_inputfield(array('name' => 'vacation_dateto', 'id' => 'vacation_dateto', 'class' => 'datepicker', 'size' => 12));              $date_format = $this->rc->config->get('date_format', 'Y-m-d'); +        } + +        if ($date_extension) { +            $time_from   = new html_inputfield(array('name' => 'vacation_timefrom', 'id' => 'vacation_timefrom', 'size' => 6)); +            $time_to     = new html_inputfield(array('name' => 'vacation_timeto', 'id' => 'vacation_timeto', 'size' => 6)); +            $time_format = $this->rc->config->get('time_format', 'H:i'); +            $date_value  = array();              foreach ((array) $this->vacation['tests'] as $test) { -                if ($test['test'] == 'currentdate' && $test['part'] == 'date') { -                    $date = $this->rc->format_date($test['arg'], $date_format, false); -                    $date_value[$test['type'] == 'value-ge' ? 'from' : 'to'] = $date; +                if ($test['test'] == 'currentdate') { +                    $idx = $test['type'] == 'value-ge' ? 'from' : 'to'; + +                    if ($test['part'] == 'date') { +                        $date_value[$idx]['date'] = $test['arg']; +                    } +                    else if ($test['part'] == 'iso8601') { +                        $date_value[$idx]['datetime'] = $test['arg']; +                    } +                } +            } + +            foreach ($date_value as $idx => $value) { +                $date = $value['datetime'] ?: $value['date']; +                $date_value[$idx] = $this->rc->format_date($date, $date_format, false); + +                if (!empty($value['datetime'])) { +                    $date_value['time_' . $idx] = $this->rc->format_date($date, $time_format, true); +                } +            } +        } +        else if ($regex_extension) { +            // Sieve 'date' extension not available, read start/end from RegEx based rules instead +            if ($date_tests = self::parse_regexp_tests($this->vacation['tests'])) { +                $date_value['from'] = $this->rc->format_date($date_tests['from'], $date_format, false); +                $date_value['to']   = $this->rc->format_date($date_tests['to'], $date_format, false); +            } +        } + +        // force domain selection in redirect email input +        $domains  = (array) $this->rc->config->get('managesieve_domains'); +        $redirect = $this->vacation['action'] == 'redirect' || $this->vacation['action'] == 'copy'; + +        if (!empty($domains)) { +            sort($domains); + +            $domain_select = new html_select(array('name' => 'action_domain', 'id' => 'action_domain')); +            $domain_select->add(array_combine($domains, $domains)); + +            if ($redirect && $this->vacation['target']) { +                $parts = explode('@', $this->vacation['target']); +                if (!empty($parts)) { +                    $this->vacation['domain'] = array_pop($parts); +                    $this->vacation['target'] = implode('@', $parts);                  }              }          } +        // redirect target +        $action_target = ' <span id="action_target_span" style="display:' . ($redirect ? 'inline' : 'none') . '">' +            . '<input type="text" name="action_target" id="action_target"' +            . ' value="' .($redirect ? rcube::Q($this->vacation['target'], 'strict', false) : '') . '"' +            . (!empty($domains) ? ' size="20"' : ' size="35"') . '/>' +            . (!empty($domains) ? ' @ ' . $domain_select->show($this->vacation['domain']) : '') +            . '</span>'; +          // Message tab          $table = new html_table(array('cols' => 2)); @@ -267,37 +401,336 @@ class rcube_sieve_vacation extends rcube_sieve_engine          $table->add('title', html::label('vacation_reason', $this->plugin->gettext('vacation.body')));          $table->add(null, $reason->show($this->vacation['reason'])); -        if ($date_extension) { -            $table->add('title', html::label('vacation_datefrom', $this->plugin->gettext('vacation.dates'))); -            $table->add(null, -                $this->plugin->gettext('vacation.from'). ' ' . $date_from->show($date_value['from']) -                . ' ' . $this->plugin->gettext('vacation.to'). ' ' . $date_to->show($date_value['to']) -            ); +        if ($date_extension || $regex_extension) { +            $table->add('title', html::label('vacation_datefrom', $this->plugin->gettext('vacation.start'))); +            $table->add(null, $date_from->show($date_value['from']) . ($time_from ? ' ' . $time_from->show($date_value['time_from']) : '')); +            $table->add('title', html::label('vacation_dateto', $this->plugin->gettext('vacation.end'))); +            $table->add(null, $date_to->show($date_value['to']) . ($time_to ? ' ' . $time_to->show($date_value['time_to']) : ''));          }          $table->add('title', html::label('vacation_status', $this->plugin->gettext('vacation.status'))); -        $table->add(null, $status->show($this->vacation['disabled'] ? 'off' : 'on')); +        $table->add(null, $status->show(!isset($this->vacation['disabled']) || $this->vacation['disabled'] ? 'off' : 'on'));          $out .= html::tag('fieldset', $class, html::tag('legend', null, $this->plugin->gettext('vacation.reply')) . $table->show($attrib));          // Advanced tab          $table = new html_table(array('cols' => 2)); -        $table->add('title', $this->plugin->gettext('vacation.addresses')); +        $table->add('title', html::label('vacation_addresses', $this->plugin->gettext('vacation.addresses')));          $table->add(null, $addresses); -        $table->add('title', $this->plugin->gettext('vacation.interval')); +        $table->add('title', html::label('vacation_interval', $this->plugin->gettext('vacation.interval')));          $table->add(null, $interval_txt); +          if ($after) { -            $table->add('title', $this->plugin->gettext('vacation.after')); +            $table->add('title', html::label('vacation_after', $this->plugin->gettext('vacation.after')));              $table->add(null, $after->show($this->vacation['idx'] - 1));          } +        $table->add('title', html::label('vacation_action', $this->plugin->gettext('vacation.action'))); +        $table->add('vacation', $action->show($this->vacation['action']) . $action_target); +          $out .= html::tag('fieldset', $class, html::tag('legend', null, $this->plugin->gettext('vacation.advanced')) . $table->show($attrib));          $out .= '</form>';          $this->rc->output->add_gui_object('sieveform', $form_id); +        if ($time_format) { +            $this->rc->output->set_env('time_format', $time_format); +        } +          return $out;      } + +    public static function build_regexp_tests($date_from, $date_to, &$error) +    { +        $tests    = array(); +        $dt_from  = rcube_utils::anytodatetime($date_from); +        $dt_to    = rcube_utils::anytodatetime($date_to); +        $interval = $dt_from->diff($dt_to); + +        if ($interval->invert || $interval->days > 365) { +            $error = 'managesieve.invaliddateformat'; +            return; +        } + +        $dt_i     = $dt_from; +        $interval = new DateInterval('P1D'); +        $matchexp = ''; + +        while (!$dt_i->diff($dt_to)->invert) { +            $days     = (int) $dt_i->format('d'); +            $matchexp .= $days < 10 ? "[ 0]$days" : $days; + +            if ($days == $dt_i->format('t') || $dt_i->diff($dt_to)->days == 0) { +                $test = array( +                    'test' => 'header', +                    'type' => 'regex', +                    'arg1' => 'received', +                    'arg2' => '('.$matchexp.') '.$dt_i->format('M Y') +                ); + +                $tests[]  = $test; +                $matchexp = ''; +            } +            else { +                $matchexp .= '|'; +            } + +            $dt_i->add($interval); +        } + +        return $tests; +    } + +    public static function parse_regexp_tests($tests) +    { +        $rx_from = '/^\(([0-9]{2}).*\)\s([A-Za-z]+)\s([0-9]{4})/'; +        $rx_to   = '/^\(.*([0-9]{2})\)\s([A-Za-z]+)\s([0-9]{4})/'; +        $result  = array(); + +        foreach ((array) $tests as $test) { +            if ($test['test'] == 'header' && $test['type'] == 'regex' && $test['arg1'] == 'received') { +                $textexp = preg_replace('/\[ ([^\]]*)\]/', '0', $test['arg2']); + +                if (!$result['from'] && preg_match($rx_from, $textexp, $matches)) { +                    $result['from'] = $matches[1]." ".$matches[2]." ".$matches[3]; +                } + +                if (preg_match($rx_to, $textexp, $matches)) { +                    $result['to'] = $matches[1]." ".$matches[2]." ".$matches[3]; +                } +            } +        } + +        return $result; +    } + +    /** +     * API: get vacation rule +     * +     * @return array Vacation rule information +     */ +    public function get_vacation() +    { +        $this->exts = $this->sieve->get_extensions(); +        $this->init_script(); +        $this->vacation_rule(); + +        // check supported extensions +        $date_extension    = in_array('date', $this->exts); +        $regex_extension   = in_array('regex', $this->exts); +        $seconds_extension = in_array('vacation-seconds', $this->exts); + +        // set user's timezone +        try { +            $timezone = new DateTimeZone($this->rc->config->get('timezone', 'GMT')); +        } +        catch (Exception $e) { +            $timezone = new DateTimeZone('GMT'); +        } + +        if ($date_extension) { +            $date_value = array(); +            foreach ((array) $this->vacation['tests'] as $test) { +                if ($test['test'] == 'currentdate') { +                    $idx = $test['type'] == 'value-ge' ? 'start' : 'end'; + +                    if ($test['part'] == 'date') { +                        $date_value[$idx]['date'] = $test['arg']; +                    } +                    else if ($test['part'] == 'iso8601') { +                        $date_value[$idx]['datetime'] = $test['arg']; +                    } +                } +            } + +            foreach ($date_value as $idx => $value) { +                $$idx = new DateTime($value['datetime'] ?: $value['date'], $timezone); +            } +        } +        else if ($regex_extension) { +            // Sieve 'date' extension not available, read start/end from RegEx based rules instead +            if ($date_tests = self::parse_regexp_tests($this->vacation['tests'])) { +                $from = new DateTime($date_tests['from'] . ' ' . '00:00:00', $timezone); +                $to   = new DateTime($date_tests['to'] . ' ' . '23:59:59', $timezone); +            } +        } + +        if (isset($this->vacation['seconds'])) { +            $interval = $this->vacation['seconds'] . 's'; +        } +        else if (isset($this->vacation['days'])) { +            $interval = $this->vacation['days'] . 'd'; +        } + +        $vacation = array( +            'supported' => $this->exts, +            'interval'  => $interval, +            'start'     => $start, +            'end'       => $end, +            'enabled'   => $this->vacation['reason'] && empty($this->vacation['disabled']), +            'message'   => $this->vacation['reason'], +            'subject'   => $this->vacation['subject'], +            'action'    => $this->vacation['action'], +            'target'    => $this->vacation['target'], +            'addresses' => $this->vacation['addresses'], +        ); + +        return $vacation; +    } + +    /** +     * API: set vacation rule +     * +     * @param array $vacation Vacation rule information (see self::get_vacation()) +     * +     * @return bool True on success, False on failure +     */ +    public function set_vacation($data) +    { +        $this->exts  = $this->sieve->get_extensions(); +        $this->error = false; + +        $this->init_script(); +        $this->vacation_rule(); + +        // check supported extensions +        $date_extension    = in_array('date', $this->exts); +        $regex_extension   = in_array('regex', $this->exts); +        $seconds_extension = in_array('vacation-seconds', $this->exts); + +        $vacation['type']      = 'vacation'; +        $vacation['reason']    = $this->strip_value(str_replace("\r\n", "\n", $data['message'])); +        $vacation['addresses'] = $data['addresses']; +        $vacation['subject']   = $data['subject']; +        $vacation_tests        = (array) $this->vacation['tests']; + +        foreach ((array) $vacation['addresses'] as $aidx => $address) { +            $vacation['addresses'][$aidx] = $address = trim($address); + +            if (empty($address)) { +                unset($vacation['addresses'][$aidx]); +            } +            else if (!rcube_utils::check_email($address)) { +                $this->error = "Invalid address in vacation addresses: $address"; +                return false; +            } +        } + +        if ($vacation['reason'] == '') { +            $this->error = "No vacation message specified"; +            return false; +        } + +        if ($data['interval']) { +            if (!preg_match('/^([0-9]+)\s*([sd])$/', $data['interval'], $m)) { +                $this->error = "Invalid vacation interval value: " . $data['interval']; +                return false; +            } +            else if ($m[1]) { +                $vacation[strtolower($m[2]) == 's' ? 'seconds' : 'days'] = $m[1]; +            } +        } + +        // find and remove existing date/regex/true rules +        foreach ((array) $vacation_tests as $idx => $t) { +            if ($t['test'] == 'currentdate' || $t['test'] == 'true' +                || ($t['test'] == 'header' && $t['type'] == 'regex' && $t['arg1'] == 'received') +            ) { +                unset($vacation_tests[$idx]); +            } +        } + +        if ($date_extension) { +            foreach (array('start', 'end') as $var) { +                if ($dt = $data[$var]) { +                    $vacation_tests[] = array( +                        'test' => 'currentdate', +                        'part' => 'iso8601', +                        'type' => 'value-' . ($var == 'start' ? 'ge' : 'le'), +                        'zone' => $dt->format('O'), +                        'arg'  => str_replace('+00:00', 'Z', strtoupper($dt->format('c'))), +                    ); +                } +            } +        } +        else if ($regex_extension) { +            // Add date range rules if range specified +            if ($data['start'] && $data['end']) { +                if ($tests = self::build_regexp_tests($data['start'], $data['end'], $error)) { +                    $vacation_tests = array_merge($vacation_tests, $tests); +                } + +                if ($error) { +                    $this->error = "Invalid dates specified or unsupported period length"; +                    return false; +                } +            } +        } + +        if ($data['action'] == 'redirect' || $data['action'] == 'copy') { +            if (empty($data['target']) || !rcube_utils::check_email($data['target'])) { +                $this->error = "Invalid address in action taget: " . $data['target']; +                return false; +            } +        } +        else if ($data['action'] && $data['action'] != 'keep' && $data['action'] != 'discard') { +            $this->error = "Unsupported vacation action: " . $data['action']; +            return false; +        } + +        if (empty($vacation_tests)) { +            $vacation_tests = $this->rc->config->get('managesieve_vacation_test', array(array('test' => 'true'))); +        } + +        // @TODO: handle situation when there's no active script + +        $rule             = $this->vacation; +        $rule['type']     = 'if'; +        $rule['name']     = $rule['name'] ?: 'Out-of-Office'; +        $rule['disabled'] = isset($data['enabled']) && !$data['enabled']; +        $rule['tests']    = $vacation_tests; +        $rule['join']     = $date_extension ? count($vacation_tests) > 1 : false; +        $rule['actions']  = array($vacation); + +        if ($data['action'] && $data['action'] != 'keep') { +            $rule['actions'][] = array( +                'type'   => $data['action'] == 'discard' ? 'discard' : 'redirect', +                'copy'   => $data['action'] == 'copy', +                'target' => $data['action'] != 'discard' ? $data['target'] : '', +            ); +        } + +        // reset original vacation rule +        if (isset($this->vacation['idx'])) { +            $this->script[$this->vacation['idx']] = null; +        } + +        array_unshift($this->script, $rule); + +        $this->sieve->script->content = array_values(array_filter($this->script)); + +        return $this->save_script(); +    } + +    /** +     * API: connect to managesieve server +     */ +    public function connect($username, $password) +    { +        if (!parent::connect($username, $password)) { +            return $this->load_script(); +        } +    } + +    /** +     * API: Returns last error +     * +     * @return string Error message +     */ +    public function get_error() +    { +        return $this->error; +    }  } | 
