From 6af79f1517d4a5c34088eaf6d0e5af69733f3eef Mon Sep 17 00:00:00 2001 From: Aleksander Machniak <alec@alec.pl> Date: Wed, 14 Jan 2015 07:30:31 -0500 Subject: [PATCH] Support "not allof" test as a negation of all sub-tests --- plugins/managesieve/lib/Roundcube/rcube_sieve_engine.php | 719 ++++++++++++++++++++++++++++++++++++++++------------------- 1 files changed, 485 insertions(+), 234 deletions(-) diff --git a/plugins/managesieve/lib/Roundcube/rcube_sieve_engine.php b/plugins/managesieve/lib/Roundcube/rcube_sieve_engine.php index bbbfa9d..035b267 100644 --- a/plugins/managesieve/lib/Roundcube/rcube_sieve_engine.php +++ b/plugins/managesieve/lib/Roundcube/rcube_sieve_engine.php @@ -5,8 +5,8 @@ * * Engine part of Managesieve plugin implementing UI and backend access. * - * Copyright (C) 2008-2013, The Roundcube Dev Team - * Copyright (C) 2011-2013, Kolab Systems AG + * Copyright (C) 2008-2014, The Roundcube Dev Team + * Copyright (C) 2011-2014, Kolab Systems AG * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -24,21 +24,21 @@ class rcube_sieve_engine { - private $rc; - private $sieve; - private $errors; - private $form; - private $tips = array(); - private $script = array(); - private $exts = array(); - private $list; - private $active = array(); - private $headers = array( + protected $rc; + protected $sieve; + protected $errors; + protected $form; + protected $tips = array(); + protected $script = array(); + protected $exts = array(); + protected $list; + protected $active = array(); + protected $headers = array( 'subject' => 'Subject', 'from' => 'From', 'to' => 'To', ); - private $addr_headers = array( + protected $addr_headers = array( // Required "from", "to", "cc", "bcc", "sender", "resent-from", "resent-to", // Additional (RFC 822 / RFC 2822) @@ -52,8 +52,18 @@ // Undocumented "x-beenthere", ); + protected $notify_methods = array( + 'mailto', + // 'sms', + // 'tel', + ); + protected $notify_importance_options = array( + 3 => 'notifyimportancelow', + 2 => 'notifyimportancenormal', + 1 => 'notifyimportancehigh' + ); - const VERSION = '7.0'; + const VERSION = '8.1'; const PROGNAME = 'Roundcube (Managesieve)'; const PORT = 4190; @@ -63,14 +73,14 @@ */ function __construct($plugin) { - $this->rc = rcmail::get_instance(); + $this->rc = rcube::get_instance(); $this->plugin = $plugin; } /** * Loads configuration, initializes plugin (including sieve connection) */ - function start() + function start($mode = null) { // register UI objects $this->rc->output->add_handlers(array( @@ -81,6 +91,76 @@ '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'); @@ -102,8 +182,8 @@ } $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, @@ -112,6 +192,7 @@ '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 @@ -125,84 +206,65 @@ $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(); - - 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']; - } - else { - // 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 (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_name) { - $this->sieve->load($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'); - break; - default: - $this->rc->output->show_message('managesieve.filterunknownerror', 'error'); - break; - } - - rcube::raise_error(array('code' => 403, 'type' => 'php', - 'file' => __FILE__, 'line' => __LINE__, - 'message' => "Unable to connect to managesieve on $host:$port"), true, false); - - // 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->script = $this->sieve->script->as_array(); - $this->rc->output->set_env('currentset', $this->sieve->current); - $_SESSION['managesieve_current'] = $this->sieve->current; - } - - return $error; + return $this->sieve->error(); } + /** + * 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 ($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 (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_name) { + $this->sieve->load($script_name); + } + + return $this->sieve->error(); + } + + /** + * User interface actions handler + */ function actions() { $error = $this->start(); @@ -287,7 +349,7 @@ } } else if ($action == 'setact' && !$error) { - $script_name = rcube_utils::get_input_value('_set', rcube_utils::INPUT_GPC, true); + $script_name = rcube_utils::get_input_value('_set', rcube_utils::INPUT_POST, true); $result = $this->activate_script($script_name); $kep14 = $this->rc->config->get('managesieve_kolab_master'); @@ -301,7 +363,7 @@ } } else if ($action == 'deact' && !$error) { - $script_name = rcube_utils::get_input_value('_set', rcube_utils::INPUT_GPC, true); + $script_name = rcube_utils::get_input_value('_set', rcube_utils::INPUT_POST, true); $result = $this->deactivate_script($script_name); if ($result === true) { @@ -314,7 +376,7 @@ } } else if ($action == 'setdel' && !$error) { - $script_name = rcube_utils::get_input_value('_set', rcube_utils::INPUT_GPC, true); + $script_name = rcube_utils::get_input_value('_set', rcube_utils::INPUT_POST, true); $result = $this->remove_script($script_name); if ($result === true) { @@ -339,14 +401,13 @@ header("Content-Type: application/octet-stream"); header("Content-Length: ".strlen($script)); - if ($browser->ie) + if ($browser->ie) { header("Content-Type: application/force-download"); - if ($browser->ie && $browser->ver < 7) - $filename = rawurlencode(abbreviate_string($script_name, 55)); - else if ($browser->ie) $filename = rawurlencode($script_name); - else + } + else { $filename = addcslashes($script_name, '\\"'); + } header("Content-Disposition: attachment; filename=\"$filename.txt\""); echo $script; @@ -358,14 +419,14 @@ $this->rc->output->command('managesieve_updatelist', 'list', array('list' => $result)); } else if ($action == 'ruleadd') { - $rid = rcube_utils::get_input_value('_rid', rcube_utils::INPUT_GPC); + $rid = rcube_utils::get_input_value('_rid', rcube_utils::INPUT_POST); $id = $this->genid(); $content = $this->rule_div($fid, $id, false); $this->rc->output->command('managesieve_rulefill', $content, $id, $rid); } else if ($action == 'actionadd') { - $aid = rcube_utils::get_input_value('_aid', rcube_utils::INPUT_GPC); + $aid = rcube_utils::get_input_value('_aid', rcube_utils::INPUT_POST); $id = $this->genid(); $content = $this->action_div($fid, $id, false); @@ -535,6 +596,7 @@ $act_types = rcube_utils::get_input_value('_action_type', rcube_utils::INPUT_POST, true); $mailboxes = rcube_utils::get_input_value('_action_mailbox', rcube_utils::INPUT_POST, true); $act_targets = rcube_utils::get_input_value('_action_target', rcube_utils::INPUT_POST, true); + $domain_targets = rcube_utils::get_input_value('_action_target_domain', rcube_utils::INPUT_POST); $area_targets = rcube_utils::get_input_value('_action_target_area', rcube_utils::INPUT_POST, true); $reasons = rcube_utils::get_input_value('_action_reason', rcube_utils::INPUT_POST, true); $addresses = rcube_utils::get_input_value('_action_addresses', rcube_utils::INPUT_POST, true); @@ -545,9 +607,10 @@ $varnames = rcube_utils::get_input_value('_action_varname', rcube_utils::INPUT_POST); $varvalues = rcube_utils::get_input_value('_action_varvalue', rcube_utils::INPUT_POST); $varmods = rcube_utils::get_input_value('_action_varmods', rcube_utils::INPUT_POST); - $notifyaddrs = rcube_utils::get_input_value('_action_notifyaddress', rcube_utils::INPUT_POST); - $notifybodies = rcube_utils::get_input_value('_action_notifybody', rcube_utils::INPUT_POST); - $notifymessages = rcube_utils::get_input_value('_action_notifymessage', rcube_utils::INPUT_POST); + $notifymethods = rcube_utils::get_input_value('_action_notifymethod', rcube_utils::INPUT_POST); + $notifytargets = rcube_utils::get_input_value('_action_notifytarget', rcube_utils::INPUT_POST, true); + $notifyoptions = rcube_utils::get_input_value('_action_notifyoption', rcube_utils::INPUT_POST, true); + $notifymessages = rcube_utils::get_input_value('_action_notifymessage', rcube_utils::INPUT_POST, true); $notifyfrom = rcube_utils::get_input_value('_action_notifyfrom', rcube_utils::INPUT_POST); $notifyimp = rcube_utils::get_input_value('_action_notifyimportance', rcube_utils::INPUT_POST); @@ -622,7 +685,21 @@ if (!count($target)) { $this->errors['tests'][$i]['target'] = $this->plugin->gettext('cannotbeempty'); } - else if ($type != 'regex' && $type != 'matches') { + else if (strpos($type, 'count-') === 0) { + foreach ($target as $arg) { + if (preg_match('/[^0-9]/', $arg)) { + $this->errors['tests'][$i]['target'] = $this->plugin->gettext('forbiddenchars'); + } + } + } + else if (strpos($type, 'value-') === 0) { + // Some date/time formats do not support i;ascii-numeric comparator + if ($comparator == 'i;ascii-numeric' && in_array($datepart, array('date', 'time', 'iso8601', 'std11'))) { + $comparator = ''; + } + } + + if (!preg_match('/^(regex|matches|count-)/', $type) && count($target)) { foreach ($target as $arg) { if (!$this->validate_date_part($datepart, $arg)) { $this->errors['tests'][$i]['target'] = $this->plugin->gettext('invaliddateformat'); @@ -668,7 +745,21 @@ if (!count($target)) { $this->errors['tests'][$i]['target'] = $this->plugin->gettext('cannotbeempty'); } - else if ($type != 'regex' && $type != 'matches') { + else if (strpos($type, 'count-') === 0) { + foreach ($target as $arg) { + if (preg_match('/[^0-9]/', $arg)) { + $this->errors['tests'][$i]['target'] = $this->plugin->gettext('forbiddenchars'); + } + } + } + else if (strpos($type, 'value-') === 0) { + // Some date/time formats do not support i;ascii-numeric comparator + if ($comparator == 'i;ascii-numeric' && in_array($datepart, array('date', 'time', 'iso8601', 'std11'))) { + $comparator = ''; + } + } + + if (count($target) && !preg_match('/^(regex|matches|count-)/', $type)) { foreach ($target as $arg) { if (!$this->validate_date_part($datepart, $arg)) { $this->errors['tests'][$i]['target'] = $this->plugin->gettext('invaliddateformat'); @@ -699,7 +790,7 @@ } else if (preg_match('/^(value|count)-/', $type)) { foreach ($target as $target_value) { - if (!preg_match('/[0-9]+/', $target_value)) { + if (preg_match('/[^0-9]/', $target_value)) { $this->errors['tests'][$i]['target'] = $this->plugin->gettext('forbiddenchars'); } } @@ -742,13 +833,22 @@ $cust_header = (is_array($headers) && count($headers) == 1) ? $headers[0] : $headers; } + $header = $header == '...' ? $cust_header : $header; + + if (is_array($header)) { + foreach ($header as $h_index => $val) { + if (isset($this->headers[$val])) { + $header[$h_index] = $this->headers[$val]; + } + } + } + if ($type == 'exists') { $this->form['tests'][$i]['test'] = 'exists'; - $this->form['tests'][$i]['arg'] = $header == '...' ? $cust_header : $header; + $this->form['tests'][$i]['arg'] = $header; } else { - $test = 'header'; - $header = $header == '...' ? $cust_header : $header; + $test = 'header'; if ($mod == 'address' || $mod == 'envelope') { $found = false; @@ -772,7 +872,7 @@ } else if (preg_match('/^(value|count)-/', $type)) { foreach ($target as $target_value) { - if (!preg_match('/[0-9]+/', $target_value)) { + if (preg_match('/[^0-9]/', $target_value)) { $this->errors['tests'][$i]['target'] = $this->plugin->gettext('forbiddenchars'); } } @@ -785,9 +885,6 @@ } if ($header != 'size' && $comparator) { - if (preg_match('/^(value|count)/', $this->form['tests'][$i]['type'])) - $comparator = 'i;ascii-numeric'; - $this->form['tests'][$i]['comparator'] = $comparator; } @@ -797,16 +894,15 @@ $i = 0; // actions - foreach($act_types as $idx => $type) { - $type = $this->strip_value($type); - $target = $this->strip_value($act_targets[$idx]); + foreach ($act_types as $idx => $type) { + $type = $this->strip_value($type); switch ($type) { - case 'fileinto': case 'fileinto_copy': $mailbox = $this->strip_value($mailboxes[$idx], false, false); $this->form['actions'][$i]['target'] = $this->mod_mailbox($mailbox, 'in'); + if ($type == 'fileinto_copy') { $type = 'fileinto'; $this->form['actions'][$i]['copy'] = true; @@ -824,17 +920,31 @@ case 'redirect': case 'redirect_copy': + $target = $this->strip_value($act_targets[$idx]); + $domain = $this->strip_value($domain_targets[$idx]); + + // force one of the configured domains + $domains = (array) $this->rc->config->get('managesieve_domains'); + if (!empty($domains) && !empty($target)) { + if (!$domain || !in_array($domain, $domains)) { + $domain = $domains[0]; + } + + $target .= '@' . $domain; + } + $this->form['actions'][$i]['target'] = $target; - if ($this->form['actions'][$i]['target'] == '') + if ($target == '') $this->errors['actions'][$i]['target'] = $this->plugin->gettext('cannotbeempty'); - else if (!rcube_utils::check_email($this->form['actions'][$i]['target'])) - $this->errors['actions'][$i]['target'] = $this->plugin->gettext('noemailwarning'); + else if (!rcube_utils::check_email($target)) + $this->errors['actions'][$i]['target'] = $this->plugin->gettext(!empty($domains) ? 'forbiddenchars' : 'noemailwarning'); if ($type == 'redirect_copy') { $type = 'redirect'; $this->form['actions'][$i]['copy'] = true; } + break; case 'addflag': @@ -855,6 +965,7 @@ case 'vacation': $reason = $this->strip_value($reasons[$idx]); $interval_type = $interval_types[$idx] == 'seconds' ? 'seconds' : 'days'; + $this->form['actions'][$i]['reason'] = str_replace("\r\n", "\n", $reason); $this->form['actions'][$i]['subject'] = $subject[$idx]; $this->form['actions'][$i]['addresses'] = array_shift($addresses); @@ -862,7 +973,12 @@ // @TODO: vacation :mime, :from, :handle foreach ((array)$this->form['actions'][$i]['addresses'] as $aidx => $address) { - if (!rcube_utils::check_email($address)) { + $this->form['actions'][$i]['addresses'][$aidx] = $address = trim($address); + + if (empty($address)) { + unset($this->form['actions'][$i]['addresses'][$aidx]); + } + else if (!rcube_utils::check_email($address)) { $this->errors['actions'][$i]['addresses'] = $this->plugin->gettext('noemailwarning'); break; } @@ -894,19 +1010,27 @@ break; case 'notify': - if (empty($notifyaddrs[$idx])) { - $this->errors['actions'][$i]['address'] = $this->plugin->gettext('cannotbeempty'); + if (empty($notifymethods[$idx])) { + $this->errors['actions'][$i]['method'] = $this->plugin->gettext('cannotbeempty'); } - else if (!rcube_utils::check_email($notifyaddrs[$idx])) { - $this->errors['actions'][$i]['address'] = $this->plugin->gettext('noemailwarning'); + if (empty($notifytargets[$idx])) { + $this->errors['actions'][$i]['target'] = $this->plugin->gettext('cannotbeempty'); } if (!empty($notifyfrom[$idx]) && !rcube_utils::check_email($notifyfrom[$idx])) { $this->errors['actions'][$i]['from'] = $this->plugin->gettext('noemailwarning'); } - $this->form['actions'][$i]['address'] = $notifyaddrs[$idx]; - $this->form['actions'][$i]['body'] = $notifybodies[$idx]; - $this->form['actions'][$i]['message'] = $notifymessages[$idx]; - $this->form['actions'][$i]['from'] = $notifyfrom[$idx]; + + // skip empty options + foreach ((array)$notifyoptions[$idx] as $opt_idx => $opt) { + if (!strlen(trim($opt))) { + unset($notifyoptions[$idx][$opt_idx]); + } + } + + $this->form['actions'][$i]['method'] = $notifymethods[$idx] . ':' . $notifytargets[$idx]; + $this->form['actions'][$i]['options'] = $notifyoptions[$idx]; + $this->form['actions'][$i]['message'] = $notifymessages[$idx]; + $this->form['actions'][$i]['from'] = $notifyfrom[$idx]; $this->form['actions'][$i]['importance'] = $notifyimp[$idx]; break; } @@ -916,12 +1040,14 @@ } if (!$this->errors && !$error) { - // zapis skryptu + // save the script if (!isset($this->script[$fid])) { $fid = $this->sieve->script->add_rule($this->form); $new = true; - } else + } + else { $fid = $this->sieve->script->update_rule($fid, $this->form); + } if ($fid !== false) $save = $this->save_script(); @@ -952,7 +1078,7 @@ $this->send(); } - private function send() + protected function send() { // Handle form action if (isset($_GET['_framed']) || isset($_POST['_framed'])) { @@ -962,7 +1088,8 @@ else { $this->rc->output->send('managesieve.filteredit'); } - } else { + } + else { $this->rc->output->set_pagetitle($this->plugin->gettext('filters')); $this->rc->output->send('managesieve.managesieve'); } @@ -1050,16 +1177,7 @@ function filter_frame($attrib) { - if (!$attrib['id']) - $attrib['id'] = 'rcmfilterframe'; - - $attrib['name'] = $attrib['id']; - - $this->rc->output->set_env('contentframe', $attrib['name']); - $this->rc->output->set_env('blankpage', $attrib['src'] ? - $this->rc->output->abs_url($attrib['src']) : 'program/resources/blank.gif'); - - return $this->rc->output->frame($attrib); + return $this->rc->output->frame($attrib, true); } function filterset_form($attrib) @@ -1152,8 +1270,11 @@ $out .= $hiddenfields->show(); // 'any' flag - if (sizeof($scr['tests']) == 1 && $scr['tests'][0]['test'] == 'true' && !$scr['tests'][0]['not']) + if ((!isset($this->form) && empty($scr['tests']) && !empty($scr)) + || (sizeof($scr['tests']) == 1 && $scr['tests'][0]['test'] == 'true' && !$scr['tests'][0]['not']) + ) { $any = true; + } // filter name input $field_id = '_name'; @@ -1214,7 +1335,7 @@ $out .= sprintf("%s<label for=\"%s\">%s</label>\n", $input_join, $field_id, rcube::Q($this->plugin->gettext('filterany'))); - $rows_num = isset($scr) ? sizeof($scr['tests']) : 1; + $rows_num = !empty($scr['tests']) ? sizeof($scr['tests']) : 1; $out .= '<div id="rules"'.($any ? ' style="display: none"' : '').'>'; for ($x=0; $x<$rows_num; $x++) @@ -1258,27 +1379,33 @@ $select_header = new html_select(array('name' => "_header[]", 'id' => 'header'.$id, 'onchange' => 'rule_header_select(' .$id .')')); - foreach ($this->headers as $name => $val) - $select_header->add(rcube::Q($this->plugin->gettext($name)), Q($val)); - $select_header->add(rcube::Q($this->plugin->gettext('...')), '...'); + foreach ($this->headers as $index => $header) { + $header = $this->rc->text_exists($index) ? $this->plugin->gettext($index) : $header; + $select_header->add($header, $index); + } + $select_header->add($this->plugin->gettext('...'), '...'); if (in_array('body', $this->exts)) - $select_header->add(rcube::Q($this->plugin->gettext('body')), 'body'); - $select_header->add(rcube::Q($this->plugin->gettext('size')), 'size'); + $select_header->add($this->plugin->gettext('body'), 'body'); + $select_header->add($this->plugin->gettext('size'), 'size'); if (in_array('date', $this->exts)) { - $select_header->add(rcube::Q($this->plugin->gettext('datetest')), 'date'); - $select_header->add(rcube::Q($this->plugin->gettext('currdate')), 'currentdate'); + $select_header->add($this->plugin->gettext('datetest'), 'date'); + $select_header->add($this->plugin->gettext('currdate'), 'currentdate'); } if (isset($rule['test'])) { if (in_array($rule['test'], array('header', 'address', 'envelope')) - && !is_array($rule['arg1']) && in_array($rule['arg1'], $this->headers) + && !is_array($rule['arg1']) + && ($header = strtolower($rule['arg1'])) + && isset($this->headers[$header]) ) { - $test = $rule['arg1']; + $test = $header; } else if ($rule['test'] == 'exists' - && !is_array($rule['arg']) && in_array($rule['arg'], $this->headers) + && !is_array($rule['arg']) + && ($header = strtolower($rule['arg'])) + && isset($this->headers[$header]) ) { - $test = $rule['arg']; + $test = $header; } else if (in_array($rule['test'], array('size', 'body', 'date', 'currentdate'))) { $test = $rule['test']; @@ -1339,12 +1466,33 @@ $select_op->add(rcube::Q($this->plugin->gettext('valuenotequals')), 'value-ne'); } + $test = self::rule_test($rule); + $target = ''; + + // target(s) input + if (in_array($rule['test'], array('header', 'address', 'envelope'))) { + $target = $rule['arg2']; + } + else if (in_array($rule['test'], array('body', 'date', 'currentdate'))) { + $target = $rule['arg']; + } + else if ($rule['test'] == 'size') { + if (preg_match('/^([0-9]+)(K|M|G)?$/', $rule['arg'], $matches)) { + $sizetarget = $matches[1]; + $sizeitem = $matches[2]; + } + else { + $sizetarget = $rule['arg']; + $sizeitem = $rule['item']; + } + } + // (current)date part select if (in_array('date', $this->exts) || in_array('currentdate', $this->exts)) { $date_parts = array('date', 'iso8601', 'std11', 'julian', 'time', 'year', 'month', 'day', 'hour', 'minute', 'second', 'weekday', 'zone'); $select_dp = new html_select(array('name' => "_rule_date_part[]", 'id' => 'rule_date_part'.$id, - 'style' => $rule['test'] == 'currentdate' || $rule['test'] == 'date' ? '' : 'display:none', + 'style' => in_array($rule['test'], array('currentdate', 'date')) && !preg_match('/^(notcount|count)-/', $test) ? '' : 'display:none', 'class' => 'datepart_selector', )); @@ -1353,32 +1501,6 @@ } $tout .= $select_dp->show($rule['test'] == 'currentdate' || $rule['test'] == 'date' ? $rule['part'] : ''); - } - - // target(s) input - if (in_array($rule['test'], array('header', 'address', 'envelope'))) { - $test = ($rule['not'] ? 'not' : '').($rule['type'] ? $rule['type'] : 'is'); - $target = $rule['arg2']; - } - else if (in_array($rule['test'], array('body', 'date', 'currentdate'))) { - $test = ($rule['not'] ? 'not' : '').($rule['type'] ? $rule['type'] : 'is'); - $target = $rule['arg']; - } - else if ($rule['test'] == 'size') { - $test = ''; - $target = ''; - if (preg_match('/^([0-9]+)(K|M|G)?$/', $rule['arg'], $matches)) { - $sizetarget = $matches[1]; - $sizeitem = $matches[2]; - } - else { - $sizetarget = $rule['arg']; - $sizeitem = $rule['item']; - } - } - else { - $test = ($rule['not'] ? 'not' : '').$rule['test']; - $target = ''; } $tout .= $select_op->show($test); @@ -1421,7 +1543,7 @@ $select_type->add(rcube::Q($this->plugin->gettext('detail')), 'detail'); } - $need_mod = $rule['test'] != 'size' && $rule['test'] != 'body'; + $need_mod = !in_array($rule['test'], array('size', 'body', 'date', 'currentdate')); $mout = '<div id="rule_mod' .$id. '" class="adv"' . (!$need_mod ? ' style="display:none"' : '') . '>'; $mout .= ' <span class="label">' . rcube::Q($this->plugin->gettext('modifier')) . ' </span>'; $mout .= $select_mod->show($rule['test']); @@ -1513,6 +1635,43 @@ return $out; } + private static function rule_test(&$rule) + { + // first modify value/count tests with 'not' keyword + // we'll revert the meaning of operators + if ($rule['not'] && preg_match('/^(count|value)-([gteqnl]{2})/', $rule['type'], $m)) { + $rule['not'] = false; + + switch ($m[2]) { + case 'gt': $rule['type'] = $m[1] . '-le'; break; + case 'ge': $rule['type'] = $m[1] . '-lt'; break; + case 'lt': $rule['type'] = $m[1] . '-ge'; break; + case 'le': $rule['type'] = $m[1] . '-gt'; break; + case 'eq': $rule['type'] = $m[1] . '-ne'; break; + case 'ne': $rule['type'] = $m[1] . '-eq'; break; + } + } + else if ($rule['not'] && $rule['test'] == 'size') { + $rule['not'] = false; + $rule['type'] = $rule['type'] == 'over' ? 'under' : 'over'; + } + + $set = array('header', 'address', 'envelope', 'body', 'date', 'currentdate'); + + // build test string supported by select element + if ($rule['size']) { + $test = $rule['type']; + } + else if (in_array($rule['test'], $set)) { + $test = ($rule['not'] ? 'not' : '') . ($rule['type'] ? $rule['type'] : 'is'); + } + else { + $test = ($rule['not'] ? 'not' : '') . $rule['test']; + } + + return $test; + } + function action_div($fid, $id, $div=true) { $action = isset($this->form) ? $this->form['actions'][$id] : $this->script[$fid]['actions'][$id]; @@ -1563,11 +1722,35 @@ // actions target inputs $out .= '<td class="rowtargets">'; - // shared targets - $out .= '<input type="text" name="_action_target['.$id.']" id="action_target' .$id. '" ' - .'value="' .($action['type']=='redirect' ? rcube::Q($action['target'], 'strict', false) : ''). '" size="35" ' - .'style="display:' .($action['type']=='redirect' ? 'inline' : 'none') .'" ' - . $this->error_class($id, 'action', 'target', 'action_target') .' />'; + + // force domain selection in redirect email input + $domains = (array) $this->rc->config->get('managesieve_domains'); + if (!empty($domains)) { + sort($domains); + + $domain_select = new html_select(array('name' => "_action_target_domain[$id]", 'id' => 'action_target_domain'.$id)); + $domain_select->add(array_combine($domains, $domains)); + + if ($action['type'] == 'redirect') { + $parts = explode('@', $action['target']); + if (!empty($parts)) { + $action['domain'] = array_pop($parts); + $action['target'] = implode('@', $parts); + } + } + } + + // redirect target + $out .= '<span id="redirect_target' . $id . '" style="white-space:nowrap;' + . ' display:' . ($action['type'] == 'redirect' ? 'inline' : 'none') . '">' + . '<input type="text" name="_action_target['.$id.']" id="action_target' .$id. '"' + . ' value="' .($action['type'] == 'redirect' ? rcube::Q($action['target'], 'strict', false) : '') . '"' + . (!empty($domains) ? ' size="20"' : ' size="35"') + . $this->error_class($id, 'action', 'target', 'action_target') .' />' + . (!empty($domains) ? ' @ ' . $domain_select->show($action['domain']) : '') + . '</span>'; + + // (e)reject target $out .= '<textarea name="_action_target_area['.$id.']" id="action_target_area' .$id. '" ' .'rows="3" cols="35" '. $this->error_class($id, 'action', 'targetarea', 'action_target_area') .'style="display:' .(in_array($action['type'], array('reject', 'ereject')) ? 'inline' : 'none') .'">' @@ -1650,52 +1833,80 @@ $out .= '</div>'; // notify - // skip :options tag - not used by the mailto method - $out .= '<div id="action_notify' .$id.'" style="display:' .($action['type']=='notify' ? 'inline' : 'none') .'">'; - $out .= '<span class="label">' .rcube::Q($this->plugin->gettext('notifyaddress')) . '</span><br />' - .'<input type="text" name="_action_notifyaddress['.$id.']" id="action_notifyaddress'.$id.'" ' - .'value="' . rcube::Q($action['address']) . '" size="35" ' - . $this->error_class($id, 'action', 'address', 'action_notifyaddress') .' />'; - $out .= '<br /><span class="label">'. rcube::Q($this->plugin->gettext('notifybody')) .'</span><br />' - .'<textarea name="_action_notifybody['.$id.']" id="action_notifybody' .$id. '" ' - .'rows="3" cols="35" '. $this->error_class($id, 'action', 'method', 'action_notifybody') . '>' - . rcube::Q($action['body'], 'strict', false) . "</textarea>\n"; - $out .= '<br /><span class="label">' .rcube::Q($this->plugin->gettext('notifysubject')) . '</span><br />' - .'<input type="text" name="_action_notifymessage['.$id.']" id="action_notifymessage'.$id.'" ' - .'value="' . rcube::Q($action['message']) . '" size="35" ' - . $this->error_class($id, 'action', 'message', 'action_notifymessage') .' />'; - $out .= '<br /><span class="label">' .rcube::Q($this->plugin->gettext('notifyfrom')) . '</span><br />' - .'<input type="text" name="_action_notifyfrom['.$id.']" id="action_notifyfrom'.$id.'" ' - .'value="' . rcube::Q($action['from']) . '" size="35" ' - . $this->error_class($id, 'action', 'from', 'action_notifyfrom') .' />'; - $importance_options = array( - 3 => 'notifyimportancelow', - 2 => 'notifyimportancenormal', - 1 => 'notifyimportancehigh' - ); + $notify_methods = (array) $this->rc->config->get('managesieve_notify_methods'); + $importance_options = $this->notify_importance_options; + + if (empty($notify_methods)) { + $notify_methods = $this->notify_methods; + } + + list($method, $target) = explode(':', $action['method'], 2); + $method = strtolower($method); + + if ($method && !in_array($method, $notify_methods)) { + $notify_methods[] = $method; + } + + $select_method = new html_select(array( + 'name' => "_action_notifymethod[$id]", + 'id' => "_action_notifymethod$id", + 'class' => $this->error_class($id, 'action', 'method', 'action_notifymethod'), + )); + foreach ($notify_methods as $m_n) { + $select_method->add(rcube::Q($this->rc->text_exists('managesieve.notifymethod'.$m_n) ? $this->plugin->gettext('managesieve.notifymethod'.$m_n) : $m_n), $m_n); + } + $select_importance = new html_select(array( - 'name' => '_action_notifyimportance[' . $id . ']', - 'id' => '_action_notifyimportance' . $id, - 'class' => $this->error_class($id, 'action', 'importance', 'action_notifyimportance'))); + 'name' => "_action_notifyimportance[$id]", + 'id' => "_action_notifyimportance$id", + 'class' => $this->error_class($id, 'action', 'importance', 'action_notifyimportance') + )); foreach ($importance_options as $io_v => $io_n) { $select_importance->add(rcube::Q($this->plugin->gettext($io_n)), $io_v); } + + // @TODO: nice UI for mailto: (other methods too) URI parameters + $out .= '<div id="action_notify' .$id.'" style="display:' .($action['type'] == 'notify' ? 'inline' : 'none') .'">'; + $out .= '<span class="label">' .rcube::Q($this->plugin->gettext('notifytarget')) . '</span><br />' + . $select_method->show($method) + .'<input type="text" name="_action_notifytarget['.$id.']" id="action_notifytarget'.$id.'" ' + .'value="' . rcube::Q($target) . '" size="25" ' + . $this->error_class($id, 'action', 'target', 'action_notifytarget') .' />'; + $out .= '<br /><span class="label">'. rcube::Q($this->plugin->gettext('notifymessage')) .'</span><br />' + .'<textarea name="_action_notifymessage['.$id.']" id="action_notifymessage' .$id. '" ' + .'rows="3" cols="35" '. $this->error_class($id, 'action', 'message', 'action_notifymessage') . '>' + . rcube::Q($action['message'], 'strict', false) . "</textarea>\n"; + if (in_array('enotify', $this->exts)) { + $out .= '<br /><span class="label">' .rcube::Q($this->plugin->gettext('notifyfrom')) . '</span><br />' + .'<input type="text" name="_action_notifyfrom['.$id.']" id="action_notifyfrom'.$id.'" ' + .'value="' . rcube::Q($action['from']) . '" size="35" ' + . $this->error_class($id, 'action', 'from', 'action_notifyfrom') .' />'; + } $out .= '<br /><span class="label">' . rcube::Q($this->plugin->gettext('notifyimportance')) . '</span><br />'; - $out .= $select_importance->show($action['importance'] ? $action['importance'] : 2); + $out .= $select_importance->show($action['importance'] ? (int) $action['importance'] : 2); + $out .= '<div id="action_notifyoption_div' . $id . '">' + .'<span class="label">' . rcube::Q($this->plugin->gettext('notifyoptions')) . '</span><br />' + .$this->list_input($id, 'action_notifyoption', (array)$action['options'], true, + $this->error_class($id, 'action', 'options', 'action_notifyoption'), 30) . '</div>'; $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:'.(!isset($action) || $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>'; @@ -1715,12 +1926,12 @@ return $out; } - private function genid() + protected function genid() { return preg_replace('/[^0-9]/', '', microtime(true)); } - private function strip_value($str, $allow_html = false, $trim = true) + protected function strip_value($str, $allow_html = false, $trim = true) { if (is_array($str)) { foreach ($str as $idx => $val) { @@ -1741,7 +1952,7 @@ return $trim ? trim($str) : $str; } - private function error_class($id, $type, $target, $elem_prefix='') + protected function error_class($id, $type, $target, $elem_prefix='') { // TODO: tooltips if (($type == 'test' && ($str = $this->errors['tests'][$id][$target])) || @@ -1754,7 +1965,7 @@ return ''; } - private function add_tip($id, $str, $error=false) + protected function add_tip($id, $str, $error=false) { if ($error) $str = html::span('sieve error', $str); @@ -1762,7 +1973,7 @@ $this->tips[] = array($id, $str); } - private function print_tips() + protected function print_tips() { if (empty($this->tips)) return; @@ -1771,7 +1982,7 @@ $this->rc->output->add_script($script, 'foot'); } - private function list_input($id, $name, $value, $enabled, $class, $size=null) + protected function list_input($id, $name, $value, $enabled, $class, $size=null) { $value = (array) $value; $value = array_map(array('rcube', 'Q'), $value); @@ -1787,7 +1998,7 @@ /** * Validate input for date part elements */ - private function validate_date_part($type, $value) + protected function validate_date_part($type, $value) { // we do simple validation of date/part format switch ($type) { @@ -1832,7 +2043,7 @@ * * @return string Mailbox name */ - private function mod_mailbox($mailbox, $mode = 'out') + protected function mod_mailbox($mailbox, $mode = 'out') { $delimiter = $_SESSION['imap_delimiter']; $replace_delimiter = $this->rc->config->get('managesieve_replace_delimiter'); @@ -2106,7 +2317,7 @@ $i = 1; foreach ($this->script as $idx => $filter) { - if ($filter['type'] != 'if') { + if (empty($filter['actions'])) { continue; } $fname = $filter['name'] ? $filter['name'] : "#$i"; @@ -2120,4 +2331,44 @@ return $result; } + + /** + * Initializes internal script data + */ + protected function init_script() + { + $this->script = $this->sieve->script->as_array(); + + if (!$this->script) { + return; + } + + $headers = array(); + $exceptions = array('date', 'currentdate', 'size', 'body'); + + // find common headers used in script, will be added to the list + // of available (predefined) headers (#1489271) + foreach ($this->script as $rule) { + foreach ((array) $rule['tests'] as $test) { + if ($test['test'] == 'header') { + foreach ((array) $test['arg1'] as $header) { + $lc_header = strtolower($header); + + // skip special names to not confuse UI + if (in_array($lc_header, $exceptions)) { + continue; + } + + if (!isset($this->headers[$lc_header]) && !isset($headers[$lc_header])) { + $headers[$lc_header] = $header; + } + } + } + } + } + + ksort($headers); + + $this->headers += $headers; + } } -- Gitblit v1.9.1