diff --git a/.htaccess b/.htaccess index 4421654aa..46cac62c4 100644 --- a/.htaccess +++ b/.htaccess @@ -28,7 +28,7 @@ FileETag MTime Size Options -Indexes - + # Disable page indexing diff --git a/CHANGELOG b/CHANGELOG index 68fa8b349..80543659b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,27 @@ CHANGELOG Roundcube Webmail =========================== +- Removed referer_check option (#6440) +- Update to TinyMCE 4.8.2 +- Plugin API: Added 'raise_error' hook (#6199) +- Managesieve: Added support for 'editheader' extension - RFC5293 (#5954) +- Password: Added 'modoboa' driver (#6361) +- Password: Fix bug where password_dovecotpw_with_method setting could be ignored (#6436) +- Password: Fix bug where new users could skip forced password change (#6434) +- Elastic: Improved UX of search dialogs (#6416) +- Elastic: Fix unwanted thread expanding when selecting a collapsed thread in non-mobile mode (#6445) +- Log errors caused by low pcre.backtrack_limit when sending a mail message (#6433) +- Fix style/navigation on error page depending on authentication state (#6362) +- Fix so invalid smtp_helo_host is never used, fallback to localhost (#6408) +- Fix custom logo size in Elastic (#6424) +- Fix listing the same attachment multiple times on forwarded messages +- Fix compatibility with MySQL 8 - error on 'system' table use +- Managesieve: Fix bug where show_real_foldernames setting wasn't respected (#6422) +- New_user_identity: Fix %fu/%u vars substitution in user specific LDAP params (#6419) +- Fix support for "allow-from " in "x_frame_options" config option (#6449) + +RELEASE 1.4-beta +---------------- - Added new skin with mobile support - the Elastic - Support Redis cache - Email Resent (Bounce) feature (#4985) @@ -90,6 +111,15 @@ CHANGELOG Roundcube Webmail - Fix touch event issue on messages list in IE/Edge (#5781) - Fix so links over images are not removed in plain text signatures converted from HTML (#4473) - Fix various issues when downloading files with names containing non-ascii chars, use RFC 2231 (#5772) +- Fix PHP warnings on dummy QUOTA responses in Courier-IMAP 4.17.1 (#6374) +- Fix so fallback from BINARY to BODY FETCH is used also on [PARSE] errors in dovecot 2.3 (#6383) +- Enigma: Fix deleting keys with authentication subkeys (#6381) +- Fix invalid regular expressions that throw warnings on PHP 7.3 (#6398) +- Fix so Classic skin splitter does not escape out of window (#6397) +- Fix XSS issue in handling invalid style tag content (#6410) + +RELEASE 1.3.7 +------------- - Fix PHP Warning: Use of undefined constant IDNA_DEFAULT on systems without php-intl (#6244) - Fix bug where some parts of quota information could have been ignored (#6280) - Fix bug where some escape sequences in html styles could bypass security checks diff --git a/INSTALL b/INSTALL index aab83e65f..9c62bbb14 100644 --- a/INSTALL +++ b/INSTALL @@ -33,6 +33,7 @@ REQUIREMENTS - session.auto_start disabled - suhosin.session.encrypt disabled - mbstring.func_overload disabled + - pcre.backtrack_limit >= 100000 * A MySQL, PostgreSQL, MS SQL Server (2005 or newer), Oracle database or SQLite support in PHP - with permission to create tables * Composer installed either locally or globally (https://getcomposer.org) diff --git a/README.md b/README.md index 1638b895e..3cb9246c7 100644 --- a/README.md +++ b/README.md @@ -25,8 +25,8 @@ which are pure XHTML and CSS 2. The code is mainly written in PHP and is designed to run on a webserver. It includes other open-source classes/libraries from [PEAR][pear], an IMAP library derived from [IlohaMail][iloha] the [TinyMCE][tinymce] rich -text editor, [Googiespell][googiespell] library for spell checking or -the [WASHTML][washtml] sanitizer by Frederic Motte. +text editor, [Googiespell][googiespell] (archive) library for spell checking or +the [HTML5-PHP][html5-php] sanitizer by Masterminds. The current default skin 'Larry' was kindly created by FLINT / Büro für Gestaltung, Berne, Switzerland. @@ -41,6 +41,19 @@ If you're updating an older version of Roundcube please follow the steps described in the UPGRADING file. +BROWSER SUPPORT +--------------- +Roundcube uses jQuery 3.x for its client and therefore inherits the browser +support from there. This currently includes: + +- Chrome: (Current - 1) and Current +- Edge: (Current - 1) and Current +- Firefox: (Current - 1) and Current, ESR +- Internet Explorer: 9+ +- Safari: (Current - 1) and Current +- Opera: Current + + LICENSE ------- This program is free software: you can redistribute it and/or modify @@ -89,13 +102,12 @@ You're always welcome to send a message to the project admin: hello(at)roundcube(dot)net -[pear]: http://pear.php.net -[iloha]: http://sourceforge.net/projects/ilohamail/ -[tinymce]: http://www.tinymce.com/ -[googiespell]: http://orangoo.com/labs/GoogieSpell/ -[washtml]: http://www.ubixis.com/washtml/ -[kmgerich]: http://kmgerich.com/ -[gpl]: http://www.gnu.org/licenses/ +[pear]: https://pear.php.net/ +[iloha]: https://sourceforge.net/projects/ilohamail/ +[tinymce]: https://www.tiny.cloud/ +[googiespell]: https://web.archive.org/web/20170314162746/orangoo.com/labs/GoogieSpell/ +[html5-php]: https://github.com/Masterminds/html5-php +[gpl]: https://www.gnu.org/licenses/ [license]: https://roundcube.net/license [contrib]: https://roundcube.net/contribute [support]: https://roundcube.net/support diff --git a/SQL/mysql.initial.sql b/SQL/mysql.initial.sql index f3b81029c..d3a9f731d 100644 --- a/SQL/mysql.initial.sql +++ b/SQL/mysql.initial.sql @@ -222,4 +222,4 @@ CREATE TABLE `system` ( /*!40014 SET FOREIGN_KEY_CHECKS=1 */; -INSERT INTO system (name, value) VALUES ('roundcube-version', '2018021600'); +INSERT INTO `system` (`name`, `value`) VALUES ('roundcube-version', '2018021600'); diff --git a/SQL/postgres.initial.sql b/SQL/postgres.initial.sql index 5fda9e7ff..3ced2e78a 100644 --- a/SQL/postgres.initial.sql +++ b/SQL/postgres.initial.sql @@ -313,4 +313,4 @@ CREATE TABLE "system" ( value text ); -INSERT INTO system (name, value) VALUES ('roundcube-version', '2018021600'); +INSERT INTO "system" (name, value) VALUES ('roundcube-version', '2018021600'); diff --git a/composer.json-dist b/composer.json-dist index 5874c0c41..97d0f4abd 100644 --- a/composer.json-dist +++ b/composer.json-dist @@ -15,7 +15,7 @@ "pear/auth_sasl": "~1.1.0", "pear/net_idna2": "~0.2.0", "pear/mail_mime": "~1.10.0", - "pear/net_smtp": "~1.7.1", + "pear/net_smtp": "~1.8.0", "pear/crypt_gpg": "~1.6.3", "pear/net_sieve": "~1.4.3", "roundcube/plugin-installer": "~0.1.6", diff --git a/config/defaults.inc.php b/config/defaults.inc.php index aa337615c..312a7ec35 100644 --- a/config/defaults.inc.php +++ b/config/defaults.inc.php @@ -1,11 +1,15 @@ . // Set to false in order to disable sending the header. diff --git a/jsdeps.json b/jsdeps.json index 6b62c4a66..c7e11cd2b 100644 --- a/jsdeps.json +++ b/jsdeps.json @@ -36,10 +36,10 @@ }, { "lib": "tinymce", - "version": "4.7.13", + "version": "4.8.2", "url": "http://download.ephox.com/tinymce/community/tinymce_$v.zip", "dest": "program/js", - "sha1": "7f988f3899aebee6d49bd55e981331da07eee6c5", + "sha1": "d7fced05acdeeb78299585ea9909b0de2b3d759d", "license": "LGPL", "copyright": "Copyright (c) 1999-2015 Ephox Corp. All rights reserved", "rm": "program/js/tinymce", @@ -56,7 +56,7 @@ }, { "lib": "tinymce-langs", - "version": "4.7.13", + "version": "4.8.2", "url": "https://tinymce-services.azurewebsites.net/1/i18n/download?langs=ar,hy,az,eu,be,bs,bg_BG,ca,zh_CN,zh_TW,hr,cs,cs_CZ,da,nl,en_CA,en_GB,eo,et,fo,fi,fr_FR,fr_CH,gd,gl,ka_GE,de,de_AT,el,he_IL,hi_IN,hu_HU,is_IS,id,ga,it,ja,kab,km_KH,ko_KR,ku,ku_IQ,lv,lt,lb,mk_MK,ml_IN,nb_NO,oc,fa,fa_IR,pl,pt_BR,pt_PT,ro,ru,sk,sl_SI,es,es_MX,sv_SE,tg,ta,ta_IN,tt,th_TH,tr,tr_TR,ug,uk,uk_UA,vi,vi_VN,cy&extension=.zip", "dest": "program/js/tinymce" }, @@ -88,10 +88,10 @@ { "lib": "bootstrap", "name": "Bootstrap", - "version": "4.1.0", + "version": "4.1.2", "url": "https://github.com/twbs/bootstrap/releases/download/v$v/bootstrap-$v-dist.zip", "dest": "skins/elastic/deps", - "sha1": "90abd51cf27a3dc4823eed6c1de620725f068b6c", + "sha1": "d43982739f8f2bb05ef2a2fa55c358ce7e30a72b", "license": "MIT", "flat": true, "map": { diff --git a/plugins/enigma/enigma.php b/plugins/enigma/enigma.php index cab792676..5a5bbc03d 100644 --- a/plugins/enigma/enigma.php +++ b/plugins/enigma/enigma.php @@ -442,15 +442,14 @@ class enigma extends rcube_plugin // find private keys for this identity if ($p['record']['email']) { $listing = array(); - $engine = $this->load_engine(); - $keys = (array)$engine->list_keys($p['record']['email']); + $engine = $this->load_engine(); + $keys = (array)$engine->list_keys($p['record']['email']); foreach ($keys as $key) { if ($key->get_type() === enigma_key::TYPE_KEYPAIR) { $listing[] = html::tag('li', null, - html::tag('span', 'identity', html::quote($key->name)) . - ' ' . html::tag('strong', 'uid', html::quote($key->id)) + . ' ' . html::tag('span', 'identity', html::quote($key->name)) ); } } @@ -464,16 +463,12 @@ class enigma extends rcube_plugin } // add button linking to enigma key management - $content .= html::p( - null, - html::a( - array( - 'class' => 'button', - 'href' => $this->rc->url(array('action' => 'plugin.enigmakeys')), - 'target' => '_parent', - ), - $this->gettext('managekeys')) + $button_attr = array( + 'class' => 'button', + 'href' => $this->rc->url(array('action' => 'plugin.enigmakeys')), + 'target' => '_parent', ); + $content .= html::p(null, html::a($button_attr, $this->gettext('managekeys'))); // rename class to avoid Mailvelope key management to kick in $p['form']['encryption']['attrs'] = array('class' => 'enigma-identity-encryption'); diff --git a/plugins/enigma/lib/enigma_driver_gnupg.php b/plugins/enigma/lib/enigma_driver_gnupg.php index 74e206c53..5e02297fb 100644 --- a/plugins/enigma/lib/enigma_driver_gnupg.php +++ b/plugins/enigma/lib/enigma_driver_gnupg.php @@ -388,13 +388,10 @@ class enigma_driver_gnupg extends enigma_driver } // need to delete private key first else if ($code == enigma_error::DELKEY) { - $key = $this->get_key($keyid); - for ($i = count($key->subkeys) - 1; $i >= 0; $i--) { - $type = ($key->subkeys[$i]->usage & enigma_key::CAN_ENCRYPT) ? 'priv' : 'pub'; - $result = $this->{'delete_' . $type . 'key'}($key->subkeys[$i]->id); - if ($result !== true) { - break; - } + $result = $this->delete_privkey($keyid); + + if ($result === true) { + $result = $this->delete_pubkey($keyid); } } } diff --git a/plugins/enigma/lib/enigma_ui.php b/plugins/enigma/lib/enigma_ui.php index 7c0867cf0..447804a9f 100644 --- a/plugins/enigma/lib/enigma_ui.php +++ b/plugins/enigma/lib/enigma_ui.php @@ -1119,6 +1119,11 @@ class enigma_ui */ function message_ready($p) { + // The message might have been already encrypted by Mailvelope + if (strpos($p['message']->getParam('ctype'), 'multipart/encrypted') === 0) { + return $p; + } + $savedraft = !empty($_POST['_draft']) && empty($_GET['_saveonly']); $sign_enable = (bool) rcube_utils::get_input_value('_enigma_sign', rcube_utils::INPUT_POST); $encrypt_enable = (bool) rcube_utils::get_input_value('_enigma_encrypt', rcube_utils::INPUT_POST); diff --git a/plugins/enigma/skins/elastic/templates/keys.html b/plugins/enigma/skins/elastic/templates/keys.html index 6f2667add..4dd498581 100644 --- a/plugins/enigma/skins/elastic/templates/keys.html +++ b/plugins/enigma/skins/elastic/templates/keys.html @@ -14,7 +14,8 @@
- +
'; // Advanced modifiers (comparators) - $select_comp = new html_select(array('name' => "_rule_comp[]", 'id' => 'rule_comp_op'.$id)); - $select_comp->add(rcube::Q($this->plugin->gettext('default')), ''); - $select_comp->add(rcube::Q($this->plugin->gettext('octet')), 'i;octet'); - $select_comp->add(rcube::Q($this->plugin->gettext('asciicasemap')), 'i;ascii-casemap'); - if (in_array('comparator-i;ascii-numeric', $this->exts)) { - $select_comp->add(rcube::Q($this->plugin->gettext('asciinumeric')), 'i;ascii-numeric'); - } - $need_comp = $rule['test'] != 'size' && $rule['test'] != 'duplicate'; $mout .= ''; // Advanced modifiers (mime) @@ -2096,32 +2106,36 @@ class rcube_sieve_engine $select_action = new html_select(array('name' => "_action_type[$id]", 'id' => 'action_type'.$id, 'onchange' => 'action_type_select(' .$id .')')); if (in_array('fileinto', $this->exts)) - $select_action->add(rcube::Q($this->plugin->gettext('messagemoveto')), 'fileinto'); + $select_action->add($this->plugin->gettext('messagemoveto'), 'fileinto'); if (in_array('fileinto', $this->exts) && in_array('copy', $this->exts)) - $select_action->add(rcube::Q($this->plugin->gettext('messagecopyto')), 'fileinto_copy'); - $select_action->add(rcube::Q($this->plugin->gettext('messageredirect')), 'redirect'); + $select_action->add($this->plugin->gettext('messagecopyto'), 'fileinto_copy'); + $select_action->add($this->plugin->gettext('messageredirect'), 'redirect'); if (in_array('copy', $this->exts)) - $select_action->add(rcube::Q($this->plugin->gettext('messagesendcopy')), 'redirect_copy'); + $select_action->add($this->plugin->gettext('messagesendcopy'), 'redirect_copy'); if (in_array('reject', $this->exts)) - $select_action->add(rcube::Q($this->plugin->gettext('messagediscard')), 'reject'); + $select_action->add($this->plugin->gettext('messagediscard'), 'reject'); else if (in_array('ereject', $this->exts)) - $select_action->add(rcube::Q($this->plugin->gettext('messagediscard')), 'ereject'); + $select_action->add($this->plugin->gettext('messagediscard'), 'ereject'); if (in_array('vacation', $this->exts)) - $select_action->add(rcube::Q($this->plugin->gettext('messagereply')), 'vacation'); - $select_action->add(rcube::Q($this->plugin->gettext('messagedelete')), 'discard'); + $select_action->add($this->plugin->gettext('messagereply'), 'vacation'); + $select_action->add($this->plugin->gettext('messagedelete'), 'discard'); if (in_array('imapflags', $this->exts) || in_array('imap4flags', $this->exts)) { - $select_action->add(rcube::Q($this->plugin->gettext('setflags')), 'setflag'); - $select_action->add(rcube::Q($this->plugin->gettext('addflags')), 'addflag'); - $select_action->add(rcube::Q($this->plugin->gettext('removeflags')), 'removeflag'); + $select_action->add($this->plugin->gettext('setflags'), 'setflag'); + $select_action->add($this->plugin->gettext('addflags'), 'addflag'); + $select_action->add($this->plugin->gettext('removeflags'), 'removeflag'); + } + if (in_array('editheader', $this->exts)) { + $select_action->add($this->plugin->gettext('addheader'), 'addheader'); + $select_action->add($this->plugin->gettext('deleteheader'), 'deleteheader'); } if (in_array('variables', $this->exts)) { - $select_action->add(rcube::Q($this->plugin->gettext('setvariable')), 'set'); + $select_action->add($this->plugin->gettext('setvariable'), 'set'); } if (in_array('enotify', $this->exts) || in_array('notify', $this->exts)) { - $select_action->add(rcube::Q($this->plugin->gettext('notify')), 'notify'); + $select_action->add($this->plugin->gettext('notify'), 'notify'); } - $select_action->add(rcube::Q($this->plugin->gettext('messagekeep')), 'keep'); - $select_action->add(rcube::Q($this->plugin->gettext('rulestop')), 'stop'); + $select_action->add($this->plugin->gettext('messagekeep'), 'keep'); + $select_action->add($this->plugin->gettext('rulestop'), 'stop'); $select_type = $action['type']; if (in_array($action['type'], array('fileinto', 'redirect')) && $action['copy']) { @@ -2190,7 +2204,7 @@ class rcube_sieve_engine } } - $out .= '
'; + $out .= '
'; $out .= ''. rcube::Q($this->plugin->gettext('vacationreason')) .'
'; $out .= html::tag('textarea', array( 'name' => '_action_reason[' . $id . ']', @@ -2353,7 +2367,7 @@ class rcube_sieve_engine } // @TODO: nice UI for mailto: (other methods too) URI parameters - $out .= '
'; + $out .= '
'; $out .= '' .rcube::Q($this->plugin->gettext('notifytarget')) . '
'; $out .= '
'; $out .= $select_method->show($method); @@ -2393,6 +2407,85 @@ class rcube_sieve_engine $this->error_class($id, 'action', 'options', 'action_notifyoption'), 30) . '
'; $out .= '
'; + if (in_array('editheader', $this->exts)) { + $action['pos'] = $action['last'] ? 'last' : ''; + $pos1_selector = new html_select(array( + 'name' => "_action_addheader_pos[$id]", + 'id' => "action_addheader_pos$id", + 'class' => $this->error_class($id, 'action', 'pos', 'action_addheader_pos') + )); + $pos1_selector->add($this->plugin->gettext('headeratstart'), ''); + $pos1_selector->add($this->plugin->gettext('headeratend'), 'last'); + $pos2_selector = new html_select(array( + 'name' => "_action_delheader_pos[$id]", + 'id' => "action_delheader_pos$id", + 'class' => $this->error_class($id, 'action', 'pos', 'action_delheader_pos') + )); + $pos2_selector->add($this->plugin->gettext('headerfromstart'), ''); + $pos2_selector->add($this->plugin->gettext('headerfromend'), 'last'); + + // addheader + $out .= '
'; + $out .= '
'; + $out .= html::tag('input', array( + 'type' => 'text', + 'name' => '_action_addheader_name[' . $id . ']', + 'id' => 'action_addheader_name' . $id, + 'value' => $action['name'], + 'size' => 35, + 'class' => $this->error_class($id, 'action', 'name', 'action_addheader_name'), + )); + $out .= '

'; + $out .= html::tag('input', array( + 'type' => 'text', + 'name' => '_action_addheader_value[' . $id . ']', + 'id' => 'action_addheader_value' . $id, + 'value' => $action['value'], + 'size' => 35, + 'class' => $this->error_class($id, 'action', 'value', 'action_addheader_value'), + )); + $out .= '

'; + $out .= $pos1_selector->show($action['pos']); + $out .= '
'; + + // deleteheader + $out .= '
'; + $out .= '
'; + $out .= html::tag('input', array( + 'type' => 'text', + 'name' => '_action_delheader_name[' . $id . ']', + 'id' => 'action_delheader_name' . $id, + 'value' => $action['name'], + 'size' => 35, + 'class' => $this->error_class($id, 'action', 'name', 'action_delheader_name'), + )); + $out .= '

'; + $out .= $this->list_input($id, 'action_delheader_value', $action['value'], true, + $this->error_class($id, 'action', 'value', 'action_delheader_value')) . "\n"; + $out .= '
'; + $out .= html::span('label input-group-prepend', html::label(array( + 'class' => 'input-group-text', 'for' => 'action_delheader_op'.$id), rcube::Q($this->plugin->gettext('headermatchtype')))); + $out .= $this->match_type_selector('action_delheader_op', $id, $action['match-type'], null, 'basic'); + $out .= '
'; + $out .= '
'; + $out .= html::span('label input-group-prepend', html::label(array( + 'class' => 'input-group-text', 'for' => 'action_delheader_comp_op'.$id), rcube::Q($this->plugin->gettext('comparator')))); + $out .= $this->comparator_selector($action['comparator'], 'action_delheader_comp', $id); + $out .= '
'; + $out .= '

'; + $out .= '
'; + $out .= html::tag('input', array( + 'type' => 'text', + 'name' => '_action_delheader_index[' . $id . ']', + 'id' => 'action_delheader_index' . $id, + 'value' => $action['index'] ? intval($action['index']) : '', + 'size' => 5, + 'class' => $this->error_class($id, 'action', 'index', 'action_delheader_index'), + )); + $out .= ' ' . $pos2_selector->show($action['pos']); + $out .= '
'; + } + // mailbox select if ($action['type'] == 'fileinto') { // make sure non-existing (or unsubscribed) mailbox is listed (#1489956) @@ -2405,7 +2498,6 @@ class rcube_sieve_engine } $select = $this->rc->folder_selector(array( - 'realnames' => false, 'maxlength' => 100, 'id' => 'action_mailbox' . $id, 'name' => "_action_mailbox[$id]", @@ -2935,4 +3027,63 @@ class rcube_sieve_engine return $headers; } + + /** + * Match type selector + */ + protected function match_type_selector($name, $id, $test, $rule = null, $mode = 'all') + { + // matching type select (operator) + $select_op = new html_select(array( + 'name' => "_{$name}[]", + 'id' => "{$name}{$id}", + 'style' => 'display:' .(!in_array($rule, array('size', 'duplicate')) ? 'inline' : 'none'), + 'class' => 'operator_selector', + 'onchange' => "{$name}_select(this, '{$id}')", + )); + + $select_op->add(rcube::Q($this->plugin->gettext('filtercontains')), 'contains'); + $select_op->add(rcube::Q($this->plugin->gettext('filternotcontains')), 'notcontains'); + $select_op->add(rcube::Q($this->plugin->gettext('filteris')), 'is'); + $select_op->add(rcube::Q($this->plugin->gettext('filterisnot')), 'notis'); + if ($mode == 'all') { + $select_op->add(rcube::Q($this->plugin->gettext('filterexists')), 'exists'); + $select_op->add(rcube::Q($this->plugin->gettext('filternotexists')), 'notexists'); + } + $select_op->add(rcube::Q($this->plugin->gettext('filtermatches')), 'matches'); + $select_op->add(rcube::Q($this->plugin->gettext('filternotmatches')), 'notmatches'); + if (in_array('regex', $this->exts)) { + $select_op->add(rcube::Q($this->plugin->gettext('filterregex')), 'regex'); + $select_op->add(rcube::Q($this->plugin->gettext('filternotregex')), 'notregex'); + } + if ($mode == 'all' && in_array('relational', $this->exts)) { + $select_op->add(rcube::Q($this->plugin->gettext('countisgreaterthan')), 'count-gt'); + $select_op->add(rcube::Q($this->plugin->gettext('countisgreaterthanequal')), 'count-ge'); + $select_op->add(rcube::Q($this->plugin->gettext('countislessthan')), 'count-lt'); + $select_op->add(rcube::Q($this->plugin->gettext('countislessthanequal')), 'count-le'); + $select_op->add(rcube::Q($this->plugin->gettext('countequals')), 'count-eq'); + $select_op->add(rcube::Q($this->plugin->gettext('countnotequals')), 'count-ne'); + $select_op->add(rcube::Q($this->plugin->gettext('valueisgreaterthan')), 'value-gt'); + $select_op->add(rcube::Q($this->plugin->gettext('valueisgreaterthanequal')), 'value-ge'); + $select_op->add(rcube::Q($this->plugin->gettext('valueislessthan')), 'value-lt'); + $select_op->add(rcube::Q($this->plugin->gettext('valueislessthanequal')), 'value-le'); + $select_op->add(rcube::Q($this->plugin->gettext('valueequals')), 'value-eq'); + $select_op->add(rcube::Q($this->plugin->gettext('valuenotequals')), 'value-ne'); + } + + return $select_op->show($test); + } + + protected function comparator_selector($comparator, $name, $id) + { + $select_comp = new html_select(array('name' => "_{$name}[]", 'id' => "{$name}_op{$id}")); + $select_comp->add(rcube::Q($this->plugin->gettext('default')), ''); + $select_comp->add(rcube::Q($this->plugin->gettext('octet')), 'i;octet'); + $select_comp->add(rcube::Q($this->plugin->gettext('asciicasemap')), 'i;ascii-casemap'); + if (in_array('comparator-i;ascii-numeric', $this->exts)) { + $select_comp->add(rcube::Q($this->plugin->gettext('asciinumeric')), 'i;ascii-numeric'); + } + + return $select_comp->show($comparator); + } } diff --git a/plugins/managesieve/lib/Roundcube/rcube_sieve_forward.php b/plugins/managesieve/lib/Roundcube/rcube_sieve_forward.php index 9ca3a011a..967cb1622 100644 --- a/plugins/managesieve/lib/Roundcube/rcube_sieve_forward.php +++ b/plugins/managesieve/lib/Roundcube/rcube_sieve_forward.php @@ -312,7 +312,7 @@ class rcube_sieve_forward extends rcube_sieve_engine $table = new html_table(array('cols' => 2)); $table->add('title', html::label('forward_action', $this->plugin->gettext('forward.action'))); - $table->add('forward', $action->show($this->forward['action']) . ' ' . $action_target); + $table->add('forward input-group input-group-combo', $action->show($this->forward['action']) . ' ' . $action_target); $table->add('title', html::label('forward_status', $this->plugin->gettext('forward.status'))); $table->add(null, $status->show(!isset($this->forward['disabled']) || $this->forward['disabled'] ? 'off' : 'on')); diff --git a/plugins/managesieve/lib/Roundcube/rcube_sieve_script.php b/plugins/managesieve/lib/Roundcube/rcube_sieve_script.php index 369f1da9d..fdfbf6e25 100644 --- a/plugins/managesieve/lib/Roundcube/rcube_sieve_script.php +++ b/plugins/managesieve/lib/Roundcube/rcube_sieve_script.php @@ -31,6 +31,7 @@ class rcube_sieve_script 'copy', // RFC3894 'date', // RFC5260 'duplicate', // RFC7352 + 'editheader', // RFC5293 'enotify', // RFC5435 'envelope', // RFC5228 'ereject', // RFC5429 @@ -422,6 +423,26 @@ class rcube_sieve_script . self::escape_string($action['target']); break; + case 'addheader': + case 'deleteheader': + array_push($exts, 'editheader'); + $action_script .= $action['type']; + if (!empty($action['index'])) { + $action_script .= " :index " . intval($action['index']); + } + if (!empty($action['last']) && (!empty($action['index']) || $action['type'] == 'addheader')) { + $action_script .= " :last"; + } + if ($action['type'] == 'deleteheader') { + $action['type'] = $action['match-type']; + $this->add_operator($action, $action_script, $exts); + } + $action_script .= " " . self::escape_string($action['name']); + if ((is_string($action['value']) && strlen($action['value']) > 0) || (is_array($action['value']) && !empty($action['value']))) { + $action_script .= " " . self::escape_string($action['value']); + } + break; + case 'keep': case 'discard': case 'stop': @@ -908,6 +929,21 @@ class rcube_sieve_script $result[] = $action; break; + case 'addheader': + case 'deleteheader': + $args = $this->test_tokens($tokens); + if ($token == 'deleteheader') { + $args['match-type'] = $args['type']; + } + if (($index = array_search(':last', $tokens)) !== false) { + $args['last'] = true; + unset($tokens[$index]); + } + $action = array('type' => $token, 'name' => array_shift($tokens), 'value' => array_shift($tokens)); + + $result[] = $action + $args; + break; + case 'reject': case 'ereject': case 'setflag': diff --git a/plugins/managesieve/lib/Roundcube/rcube_sieve_vacation.php b/plugins/managesieve/lib/Roundcube/rcube_sieve_vacation.php index 1cca437c2..5fcbfb3f0 100644 --- a/plugins/managesieve/lib/Roundcube/rcube_sieve_vacation.php +++ b/plugins/managesieve/lib/Roundcube/rcube_sieve_vacation.php @@ -381,7 +381,7 @@ class rcube_sieve_vacation extends rcube_sieve_engine if ($this->rc->config->get('managesieve_vacation') != 2 && !empty($this->vacation['list'])) { $after = new html_select(array('name' => 'vacation_after', 'id' => 'vacation_after')); - $after->add('', ''); + $after->add('---', ''); foreach ($this->vacation['list'] as $idx => $rule) { $after->add($rule, $idx); } @@ -461,7 +461,7 @@ class rcube_sieve_vacation extends rcube_sieve_engine } // redirect target - $action_target = ' ' + $action_target = '