Merge branch 'master' into dev/elastic

pull/6486/head
Aleksander Machniak 6 years ago
commit 2dcf50019c

@ -28,7 +28,7 @@ FileETag MTime Size
<IfModule mod_autoindex.c> <IfModule mod_autoindex.c>
Options -Indexes Options -Indexes
</ifModule> </IfModule>
<IfModule mod_headers.c> <IfModule mod_headers.c>
# Disable page indexing # Disable page indexing

@ -1,6 +1,27 @@
CHANGELOG Roundcube Webmail 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 <uri>" in "x_frame_options" config option (#6449)
RELEASE 1.4-beta
----------------
- Added new skin with mobile support - the Elastic - Added new skin with mobile support - the Elastic
- Support Redis cache - Support Redis cache
- Email Resent (Bounce) feature (#4985) - Email Resent (Bounce) feature (#4985)
@ -90,6 +111,15 @@ CHANGELOG Roundcube Webmail
- Fix touch event issue on messages list in IE/Edge (#5781) - 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 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 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 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 parts of quota information could have been ignored (#6280)
- Fix bug where some escape sequences in html styles could bypass security checks - Fix bug where some escape sequences in html styles could bypass security checks

@ -33,6 +33,7 @@ REQUIREMENTS
- session.auto_start disabled - session.auto_start disabled
- suhosin.session.encrypt disabled - suhosin.session.encrypt disabled
- mbstring.func_overload disabled - mbstring.func_overload disabled
- pcre.backtrack_limit >= 100000
* A MySQL, PostgreSQL, MS SQL Server (2005 or newer), Oracle database * A MySQL, PostgreSQL, MS SQL Server (2005 or newer), Oracle database
or SQLite support in PHP - with permission to create tables or SQLite support in PHP - with permission to create tables
* Composer installed either locally or globally (https://getcomposer.org) * Composer installed either locally or globally (https://getcomposer.org)

@ -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. 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], It includes other open-source classes/libraries from [PEAR][pear],
an IMAP library derived from [IlohaMail][iloha] the [TinyMCE][tinymce] rich an IMAP library derived from [IlohaMail][iloha] the [TinyMCE][tinymce] rich
text editor, [Googiespell][googiespell] library for spell checking or text editor, [Googiespell][googiespell] (archive) library for spell checking or
the [WASHTML][washtml] sanitizer by Frederic Motte. the [HTML5-PHP][html5-php] sanitizer by Masterminds.
The current default skin 'Larry' was kindly created by FLINT / Büro für The current default skin 'Larry' was kindly created by FLINT / Büro für
Gestaltung, Berne, Switzerland. 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. 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 LICENSE
------- -------
This program is free software: you can redistribute it and/or modify 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 hello(at)roundcube(dot)net
[pear]: http://pear.php.net [pear]: https://pear.php.net/
[iloha]: http://sourceforge.net/projects/ilohamail/ [iloha]: https://sourceforge.net/projects/ilohamail/
[tinymce]: http://www.tinymce.com/ [tinymce]: https://www.tiny.cloud/
[googiespell]: http://orangoo.com/labs/GoogieSpell/ [googiespell]: https://web.archive.org/web/20170314162746/orangoo.com/labs/GoogieSpell/
[washtml]: http://www.ubixis.com/washtml/ [html5-php]: https://github.com/Masterminds/html5-php
[kmgerich]: http://kmgerich.com/ [gpl]: https://www.gnu.org/licenses/
[gpl]: http://www.gnu.org/licenses/
[license]: https://roundcube.net/license [license]: https://roundcube.net/license
[contrib]: https://roundcube.net/contribute [contrib]: https://roundcube.net/contribute
[support]: https://roundcube.net/support [support]: https://roundcube.net/support

@ -222,4 +222,4 @@ CREATE TABLE `system` (
/*!40014 SET FOREIGN_KEY_CHECKS=1 */; /*!40014 SET FOREIGN_KEY_CHECKS=1 */;
INSERT INTO system (name, value) VALUES ('roundcube-version', '2018021600'); INSERT INTO `system` (`name`, `value`) VALUES ('roundcube-version', '2018021600');

@ -313,4 +313,4 @@ CREATE TABLE "system" (
value text value text
); );
INSERT INTO system (name, value) VALUES ('roundcube-version', '2018021600'); INSERT INTO "system" (name, value) VALUES ('roundcube-version', '2018021600');

@ -15,7 +15,7 @@
"pear/auth_sasl": "~1.1.0", "pear/auth_sasl": "~1.1.0",
"pear/net_idna2": "~0.2.0", "pear/net_idna2": "~0.2.0",
"pear/mail_mime": "~1.10.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/crypt_gpg": "~1.6.3",
"pear/net_sieve": "~1.4.3", "pear/net_sieve": "~1.4.3",
"roundcube/plugin-installer": "~0.1.6", "roundcube/plugin-installer": "~0.1.6",

@ -1,11 +1,15 @@
<?php <?php
// ---------------------------------------------------------------------
// WARNING: Do not edit this file! Copy configuration to config.inc.php.
// ---------------------------------------------------------------------
/* /*
+-----------------------------------------------------------------------+ +-----------------------------------------------------------------------+
| Main configuration file with default settings | | Default settings for all configuration options |
| | | |
| This file is part of the Roundcube Webmail client | | This file is part of the Roundcube Webmail client |
| Copyright (C) 2005-2013, The Roundcube Dev Team | | Copyright (C) 2005-2018, The Roundcube Dev Team |
| | | |
| Licensed under the GNU General Public License version 3 or | | Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. | | any later version with exceptions for skins & plugins. |
@ -493,9 +497,6 @@ $config['trusted_host_patterns'] = array();
// check client IP in session authorization // check client IP in session authorization
$config['ip_check'] = false; $config['ip_check'] = false;
// check referer of incoming requests
$config['referer_check'] = false;
// X-Frame-Options HTTP header value sent to prevent from Clickjacking. // X-Frame-Options HTTP header value sent to prevent from Clickjacking.
// Possible values: sameorigin|deny|allow-from <uri>. // Possible values: sameorigin|deny|allow-from <uri>.
// Set to false in order to disable sending the header. // Set to false in order to disable sending the header.

@ -36,10 +36,10 @@
}, },
{ {
"lib": "tinymce", "lib": "tinymce",
"version": "4.7.13", "version": "4.8.2",
"url": "http://download.ephox.com/tinymce/community/tinymce_$v.zip", "url": "http://download.ephox.com/tinymce/community/tinymce_$v.zip",
"dest": "program/js", "dest": "program/js",
"sha1": "7f988f3899aebee6d49bd55e981331da07eee6c5", "sha1": "d7fced05acdeeb78299585ea9909b0de2b3d759d",
"license": "LGPL", "license": "LGPL",
"copyright": "Copyright (c) 1999-2015 Ephox Corp. All rights reserved", "copyright": "Copyright (c) 1999-2015 Ephox Corp. All rights reserved",
"rm": "program/js/tinymce", "rm": "program/js/tinymce",
@ -56,7 +56,7 @@
}, },
{ {
"lib": "tinymce-langs", "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", "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" "dest": "program/js/tinymce"
}, },
@ -88,10 +88,10 @@
{ {
"lib": "bootstrap", "lib": "bootstrap",
"name": "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", "url": "https://github.com/twbs/bootstrap/releases/download/v$v/bootstrap-$v-dist.zip",
"dest": "skins/elastic/deps", "dest": "skins/elastic/deps",
"sha1": "90abd51cf27a3dc4823eed6c1de620725f068b6c", "sha1": "d43982739f8f2bb05ef2a2fa55c358ce7e30a72b",
"license": "MIT", "license": "MIT",
"flat": true, "flat": true,
"map": { "map": {

@ -448,9 +448,8 @@ class enigma extends rcube_plugin
foreach ($keys as $key) { foreach ($keys as $key) {
if ($key->get_type() === enigma_key::TYPE_KEYPAIR) { if ($key->get_type() === enigma_key::TYPE_KEYPAIR) {
$listing[] = html::tag('li', null, $listing[] = html::tag('li', null,
html::tag('span', 'identity', html::quote($key->name)) .
' ' .
html::tag('strong', 'uid', html::quote($key->id)) 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 // add button linking to enigma key management
$content .= html::p( $button_attr = array(
null,
html::a(
array(
'class' => 'button', 'class' => 'button',
'href' => $this->rc->url(array('action' => 'plugin.enigmakeys')), 'href' => $this->rc->url(array('action' => 'plugin.enigmakeys')),
'target' => '_parent', 'target' => '_parent',
),
$this->gettext('managekeys'))
); );
$content .= html::p(null, html::a($button_attr, $this->gettext('managekeys')));
// rename class to avoid Mailvelope key management to kick in // rename class to avoid Mailvelope key management to kick in
$p['form']['encryption']['attrs'] = array('class' => 'enigma-identity-encryption'); $p['form']['encryption']['attrs'] = array('class' => 'enigma-identity-encryption');

@ -388,13 +388,10 @@ class enigma_driver_gnupg extends enigma_driver
} }
// need to delete private key first // need to delete private key first
else if ($code == enigma_error::DELKEY) { else if ($code == enigma_error::DELKEY) {
$key = $this->get_key($keyid); $result = $this->delete_privkey($keyid);
for ($i = count($key->subkeys) - 1; $i >= 0; $i--) {
$type = ($key->subkeys[$i]->usage & enigma_key::CAN_ENCRYPT) ? 'priv' : 'pub'; if ($result === true) {
$result = $this->{'delete_' . $type . 'key'}($key->subkeys[$i]->id); $result = $this->delete_pubkey($keyid);
if ($result !== true) {
break;
}
} }
} }
} }

@ -1119,6 +1119,11 @@ class enigma_ui
*/ */
function message_ready($p) 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']); $savedraft = !empty($_POST['_draft']) && empty($_GET['_saveonly']);
$sign_enable = (bool) rcube_utils::get_input_value('_enigma_sign', rcube_utils::INPUT_POST); $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); $encrypt_enable = (bool) rcube_utils::get_input_value('_enigma_encrypt', rcube_utils::INPUT_POST);

@ -14,7 +14,8 @@
<roundcube:object name="searchform" id="keysearch" wrapper="searchbar toolbar" <roundcube:object name="searchform" id="keysearch" wrapper="searchbar toolbar"
label="keysearchform" buttontitle="findkeys" ariatag="h2" /> label="keysearchform" buttontitle="findkeys" ariatag="h2" />
<div class="scroller"> <div class="scroller">
<roundcube:object name="keyslist" id="keys-table" class="listing" role="listbox" noheader="true" data-list="keys_list" /> <roundcube:object name="keyslist" id="keys-table" class="listing" role="listbox" noheader="true"
data-list="keys_list"data-label-msg="listempty" />
</div> </div>
<div class="pagenav footer toolbar small" role="toolbar"> <div class="pagenav footer toolbar small" role="toolbar">
<roundcube:button command="firstpage" type="link" <roundcube:button command="firstpage" type="link"

@ -114,6 +114,9 @@ class krb_authentication extends rcube_plugin
function managesieve_connect($args) function managesieve_connect($args)
{ {
if ((!isset($args['auth_type']) || $args['auth_type'] == 'GSSAPI') && !empty($_SERVER['REMOTE_USER']) && !empty($_SERVER['KRB5CCNAME'])) { if ((!isset($args['auth_type']) || $args['auth_type'] == 'GSSAPI') && !empty($_SERVER['REMOTE_USER']) && !empty($_SERVER['KRB5CCNAME'])) {
// Load plugin's config file
$this->load_config();
$rcmail = rcmail::get_instance(); $rcmail = rcmail::get_instance();
$context = $rcmail->config->get('krb_authentication_context'); $context = $rcmail->config->get('krb_authentication_context');

@ -1,3 +1,6 @@
- Added support for 'editheader' extension - RFC5293 (#5954)
- Fix bug where show_real_foldernames setting wasn't respected (#6422)
* version 9.1 [2018-05-19] * version 9.1 [2018-05-19]
----------------------------------------------------------- -----------------------------------------------------------
- Added GSSAPI support (#5779) - Added GSSAPI support (#5779)

@ -313,14 +313,10 @@ class rcube_sieve
if ($this->script) { if ($this->script) {
$supported = $this->script->get_extensions(); $supported = $this->script->get_extensions();
foreach ($ext as $idx => $ext_name) { $ext = array_values(array_intersect($ext, $supported));
if (!in_array($ext_name, $supported)) {
unset($ext[$idx]);
}
}
} }
return array_values($ext); return $ext;
} }
/** /**

@ -688,6 +688,15 @@ class rcube_sieve_engine
$notifymessages = rcube_utils::get_input_value('_action_notifymessage', 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); $notifyfrom = rcube_utils::get_input_value('_action_notifyfrom', rcube_utils::INPUT_POST);
$notifyimp = rcube_utils::get_input_value('_action_notifyimportance', rcube_utils::INPUT_POST); $notifyimp = rcube_utils::get_input_value('_action_notifyimportance', rcube_utils::INPUT_POST);
$addheader_name = rcube_utils::get_input_value('_action_addheader_name', rcube_utils::INPUT_POST);
$addheader_value = rcube_utils::get_input_value('_action_addheader_value', rcube_utils::INPUT_POST, true);
$addheader_pos = rcube_utils::get_input_value('_action_addheader_pos', rcube_utils::INPUT_POST);
$delheader_name = rcube_utils::get_input_value('_action_delheader_name', rcube_utils::INPUT_POST);
$delheader_value = rcube_utils::get_input_value('_action_delheader_value', rcube_utils::INPUT_POST, true);
$delheader_pos = rcube_utils::get_input_value('_action_delheader_pos', rcube_utils::INPUT_POST);
$delheader_index = rcube_utils::get_input_value('_action_delheader_index', rcube_utils::INPUT_POST);
$delheader_op = rcube_utils::get_input_value('_action_delheader_op', rcube_utils::INPUT_POST);
$delheader_comp = rcube_utils::get_input_value('_action_delheader_comp', rcube_utils::INPUT_POST);
// we need a "hack" for radiobuttons // we need a "hack" for radiobuttons
foreach ($sizeitems as $item) foreach ($sizeitems as $item)
@ -1091,6 +1100,47 @@ class rcube_sieve_engine
$this->form['actions'][$i]['target'] = $_target; $this->form['actions'][$i]['target'] = $_target;
break; break;
case 'addheader':
case 'deleteheader':
$this->form['actions'][$i]['name'] = trim($type == 'addheader' ? $addheader_name[$idx] : $delheader_name[$idx]);
$this->form['actions'][$i]['value'] = $type == 'addheader' ? $addheader_value[$idx] : $delheader_value[$idx];
$this->form['actions'][$i]['last'] = ($type == 'addheader' ? $addheader_pos[$idx] : $delheader_pos[$idx]) == 'last';
if (empty($this->form['actions'][$i]['name'])) {
$this->errors['actions'][$i]['name'] = $this->plugin->gettext('cannotbeempty');
}
else if (!preg_match('/^[0-9a-z_-]+$/i', $this->form['actions'][$i]['name'])) {
$this->errors['actions'][$i]['name'] = $this->plugin->gettext('forbiddenchars');
}
if ($type == 'deleteheader') {
foreach ((array) $this->form['actions'][$i]['value'] as $pidx => $pattern) {
if (empty($pattern)) {
unset($this->form['actions'][$i]['value'][$pidx]);
}
}
$this->form['actions'][$i]['match-type'] = $delheader_op[$idx];
$this->form['actions'][$i]['comparator'] = $delheader_comp[$idx];
$this->form['actions'][$i]['index'] = $delheader_index[$idx];
if (empty($this->form['actions'][$i]['index'])) {
if (!empty($this->form['actions'][$i]['last'])) {
$this->errors['actions'][$i]['index'] = $this->plugin->gettext('lastindexempty');
}
}
else if (!preg_match('/^[0-9]+$/i', $this->form['actions'][$i]['index'])) {
$this->errors['actions'][$i]['index'] = $this->plugin->gettext('forbiddenchars');
}
}
else {
if (empty($this->form['actions'][$i]['value'])) {
$this->errors['actions'][$i]['value'] = $this->plugin->gettext('cannotbeempty');
}
}
break;
case 'vacation': case 'vacation':
$reason = $this->strip_value($reasons[$idx]); $reason = $this->strip_value($reasons[$idx]);
$interval_type = $interval_types[$idx] == 'seconds' ? 'seconds' : 'days'; $interval_type = $interval_types[$idx] == 'seconds' ? 'seconds' : 'days';
@ -1714,38 +1764,6 @@ class rcube_sieve_engine
$aout .= $this->list_input($id, 'custom_var', $customv, isset($customv), $aout .= $this->list_input($id, 'custom_var', $customv, isset($customv),
$this->error_class($id, 'test', 'header', 'custom_var'), 15) . "\n"; $this->error_class($id, 'test', 'header', 'custom_var'), 15) . "\n";
// matching type select (operator)
$select_op = new html_select(array('name' => "_rule_op[]", 'id' => 'rule_op'.$id,
'style' => 'display:' .(!in_array($rule['test'], array('size', 'duplicate')) ? 'inline' : 'none'),
'class' => 'operator_selector',
'onchange' => 'rule_op_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');
$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 (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');
}
$test = self::rule_test($rule); $test = self::rule_test($rule);
$target = ''; $target = '';
@ -1796,7 +1814,7 @@ class rcube_sieve_engine
$tout .= $select_msg->show($test); $tout .= $select_msg->show($test);
} }
$tout .= $select_op->show($test); $tout .= $this->match_type_selector('rule_op', $id, $test, $rule['test']);
$tout .= $this->list_input($id, 'rule_target', $target, $tout .= $this->list_input($id, 'rule_target', $target,
$rule['test'] != 'size' && $rule['test'] != 'exists' && $rule['test'] != 'duplicate', $rule['test'] != 'size' && $rule['test'] != 'exists' && $rule['test'] != 'duplicate',
$this->error_class($id, 'test', 'target', 'rule_target')) . "\n"; $this->error_class($id, 'test', 'target', 'rule_target')) . "\n";
@ -1854,18 +1872,10 @@ class rcube_sieve_engine
$mout .= '</div>'; $mout .= '</div>';
// Advanced modifiers (comparators) // 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'; $need_comp = $rule['test'] != 'size' && $rule['test'] != 'duplicate';
$mout .= '<div id="rule_comp' .$id. '" class="adv input-group"' . (!$need_comp ? ' style="display:none"' : '') . '>'; $mout .= '<div id="rule_comp' .$id. '" class="adv input-group"' . (!$need_comp ? ' style="display:none"' : '') . '>';
$mout .= html::span('label input-group-prepend', html::span('input-group-text', rcube::Q($this->plugin->gettext('comparator')))); $mout .= html::span('label input-group-prepend', html::span('input-group-text', rcube::Q($this->plugin->gettext('comparator'))));
$mout .= $select_comp->show($rule['comparator']); $mout .= $this->comparator_selector($rule['comparator'], 'rule_comp', $id);
$mout .= '</div>'; $mout .= '</div>';
// Advanced modifiers (mime) // 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, $select_action = new html_select(array('name' => "_action_type[$id]", 'id' => 'action_type'.$id,
'onchange' => 'action_type_select(' .$id .')')); 'onchange' => 'action_type_select(' .$id .')'));
if (in_array('fileinto', $this->exts)) 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)) 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($this->plugin->gettext('messagecopyto'), 'fileinto_copy');
$select_action->add(rcube::Q($this->plugin->gettext('messageredirect')), 'redirect'); $select_action->add($this->plugin->gettext('messageredirect'), 'redirect');
if (in_array('copy', $this->exts)) 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)) 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)) 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)) if (in_array('vacation', $this->exts))
$select_action->add(rcube::Q($this->plugin->gettext('messagereply')), 'vacation'); $select_action->add($this->plugin->gettext('messagereply'), 'vacation');
$select_action->add(rcube::Q($this->plugin->gettext('messagedelete')), 'discard'); $select_action->add($this->plugin->gettext('messagedelete'), 'discard');
if (in_array('imapflags', $this->exts) || in_array('imap4flags', $this->exts)) { if (in_array('imapflags', $this->exts) || in_array('imap4flags', $this->exts)) {
$select_action->add(rcube::Q($this->plugin->gettext('setflags')), 'setflag'); $select_action->add($this->plugin->gettext('setflags'), 'setflag');
$select_action->add(rcube::Q($this->plugin->gettext('addflags')), 'addflag'); $select_action->add($this->plugin->gettext('addflags'), 'addflag');
$select_action->add(rcube::Q($this->plugin->gettext('removeflags')), 'removeflag'); $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)) { 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)) { 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($this->plugin->gettext('messagekeep'), 'keep');
$select_action->add(rcube::Q($this->plugin->gettext('rulestop')), 'stop'); $select_action->add($this->plugin->gettext('rulestop'), 'stop');
$select_type = $action['type']; $select_type = $action['type'];
if (in_array($action['type'], array('fileinto', 'redirect')) && $action['copy']) { if (in_array($action['type'], array('fileinto', 'redirect')) && $action['copy']) {
@ -2190,7 +2204,7 @@ class rcube_sieve_engine
} }
} }
$out .= '<div id="action_vacation' .$id.'" style="display:' .($action['type']=='vacation' ? 'inline' : 'none') .'">'; $out .= '<div id="action_vacation' .$id.'" style="display:' .($action['type']=='vacation' ? 'inline' : 'none') .'" class="composite">';
$out .= '<span class="label">'. rcube::Q($this->plugin->gettext('vacationreason')) .'</span><br>'; $out .= '<span class="label">'. rcube::Q($this->plugin->gettext('vacationreason')) .'</span><br>';
$out .= html::tag('textarea', array( $out .= html::tag('textarea', array(
'name' => '_action_reason[' . $id . ']', 'name' => '_action_reason[' . $id . ']',
@ -2353,7 +2367,7 @@ class rcube_sieve_engine
} }
// @TODO: nice UI for mailto: (other methods too) URI parameters // @TODO: nice UI for mailto: (other methods too) URI parameters
$out .= '<div id="action_notify' .$id.'" style="display:' .($action['type'] == 'notify' ? 'inline' : 'none') .'">'; $out .= '<div id="action_notify' .$id.'" style="display:' .($action['type'] == 'notify' ? 'inline' : 'none') .'" class="composite">';
$out .= '<span class="label">' .rcube::Q($this->plugin->gettext('notifytarget')) . '</span><br>'; $out .= '<span class="label">' .rcube::Q($this->plugin->gettext('notifytarget')) . '</span><br>';
$out .= '<div class="input-group">'; $out .= '<div class="input-group">';
$out .= $select_method->show($method); $out .= $select_method->show($method);
@ -2393,6 +2407,85 @@ class rcube_sieve_engine
$this->error_class($id, 'action', 'options', 'action_notifyoption'), 30) . '</div>'; $this->error_class($id, 'action', 'options', 'action_notifyoption'), 30) . '</div>';
$out .= '</div>'; $out .= '</div>';
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 .= '<div id="action_addheader' .$id.'" style="display:' .($action['type'] == 'addheader' ? 'inline' : 'none') .'" class="composite">';
$out .= '<label class="label" for="action_addheader_name' . $id .'">' .rcube::Q($this->plugin->gettext('headername')) . '</label><br>';
$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 .= '<br><label class="label" for="action_addheader_value' . $id .'">'. rcube::Q($this->plugin->gettext('headervalue')) .'</label><br>';
$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 .= '<br><label class="label" for="action_addheader_pos' . $id .'">'. rcube::Q($this->plugin->gettext('headerpos')) .'</label><br>';
$out .= $pos1_selector->show($action['pos']);
$out .= '</div>';
// deleteheader
$out .= '<div id="action_deleteheader' .$id.'" style="display:' .($action['type'] == 'deleteheader' ? 'inline' : 'none') .'" class="composite">';
$out .= '<label class="label" for="action_delheader_name' . $id .'">' .rcube::Q($this->plugin->gettext('headername')) . '</label><br>';
$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 .= '<br><label class="label" for="action_delheader_value' . $id .'">'. rcube::Q($this->plugin->gettext('headerpatterns')) .'</label><br>';
$out .= $this->list_input($id, 'action_delheader_value', $action['value'], true,
$this->error_class($id, 'action', 'value', 'action_delheader_value')) . "\n";
$out .= '<br><div class="adv input-group">';
$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 .= '</div>';
$out .= '<div class="adv input-group">';
$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 .= '</div>';
$out .= '<br><label class="label" for="action_delheader_index' . $id .'">'. rcube::Q($this->plugin->gettext('headeroccurrence')) .'</label><br>';
$out .= '<div class="input-group">';
$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 .= '</div></div>';
}
// mailbox select // mailbox select
if ($action['type'] == 'fileinto') { if ($action['type'] == 'fileinto') {
// make sure non-existing (or unsubscribed) mailbox is listed (#1489956) // make sure non-existing (or unsubscribed) mailbox is listed (#1489956)
@ -2405,7 +2498,6 @@ class rcube_sieve_engine
} }
$select = $this->rc->folder_selector(array( $select = $this->rc->folder_selector(array(
'realnames' => false,
'maxlength' => 100, 'maxlength' => 100,
'id' => 'action_mailbox' . $id, 'id' => 'action_mailbox' . $id,
'name' => "_action_mailbox[$id]", 'name' => "_action_mailbox[$id]",
@ -2935,4 +3027,63 @@ class rcube_sieve_engine
return $headers; 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);
}
} }

@ -312,7 +312,7 @@ class rcube_sieve_forward extends rcube_sieve_engine
$table = new html_table(array('cols' => 2)); $table = new html_table(array('cols' => 2));
$table->add('title', html::label('forward_action', $this->plugin->gettext('forward.action'))); $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('title', html::label('forward_status', $this->plugin->gettext('forward.status')));
$table->add(null, $status->show(!isset($this->forward['disabled']) || $this->forward['disabled'] ? 'off' : 'on')); $table->add(null, $status->show(!isset($this->forward['disabled']) || $this->forward['disabled'] ? 'off' : 'on'));

@ -31,6 +31,7 @@ class rcube_sieve_script
'copy', // RFC3894 'copy', // RFC3894
'date', // RFC5260 'date', // RFC5260
'duplicate', // RFC7352 'duplicate', // RFC7352
'editheader', // RFC5293
'enotify', // RFC5435 'enotify', // RFC5435
'envelope', // RFC5228 'envelope', // RFC5228
'ereject', // RFC5429 'ereject', // RFC5429
@ -422,6 +423,26 @@ class rcube_sieve_script
. self::escape_string($action['target']); . self::escape_string($action['target']);
break; 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 'keep':
case 'discard': case 'discard':
case 'stop': case 'stop':
@ -908,6 +929,21 @@ class rcube_sieve_script
$result[] = $action; $result[] = $action;
break; 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 'reject':
case 'ereject': case 'ereject':
case 'setflag': case 'setflag':

@ -381,7 +381,7 @@ class rcube_sieve_vacation extends rcube_sieve_engine
if ($this->rc->config->get('managesieve_vacation') != 2 && !empty($this->vacation['list'])) { if ($this->rc->config->get('managesieve_vacation') != 2 && !empty($this->vacation['list'])) {
$after = new html_select(array('name' => 'vacation_after', 'id' => 'vacation_after')); $after = new html_select(array('name' => 'vacation_after', 'id' => 'vacation_after'));
$after->add('', ''); $after->add('---', '');
foreach ($this->vacation['list'] as $idx => $rule) { foreach ($this->vacation['list'] as $idx => $rule) {
$after->add($rule, $idx); $after->add($rule, $idx);
} }
@ -461,7 +461,7 @@ class rcube_sieve_vacation extends rcube_sieve_engine
} }
// redirect target // redirect target
$action_target = ' <span id="action_target_span" class="input-group" style="display:' . ($redirect ? 'inline' : 'none') . '">' $action_target = ' <span id="action_target_span" class="input-group"' . (!$redirect ? ' style="display:none"' : '') . '>'
. '<input type="text" name="action_target" id="action_target"' . '<input type="text" name="action_target" id="action_target"'
. ' value="' .($redirect ? rcube::Q($this->vacation['target'], 'strict', false) : '') . '"' . ' value="' .($redirect ? rcube::Q($this->vacation['target'], 'strict', false) : '') . '"'
. (!empty($domains) ? ' size="20"' : ' size="35"') . '/>' . (!empty($domains) ? ' size="20"' : ' size="35"') . '/>'
@ -505,7 +505,7 @@ class rcube_sieve_vacation extends rcube_sieve_engine
} }
$table->add('title', html::label('vacation_action', $this->plugin->gettext('vacation.action'))); $table->add('title', html::label('vacation_action', $this->plugin->gettext('vacation.action')));
$table->add('vacation', $action->show($this->vacation['action']) . $action_target); $table->add('vacation input-group input-group-combo', $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 .= html::tag('fieldset', $class, html::tag('legend', null, $this->plugin->gettext('vacation.advanced')) . $table->show($attrib));

@ -113,6 +113,18 @@ $labels['flagdeleted'] = 'Deleted';
$labels['flaganswered'] = 'Answered'; $labels['flaganswered'] = 'Answered';
$labels['flagflagged'] = 'Flagged'; $labels['flagflagged'] = 'Flagged';
$labels['flagdraft'] = 'Draft'; $labels['flagdraft'] = 'Draft';
$labels['addheader'] = 'Add header to the message';
$labels['deleteheader'] = 'Remove header from the message';
$labels['headername'] = 'Header name';
$labels['headervalue'] = 'Header value';
$labels['headerpos'] = 'Header position';
$labels['headeratstart'] = 'at the beginning';
$labels['headeratend'] = 'at the end';
$labels['headeroccurrence'] = 'Header occurrence/position';
$labels['headerfromstart'] = 'from start';
$labels['headerfromend'] = 'from end';
$labels['headerpatterns'] = 'Header value patterns';
$labels['headermatchtype'] = 'match type:';
$labels['setvariable'] = 'Set variable'; $labels['setvariable'] = 'Set variable';
$labels['setvarname'] = 'Variable name:'; $labels['setvarname'] = 'Variable name:';
$labels['setvarvalue'] = 'Variable value:'; $labels['setvarvalue'] = 'Variable value:';
@ -264,5 +276,6 @@ $messages['forwardsaved'] = 'Forward data saved successfully.';
$messages['emptyvacationbody'] = 'Body of vacation message is required!'; $messages['emptyvacationbody'] = 'Body of vacation message is required!';
$messages['duplicate.conflict.err'] = 'Both header and unique identifier are not allowed.'; $messages['duplicate.conflict.err'] = 'Both header and unique identifier are not allowed.';
$messages['disabledaction'] = 'Action not permitted.'; $messages['disabledaction'] = 'Action not permitted.';
$messages['lastindexempty'] = 'Index is required when counting from end';
?> ?>

@ -766,7 +766,9 @@ function action_type_select(id)
vacation: document.getElementById('action_vacation' + id), vacation: document.getElementById('action_vacation' + id),
forward: document.getElementById('action_forward' + id), forward: document.getElementById('action_forward' + id),
set: document.getElementById('action_set' + id), set: document.getElementById('action_set' + id),
notify: document.getElementById('action_notify' + id) notify: document.getElementById('action_notify' + id),
addheader: document.getElementById('action_addheader' + id),
deleteheader: document.getElementById('action_deleteheader' + id)
}; };
if (v == 'fileinto' || v == 'fileinto_copy') { if (v == 'fileinto' || v == 'fileinto_copy') {
@ -781,17 +783,8 @@ function action_type_select(id)
else if (v.match(/^(add|set|remove)flag$/)) { else if (v.match(/^(add|set|remove)flag$/)) {
enabled.flags = 1; enabled.flags = 1;
} }
else if (v == 'vacation') { else if (v.match(/^(vacation|forward|set|notify|addheader|deleteheader)$/)) {
enabled.vacation = 1; enabled[v] = 1;
}
else if (v == 'forward') {
enabled.forward = 1;
}
else if (v == 'set') {
enabled.set = 1;
}
else if (v == 'notify') {
enabled.notify = 1;
} }
for (var x in elems) { for (var x in elems) {

@ -244,12 +244,6 @@ td.rowtargets div.adv input
margin-bottom: 1px; margin-bottom: 1px;
} }
html.mozilla #filter-form select
{
padding-top: 3px;
padding-bottom: 3px;
}
input.disabled, input.disabled:hover input.disabled, input.disabled:hover
{ {
color: #999999; color: #999999;

@ -0,0 +1,36 @@
require ["editheader"];
# rule:[test-addheader1]
if true
{
addheader "X-Sieve-Filtered" "<test@test.com>";
}
# rule:[test-addheader2]
if not header :contains "X-Sieve-Filtered" "<test@test.com>"
{
addheader :last "X-Sieve-Filtered" "<test@test.com>";
}
# rule:[test-deleteheader1]
if true
{
deleteheader :index 1 :contains "Delivered-To" ["bob@example.com","test@test.com"];
}
# rule:[test-deleteheader2]
if true
{
deleteheader :index 2 :last :contains :comparator "i;octet" "Delivered-To" "test@test.com";
}
# rule:[test-deleteheader3]
if true
{
deleteheader "Delivered-To";
}
# rule:[test-deleteheader4]
if true
{
deleteheader :index 3 :last :contains "Delivered-To";
}
# rule:[test-deleteheader5]
if true
{
deleteheader "Delivered-To" "test";
}

@ -26,7 +26,7 @@ class new_user_identity extends rcube_plugin
function lookup_user_name($args) function lookup_user_name($args)
{ {
if ($this->init_ldap($args['host'])) { if ($this->init_ldap($args['host'], $args['user'])) {
$results = $this->ldap->search('*', $args['user'], true); $results = $this->ldap->search('*', $args['user'], true);
if (count($results->records) == 1) { if (count($results->records) == 1) {
@ -96,7 +96,7 @@ class new_user_identity extends rcube_plugin
return $args; return $args;
} }
private function init_ldap($host) private function init_ldap($host, $user)
{ {
if ($this->ldap) { if ($this->ldap) {
return $this->ldap->ready; return $this->ldap->ready;
@ -112,11 +112,17 @@ class new_user_identity extends rcube_plugin
return false; return false;
} }
$this->ldap = new new_user_identity_ldap_backend( $debug = $this->rc->config->get('ldap_debug');
$ldap_config[$addressbook], $domain = $this->rc->config->mail_domain($host);
$this->rc->config->get('ldap_debug'), $props = $ldap_config[$addressbook];
$this->rc->config->mail_domain($host),
$match); // Set 'username' prop for correct variables substitution (#6419)
$props['username'] = $user;
if (!strpos($user, '@')) {
$props['username'] .= '@' . $domain;
}
$this->ldap = new new_user_identity_ldap_backend($props, $debug, $domain, $match);
return $this->ldap->ready; return $this->ldap->ready;
} }

@ -46,6 +46,7 @@
2.19. Vpopmail daemon (vpopmaild) 2.19. Vpopmail daemon (vpopmaild)
2.20. Plesk (Plesk RPC-API) 2.20. Plesk (Plesk RPC-API)
2.21. Kpasswd 2.21. Kpasswd
2.22. Modoboa
3. Driver API 3. Driver API
4. Sudo setup 4. Sudo setup
@ -362,6 +363,13 @@
See config.inc.php.dist file for configuration description. See config.inc.php.dist file for configuration description.
2.22. Modoboa
-----------------------------------
Driver to change the password in Modoboa servers.
See config.inc.php.dist file for configuration description.
3. Driver API 3. Driver API
------------- -------------

@ -3,7 +3,7 @@
"type": "roundcube-plugin", "type": "roundcube-plugin",
"description": "Password Change for Roundcube. Plugin adds a possibility to change user password using many methods (drivers) via Settings/Password tab.", "description": "Password Change for Roundcube. Plugin adds a possibility to change user password using many methods (drivers) via Settings/Password tab.",
"license": "GPLv3+", "license": "GPLv3+",
"version": "4.4", "version": "4.5",
"authors": [ "authors": [
{ {
"name": "Aleksander Machniak", "name": "Aleksander Machniak",

@ -482,3 +482,8 @@ $config['password_plesk_rpc_path'] = 'enterprise/control/agent.php';
// --------------------- // ---------------------
// Command to use // Command to use
$config['password_kpasswd_cmd'] = '/usr/bin/kpasswd'; $config['password_kpasswd_cmd'] = '/usr/bin/kpasswd';
// Modoboa Driver options
// ---------------------
// put token number from Modoboa server
$config['password_modoboa_api_token'] = '';

@ -0,0 +1,119 @@
<?php
/**
* Modoboa Password Driver
*
* Payload is json string containing username, oldPassword and newPassword
* Return value is a json string saying result: true if success.
*
* @version 1.0.1
* @author stephane @actionweb.fr
*
* Copyright (C) 2018, The Roundcube Dev Team
*
* 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
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*
* The driver need modoboa core 1.10.6 or later
*
* You need to define theses variables in plugin/password/config.inc.php
*
* $config['password_driver'] = 'modoboa'; // use modoboa as driver
* $config['password_modoboa_api_token'] = ''; // put token number from Modoboa server
* $config['password_minimum_length'] = 8; // select same number as in Modoboa server
*/
class rcube_modoboa_password
{
function save($curpass, $passwd)
{
// Init config access
$rcmail = rcmail::get_instance();
$ModoboaToken = $rcmail->config->get('password_modoboa_api_token');
$RoudCubeUsername = $_SESSION['username'];
$IMAPhost = $_SESSION['imap_host'];
// Call GET to fetch values from modoboa server
$curl = curl_init();
curl_setopt_array($curl, array(
CURLOPT_URL => "https://" . $IMAPhost . "/api/v1/accounts/?search=" . urlencode($RoudCubeUsername),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => array(
"Authorization: Token " . $ModoboaToken,
"Cache-Control: no-cache",
"Content-Type: application/json"
),
));
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
return PASSWORD_CONNECT_ERROR;
}
// Decode json string
$decoded = json_decode($response);
if (!is_array($decoded)) {
return PASSWORD_CONNECT_ERROR;
}
// Get user ID (pk)
$userid = $decoded[0]->pk;
// Encode json with new password
$ret['username'] = $decoded[0]->username;
$ret['role'] = $decoded[0]->role;
$ret['password'] = $passwd; // new password
$encoded = json_encode($ret);
// Call HTTP API Modoboa
$curl = curl_init();
curl_setopt_array($curl, array(
CURLOPT_URL => "https://" . $IMAPhost . "/api/v1/accounts/" . $userid . "/",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "PUT",
CURLOPT_POSTFIELDS => "" . $encoded . "",
CURLOPT_HTTPHEADER => array(
"Authorization: Token " . $ModoboaToken,
"Cache-Control: no-cache",
"Content-Type: application/json"
),
));
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
return PASSWORD_CONNECT_ERROR;
}
return PASSWORD_SUCCESS;
}
}

@ -41,7 +41,7 @@ define('PASSWORD_SUCCESS', 0);
*/ */
class password extends rcube_plugin class password extends rcube_plugin
{ {
public $task = 'settings|login'; public $task = '?(?!logout).*';
public $noframe = true; public $noframe = true;
public $noajax = true; public $noajax = true;
@ -65,7 +65,14 @@ class password extends rcube_plugin
$this->register_action('plugin.password', array($this, 'password_init')); $this->register_action('plugin.password', array($this, 'password_init'));
$this->register_action('plugin.password-save', array($this, 'password_save')); $this->register_action('plugin.password-save', array($this, 'password_save'));
} }
else if ($rcmail->config->get('password_force_new_user')) {
if ($rcmail->config->get('password_force_new_user')) {
if ($rcmail->config->get('newuserpassword') && $this->check_host_login_exceptions()) {
if (!($rcmail->task == 'settings' && strpos($rcmail->action, 'plugin.password') === 0)) {
$rcmail->output->command('redirect', '?_task=settings&_action=plugin.password&_first=1', false);
}
}
$this->add_hook('user_create', array($this, 'user_create')); $this->add_hook('user_create', array($this, 'user_create'));
$this->add_hook('login_after', array($this, 'login_after')); $this->add_hook('login_after', array($this, 'login_after'));
} }
@ -179,6 +186,10 @@ class password extends rcube_plugin
// Reset session password // Reset session password
$_SESSION['password'] = $rcmail->encrypt($plugin['new_pass']); $_SESSION['password'] = $rcmail->encrypt($plugin['new_pass']);
if ($rcmail->config->get('newuserpassword')) {
$rcmail->user->save_prefs(array('newuserpassword' => false));
}
// Log password change // Log password change
if ($rcmail->config->get('password_log')) { if ($rcmail->config->get('password_log')) {
rcube::write_log('password', sprintf('Password changed for user %s (ID: %d) from %s', rcube::write_log('password', sprintf('Password changed for user %s (ID: %d) from %s',
@ -375,6 +386,9 @@ class password extends rcube_plugin
function login_after($args) function login_after($args)
{ {
if ($this->newuser && $this->check_host_login_exceptions()) { if ($this->newuser && $this->check_host_login_exceptions()) {
$rcmail = rcmail::get_instance();
$rcmail->user->save_prefs(array('newuserpassword' => true));
$args['_task'] = 'settings'; $args['_task'] = 'settings';
$args['_action'] = 'plugin.password'; $args['_action'] = 'plugin.password';
$args['_first'] = 'true'; $args['_first'] = 'true';
@ -428,12 +442,10 @@ class password extends rcube_plugin
$rcmail = rcmail::get_instance(); $rcmail = rcmail::get_instance();
$prefix = ''; $prefix = '';
$crypted = ''; $crypted = '';
$default = false;
if (empty($method) || $method == 'default') { if (empty($method) || $method == 'default') {
$method = $rcmail->config->get('password_algorithm'); $method = $rcmail->config->get('password_algorithm');
$prefixed = $rcmail->config->get('password_algorithm_prefix'); $prefixed = $rcmail->config->get('password_algorithm_prefix');
$default = true;
} }
else if ($method == 'crypt') { // deprecated else if ($method == 'crypt') { // deprecated
if (!($method = $rcmail->config->get('password_crypt_hash'))) { if (!($method = $rcmail->config->get('password_crypt_hash'))) {
@ -622,7 +634,7 @@ class password extends rcube_plugin
return false; return false;
} }
if (!$default) { if (!$prefixed) {
$prefixed = (bool) $rcmail->config->get('password_dovecotpw_with_method'); $prefixed = (bool) $rcmail->config->get('password_dovecotpw_with_method');
} }

@ -458,7 +458,7 @@ class rcmail extends rcube
// add some basic labels to client // add some basic labels to client
$this->output->add_label('loading', 'servererror', 'connerror', 'requesttimedout', $this->output->add_label('loading', 'servererror', 'connerror', 'requesttimedout',
'refreshing', 'windowopenerror', 'uploadingmany', 'close', 'save', 'cancel', 'refreshing', 'windowopenerror', 'uploadingmany', 'uploading', 'close', 'save', 'cancel',
'alerttitle', 'confirmationtitle', 'delete', 'continue', 'ok'); 'alerttitle', 'confirmationtitle', 'delete', 'continue', 'ok');
return $this->output; return $this->output;
@ -926,12 +926,6 @@ class rcmail extends rcube
$error = array('code' => 403, 'message' => "Request security check failed"); $error = array('code' => 403, 'message' => "Request security check failed");
self::raise_error($error, false, true); self::raise_error($error, false, true);
} }
// check referer if configured
if ($this->config->get('referer_check') && !rcube_utils::check_referer()) {
$error = array('code' => 403, 'message' => "Referer check failed");
self::raise_error($error, true, true);
}
} }
/** /**

@ -5,7 +5,7 @@
| rcmail_install.php | | rcmail_install.php |
| | | |
| This file is part of the Roundcube Webmail package | | This file is part of the Roundcube Webmail package |
| Copyright (C) 2008-2016, The Roundcube Dev Team | | Copyright (C) 2008-2018, The Roundcube Dev Team |
| | | |
| Licensed under the GNU General Public License version 3 or | | Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. | | any later version with exceptions for skins & plugins. |
@ -33,7 +33,7 @@ class rcmail_install
public $bool_config_props = array(); public $bool_config_props = array();
public $local_config = array('db_dsnw', 'default_host', 'support_url', 'des_key', 'plugins'); public $local_config = array('db_dsnw', 'default_host', 'support_url', 'des_key', 'plugins');
public $obsolete_config = array('db_backend', 'db_max_length', 'double_auth', 'preview_pane'); public $obsolete_config = array('db_backend', 'db_max_length', 'double_auth', 'preview_pane', 'debug_level', 'referer_check');
public $replaced_config = array( public $replaced_config = array(
'skin_path' => 'skin', 'skin_path' => 'skin',
'locale_string' => 'language', 'locale_string' => 'language',

@ -483,8 +483,26 @@ EOF;
public function reset($all = false) public function reset($all = false)
{ {
$framed = $this->framed; $framed = $this->framed;
$task = $this->env['task'];
$env = $all ? null : array_intersect_key($this->env, array('extwin'=>1, 'framed'=>1)); $env = $all ? null : array_intersect_key($this->env, array('extwin'=>1, 'framed'=>1));
// keep jQuery-UI files
$css_files = $script_files = array();
foreach ($this->css_files as $file) {
if (strpos($file, 'plugins/jqueryui') === 0) {
$css_files[] = $file;
}
}
foreach ($this->script_files as $position => $files) {
foreach ($files as $file) {
if (strpos($file, 'plugins/jqueryui') === 0) {
$script_files[$position][] = $file;
}
}
}
parent::reset(); parent::reset();
// let some env variables survive // let some env variables survive
@ -492,16 +510,23 @@ EOF;
$this->framed = $framed || $this->env['framed']; $this->framed = $framed || $this->env['framed'];
$this->js_labels = array(); $this->js_labels = array();
$this->js_commands = array(); $this->js_commands = array();
$this->script_files = array();
$this->scripts = array(); $this->scripts = array();
$this->header = ''; $this->header = '';
$this->footer = ''; $this->footer = '';
$this->body = ''; $this->body = '';
$this->css_files = array();
$this->script_files = array();
// load defaults // load defaults
if (!$all) { if (!$all) {
$this->__construct(); $this->__construct();
} }
// Note: we merge jQuery-UI scripts after jQuery...
$this->css_files = array_merge($this->css_files, $css_files);
$this->script_files = array_merge_recursive($this->script_files, $script_files);
$this->set_env('orig_task', $task);
} }
/** /**
@ -573,7 +598,8 @@ EOF;
// if all js commands go to parent window we can ignore all // if all js commands go to parent window we can ignore all
// script files and skip rcube_webmail initialization (#1489792) // script files and skip rcube_webmail initialization (#1489792)
if ($framed) { // but not on error pages where skins may need jQuery, etc.
if ($framed && empty($this->js_env['server_error'])) {
$this->scripts = array(); $this->scripts = array();
$this->script_files = array(); $this->script_files = array();
$this->header = ''; $this->header = '';
@ -588,7 +614,7 @@ EOF;
// allow (legal) iframe content to be loaded // allow (legal) iframe content to be loaded
$iframe = $this->framed || $this->env['framed']; $iframe = $this->framed || $this->env['framed'];
if (!headers_sent() && $iframe && ($xopt = $this->app->config->get('x_frame_options', 'sameorigin'))) { if (!headers_sent() && $iframe && ($xopt = $this->app->config->get('x_frame_options', 'sameorigin'))) {
if (strtolower($xopt) != 'sameorigin') { if (strtolower($xopt) === 'deny') {
header('X-Frame-Options: sameorigin', true); header('X-Frame-Options: sameorigin', true);
} }
} }
@ -1263,7 +1289,7 @@ EOF;
} }
} }
// execute object handler function // execute object handler function
else if (function_exists($handler)) { else if (is_callable($handler)) {
$this->prepare_object_attribs($attrib); $this->prepare_object_attribs($attrib);
$content = call_user_func($handler, $attrib); $content = call_user_func($handler, $attrib);
} }
@ -1649,11 +1675,11 @@ EOF;
* Link an external script file * Link an external script file
* *
* @param string $file File URL * @param string $file File URL
* @param string $position Target position [head|foot] * @param string $position Target position [head|head_bottom|foot]
*/ */
public function include_script($file, $position='head') public function include_script($file, $position = 'head', $add_path = true)
{ {
if (!preg_match('|^https?://|i', $file) && $file[0] != '/') { if ($add_path && !preg_match('|^https?://|i', $file) && $file[0] != '/') {
$file = $this->file_mod($this->scripts_path . $file); $file = $this->file_mod($this->scripts_path . $file);
} }
@ -1742,6 +1768,19 @@ EOF;
} }
} }
$merge_script_files = function($output, $script) {
return $output . html::script($script);
};
$merge_scripts = function($output, $script) {
return $output . html::script(array(), $script);
};
// put docready commands into page footer
if (!empty($this->scripts['docready'])) {
$this->add_script('$(document).ready(function(){ ' . $this->scripts['docready'] . "\n});", 'foot');
}
// replace specialchars in content // replace specialchars in content
$page_title = html::quote($this->pagetitle); $page_title = html::quote($this->pagetitle);
$page_header = ''; $page_header = '';
@ -1757,40 +1796,15 @@ EOF;
$page_header.= $this->charset . '" />'."\n"; $page_header.= $this->charset . '" />'."\n";
} }
// definition of the code to be placed in the document header and footer // include scripts into header/footer
if (is_array($this->script_files['head'])) { $page_header .= array_reduce((array) $this->script_files['head'], $merge_script_files);
foreach ($this->script_files['head'] as $file) { $page_header .= array_reduce(array($this->scripts['head_top'] . $this->scripts['head']), $merge_scripts);
$page_header .= html::script($file); $page_header .= $this->header . "\n";
} $page_header .= array_reduce((array) $this->script_files['head_bottom'], $merge_script_files);
}
$head_script = $this->scripts['head_top'] . $this->scripts['head'];
if (!empty($head_script)) {
$page_header .= html::script(array(), $head_script);
}
if (!empty($this->header)) {
$page_header .= $this->header;
}
// put docready commands into page footer $page_footer .= array_reduce((array) $this->script_files['foot'], $merge_script_files);
if (!empty($this->scripts['docready'])) {
$this->add_script('$(document).ready(function(){ ' . $this->scripts['docready'] . "\n});", 'foot');
}
if (is_array($this->script_files['foot'])) {
foreach ($this->script_files['foot'] as $file) {
$page_footer .= html::script($file);
}
}
if (!empty($this->footer)) {
$page_footer .= $this->footer . "\n"; $page_footer .= $this->footer . "\n";
} $page_footer .= array_reduce((array) $this->scripts['foot'], $merge_scripts);
if (!empty($this->scripts['foot'])) {
$page_footer .= html::script(array(), $this->scripts['foot']);
}
// find page header // find page header
if ($hpos = stripos($output, '</head>')) { if ($hpos = stripos($output, '</head>')) {

@ -1526,7 +1526,7 @@ function rcube_webmail()
if (task == 'mail') if (task == 'mail')
url += '&_mbox=INBOX'; url += '&_mbox=INBOX';
else if (task == 'logout' && !this.env.server_error) { else if (task == 'logout') {
url = this.secure_url(url); url = this.secure_url(url);
this.clear_compose_data(); this.clear_compose_data();
} }
@ -4109,8 +4109,8 @@ function rcube_webmail()
var ul = $('<ul>').addClass('keylist').appendTo(content); var ul = $('<ul>').addClass('keylist').appendTo(content);
$.each(private_keys, function(i, key) { $.each(private_keys, function(i, key) {
$('<li>').appendTo(ul) $('<li>').appendTo(ul)
.append($('<span>').addClass('identity').text('<' + identity_email + '> ')) .append($('<strong>').addClass('fingerprint').text(String(key.fingerprint).toUpperCase()))
.append($('<strong>').addClass('fingerprint').text(String(key.fingerprint).toUpperCase())); .append($('<span>').addClass('identity').text('<' + identity_email + '> '));
}); });
} else { } else {
$('<p>').text(ref.get_label('encryptionnoprivkeysinmailvelope')).appendTo(content); $('<p>').text(ref.get_label('encryptionnoprivkeysinmailvelope')).appendTo(content);
@ -9680,7 +9680,7 @@ function rcube_webmail()
// catch Error: Permission denied to access property rcmail // catch Error: Permission denied to access property rcmail
try { try {
if (win && !win.closed) { if (win && !win.closed && win !== window) {
// try parent of the opener window, e.g. preview frame // try parent of the opener window, e.g. preview frame
if (deep && (!win.rcmail || win.rcmail.env.framed) && win.parent && win.parent.rcmail) if (deep && (!win.rcmail || win.rcmail.env.framed) && win.parent && win.parent.rcmail)
win = win.parent; win = win.parent;
@ -10016,12 +10016,12 @@ function rcube_webmail()
// some static methods // some static methods
rcube_webmail.long_subject_title = function(elem, indent) rcube_webmail.long_subject_title = function(elem, indent, text_elem)
{ {
if (!elem.title) { if (!elem.title) {
var $elem = $(elem); var $elem = $(text_elem || elem);
if ($elem.width() + (indent || 0) * 15 > $elem.parent().width()) if ($elem.width() + (indent || 0) * 15 > $elem.parent().width())
elem.title = rcube_webmail.subject_text(elem); elem.title = rcube_webmail.subject_text($elem[0]);
} }
}; };

@ -39,7 +39,7 @@ function rcube_text_editor(config, id)
abs_url = location.href.replace(/[?#].*$/, '').replace(/\/$/, ''), abs_url = location.href.replace(/[?#].*$/, '').replace(/\/$/, ''),
conf = { conf = {
selector: '#' + ($('#' + id).is('.mce_editor') ? id : 'fake-editor-id'), selector: '#' + ($('#' + id).is('.mce_editor') ? id : 'fake-editor-id'),
cache_suffix: 's=4071300', cache_suffix: 's=4080200',
theme: 'modern', theme: 'modern',
language: config.lang, language: config.lang,
content_css: rcmail.assets_path('program/resources/tinymce/content.css'), content_css: rcmail.assets_path('program/resources/tinymce/content.css'),

@ -1303,6 +1303,7 @@ class rcube
*/ */
public static function raise_error($arg = array(), $log = false, $terminate = false) public static function raise_error($arg = array(), $log = false, $terminate = false)
{ {
// handle PHP exceptions
if ($arg instanceof Exception) { if ($arg instanceof Exception) {
$arg = array( $arg = array(
'code' => $arg->getCode(), 'code' => $arg->getCode(),
@ -1328,6 +1329,13 @@ class rcube
$cli = php_sapi_name() == 'cli'; $cli = php_sapi_name() == 'cli';
$arg['cli'] = $cli;
$arg['log'] = $log;
$arg['terminate'] = $terminate;
// send error to external error tracking tool
$arg = self::$instance->plugins->exec_hook('raise_error', $arg);
// installer // installer
if (!$cli && class_exists('rcmail_install', false)) { if (!$cli && class_exists('rcmail_install', false)) {
$rci = rcmail_install::get_instance(); $rci = rcmail_install::get_instance();
@ -1592,7 +1600,8 @@ class rcube
* *
* @param object $message Reference to Mail_MIME object * @param object $message Reference to Mail_MIME object
* @param string $from Sender address string * @param string $from Sender address string
* @param array $mailto Array of recipient address strings * @param array|string $mailto Either a comma-separated list of recipients (RFC822 compliant),
* or an array of recipients, each RFC822 valid
* @param array $error SMTP error array (reference) * @param array $error SMTP error array (reference)
* @param string $body_file Location of file with saved message body (reference), * @param string $body_file Location of file with saved message body (reference),
* used when delay_file_io is enabled * used when delay_file_io is enabled

@ -2822,7 +2822,7 @@ class rcube_imap_generic
} }
// handle UNKNOWN-CTE response - RFC 3516, try again with standard BODY request // handle UNKNOWN-CTE response - RFC 3516, try again with standard BODY request
if ($binary && !$found && preg_match('/^' . $key . ' NO \[UNKNOWN-CTE\]/i', $line)) { if ($binary && !$found && preg_match('/^' . $key . ' NO \[(UNKNOWN-CTE|PARSE)\]/i', $line)) {
$binary = $initiated = false; $binary = $initiated = false;
continue; continue;
} }
@ -3131,6 +3131,11 @@ class rcube_imap_generic
list(, , $quota_root) = $this->tokenizeResponse($line, 3); list(, , $quota_root) = $this->tokenizeResponse($line, 3);
$quotas = $this->tokenizeResponse($line, 1); $quotas = $this->tokenizeResponse($line, 1);
if (empty($quotas)) {
continue;
}
foreach (array_chunk($quotas, 3) as $quota) { foreach (array_chunk($quotas, 3) as $quota) {
list($type, $used, $total) = $quota; list($type, $used, $total) = $quota;
$type = strtolower($type); $type = strtolower($type);

@ -300,7 +300,7 @@ class rcube_ldap extends rcube_addressbook
} }
// Get the pieces needed for variable replacement. // Get the pieces needed for variable replacement.
if ($fu = $rcube->get_user_email()) { if ($fu = ($rcube->get_user_email() ?: $this->prop['username'])) {
list($u, $d) = explode('@', $fu); list($u, $d) = explode('@', $fu);
} }
else { else {

@ -948,9 +948,11 @@ class rcube_message
private function add_part($part, $type = null) private function add_part($part, $type = null)
{ {
if ($this->check_context($part)) { if ($this->check_context($part)) {
// It may happen that we add the same part to the array many times
// use part ID index to prevent from duplicates
switch ($type) { switch ($type) {
case 'inline': $this->inline_parts[] = $part; break; case 'inline': $this->inline_parts[(string) $part->mime_id] = $part; break;
case 'attachment': $this->attachments[] = $part; break; case 'attachment': $this->attachments[(string) $part->mime_id] = $part; break;
default: $this->parts[] = $part; break; default: $this->parts[] = $part; break;
} }
} }

@ -596,8 +596,7 @@ class rcube_plugin_api
{ {
if (is_object($this->output) && $this->output->type == 'html') { if (is_object($this->output) && $this->output->type == 'html') {
$src = $this->resource_url($fn); $src = $this->resource_url($fn);
$this->output->add_header(html::tag('script', $this->output->include_script($src, 'head_bottom', false);
array('type' => "text/javascript", 'src' => $src)));
} }
} }

@ -98,13 +98,10 @@ class rcube_smtp
// Handle per-host socket options // Handle per-host socket options
rcube_utils::parse_socket_options($CONFIG['smtp_conn_options'], $smtp_host); rcube_utils::parse_socket_options($CONFIG['smtp_conn_options'], $smtp_host);
if (!empty($CONFIG['smtp_helo_host'])) { // Use valid EHLO/HELO host (#6408)
$helo_host = $CONFIG['smtp_helo_host']; $helo_host = $CONFIG['smtp_helo_host'] ?: rcube_utils::server_name();
} $helo_host = rcube_utils::idn_to_ascii($helo_host);
else if (!empty($_SERVER['SERVER_NAME'])) { if (!preg_match('/^[a-zA-Z0-9.:-]+$/', $helo_host)) {
$helo_host = rcube_utils::server_name();
}
else {
$helo_host = 'localhost'; $helo_host = 'localhost';
} }

@ -153,19 +153,6 @@ class rcube_utils
return filter_var($ip, FILTER_VALIDATE_IP) !== false; return filter_var($ip, FILTER_VALIDATE_IP) !== false;
} }
/**
* Check whether the HTTP referer matches the current request
*
* @return boolean True if referer is the same host+path, false if not
*/
public static function check_referer()
{
$uri = parse_url($_SERVER['REQUEST_URI']);
$referer = parse_url(self::request_header('Referer'));
return $referer['host'] == self::request_header('Host') && $referer['path'] == $uri['path'];
}
/** /**
* Replacing specials characters to a specific encoding type * Replacing specials characters to a specific encoding type
* *
@ -1364,4 +1351,41 @@ class rcube_utils
return $max_filesize; return $max_filesize;
} }
/**
* Detect and log last PREG operation error
*
* @param array $error Error data (line, file, code, message)
* @param bool $terminate Stop script execution
*
* @return bool True on error, False otherwise
*/
public static function preg_error($error = array(), $terminate = false)
{
if (($preg_error = preg_last_error()) != PREG_NO_ERROR) {
$errstr = "PCRE Error: $preg_error.";
if ($preg_error == PREG_BACKTRACK_LIMIT_ERROR) {
$errstr .= " Consider raising pcre.backtrack_limit!";
}
if ($preg_error == PREG_RECURSION_LIMIT_ERROR) {
$errstr .= " Consider raising pcre.recursion_limit!";
}
$error = array_merge(array('code' => 620, 'line' => __LINE__, 'file' => __FILE__), $error);
if (!empty($error['message'])) {
$error['message'] .= ' ' . $errstr;
}
else {
$error['message'] = $errstr;
}
rcube::raise_error($error, true, $terminate);
return true;
}
return false;
}
} }

@ -525,14 +525,14 @@ class rcube_vcard
{ {
// convert Apple X-ABRELATEDNAMES into X-* fields for better compatibility // convert Apple X-ABRELATEDNAMES into X-* fields for better compatibility
$vcard = preg_replace_callback( $vcard = preg_replace_callback(
'/item(\d+)\.(X-ABRELATEDNAMES)([^:]*?):(.*?)item\1.X-ABLabel:(?:_\$!<)?([\w-() ]*)(?:>!\$_)?./s', '/item(\d+)\.(X-ABRELATEDNAMES)([^:]*?):(.*?)item\1.X-ABLabel:(?:_\$!<)?([\w() -]*)(?:>!\$_)?./s',
array('self', 'x_abrelatednames_callback'), array('self', 'x_abrelatednames_callback'),
$vcard); $vcard);
// Cleanup // Cleanup
$vcard = preg_replace(array( $vcard = preg_replace(array(
// convert special types (like Skype) to normal type='skype' classes with this simple regex ;) // convert special types (like Skype) to normal type='skype' classes with this simple regex ;)
'/item(\d+)\.(TEL|EMAIL|URL)([^:]*?):(.*?)item\1.X-ABLabel:(?:_\$!<)?([\w-() ]*)(?:>!\$_)?./si', '/item(\d+)\.(TEL|EMAIL|URL)([^:]*?):(.*?)item\1.X-ABLabel:(?:_\$!<)?([\w() -]*)(?:>!\$_)?./si',
'/^item\d*\.X-AB.*$/mi', // remove cruft like item1.X-AB* '/^item\d*\.X-AB.*$/mi', // remove cruft like item1.X-AB*
'/^item\d*\./mi', // remove item1.ADR instead of ADR '/^item\d*\./mi', // remove item1.ADR instead of ADR
'/\n+/', // remove empty lines '/\n+/', // remove empty lines

@ -615,7 +615,12 @@ class rcube_washtml
'<html>', '<html>',
); );
$html = preg_replace($html_search, $html_replace, trim($html)); $html = preg_replace($html_search, $html_replace, $html);
$err = array('line' => __LINE__, 'file' => __FILE__, 'message' => "Could not clean up HTML!");
if ($html === null && rcube_utils::preg_error($err)) {
return '';
}
// Replace all of those weird MS Word quotes and other high characters // Replace all of those weird MS Word quotes and other high characters
$badwordchars = array( $badwordchars = array(
@ -638,24 +643,6 @@ class rcube_washtml
$html = str_replace($badwordchars, $fixedwordchars, $html); $html = str_replace($badwordchars, $fixedwordchars, $html);
// PCRE errors handling (#1486856), should we use something like for every preg_* use?
if ($html === null && ($preg_error = preg_last_error()) != PREG_NO_ERROR) {
$errstr = "Could not clean up HTML message! PCRE Error: $preg_error.";
if ($preg_error == PREG_BACKTRACK_LIMIT_ERROR) {
$errstr .= " Consider raising pcre.backtrack_limit!";
}
if ($preg_error == PREG_RECURSION_LIMIT_ERROR) {
$errstr .= " Consider raising pcre.recursion_limit!";
}
rcube::raise_error(array('code' => 620, 'type' => 'php',
'line' => __LINE__, 'file' => __FILE__,
'message' => $errstr), true, false);
return '';
}
// fix (unknown/malformed) HTML tags before "wash" // fix (unknown/malformed) HTML tags before "wash"
$html = preg_replace_callback('/(<(?!\!)[\/]*)([^\s>]+)([^>]*)/', array($this, 'html_tag_callback'), $html); $html = preg_replace_callback('/(<(?!\!)[\/]*)([^\s>]+)([^>]*)/', array($this, 'html_tag_callback'), $html);

@ -75,13 +75,13 @@ if (!is_array($COMPOSE)) {
// add some labels to client // add some labels to client
$OUTPUT->add_label('notuploadedwarning', 'savingmessage', 'siginserted', 'responseinserted', $OUTPUT->add_label('notuploadedwarning', 'savingmessage', 'siginserted', 'responseinserted',
'messagesaved', 'converting', 'editorwarning', 'uploading', 'uploadingmany', 'messagesaved', 'converting', 'editorwarning', 'discard',
'fileuploaderror', 'sendmessage', 'newresponse', 'responsename', 'responsetext', 'save', 'fileuploaderror', 'sendmessage', 'newresponse', 'responsename', 'responsetext', 'save',
'savingresponse', 'restoresavedcomposedata', 'restoremessage', 'delete', 'restore', 'ignore', 'savingresponse', 'restoresavedcomposedata', 'restoremessage', 'delete', 'restore', 'ignore',
'selectimportfile', 'messageissent', 'loadingdata', 'nopubkeyfor', 'nopubkeyforsender', 'selectimportfile', 'messageissent', 'loadingdata', 'nopubkeyfor', 'nopubkeyforsender',
'encryptnoattachments','encryptedsendialog','searchpubkeyservers', 'importpubkeys', 'encryptnoattachments','encryptedsendialog','searchpubkeyservers', 'importpubkeys',
'encryptpubkeysfound', 'search', 'close', 'import', 'keyid', 'keylength', 'keyexpired', 'encryptpubkeysfound', 'search', 'close', 'import', 'keyid', 'keylength', 'keyexpired',
'keyrevoked', 'keyimportsuccess', 'keyservererror', 'attaching', 'namex', 'attachmentrename', 'discard' 'keyrevoked', 'keyimportsuccess', 'keyservererror', 'attaching', 'namex', 'attachmentrename'
); );
$OUTPUT->set_pagetitle($RCMAIL->gettext('compose')); $OUTPUT->set_pagetitle($RCMAIL->gettext('compose'));

@ -1003,7 +1003,8 @@ function rcmail_washtml_callback($tagname, $attrib, $content, $washtml)
} }
// decode all escaped entities and reduce to ascii strings // decode all escaped entities and reduce to ascii strings
$stripped = preg_replace('/[^a-zA-Z\(:;]/', '', rcube_utils::xss_entity_decode($content)); $decoded = rcube_utils::xss_entity_decode($content);
$stripped = preg_replace('/[^a-zA-Z\(:;]/', '', $decoded);
// now check for evil strings like expression, behavior or url() // now check for evil strings like expression, behavior or url()
if (!preg_match('/expression|behavior|javascript:|import[^a]/i', $stripped)) { if (!preg_match('/expression|behavior|javascript:|import[^a]/i', $stripped)) {
@ -1011,7 +1012,7 @@ function rcmail_washtml_callback($tagname, $attrib, $content, $washtml)
$washtml->extlinks = true; $washtml->extlinks = true;
} }
else { else {
$out = html::tag('style', array('type' => 'text/css'), $content); $out = html::tag('style', array('type' => 'text/css'), $decoded);
} }
break; break;
} }
@ -1606,7 +1607,9 @@ function rcmail_search_filter($attrib)
$attrib['id'] = 'rcmlistfilter'; $attrib['id'] = 'rcmlistfilter';
} }
if (!rcube_utils::get_boolean($attrib['noevent'])) {
$attrib['onchange'] = rcmail_output::JS_OBJECT_NAME.'.filter_mailbox(this.value)'; $attrib['onchange'] = rcmail_output::JS_OBJECT_NAME.'.filter_mailbox(this.value)';
}
// Content-Type values of messages with attachments // Content-Type values of messages with attachments
// the same as in app.js:add_message_row() // the same as in app.js:add_message_row()
@ -1669,6 +1672,8 @@ function rcmail_message_error()
{ {
global $RCMAIL; global $RCMAIL;
// ... display message error page
if ($RCMAIL->output->template_exists('messageerror')) {
// Set env variables for messageerror.html template // Set env variables for messageerror.html template
if ($RCMAIL->action == 'show') { if ($RCMAIL->action == 'show') {
$mbox_name = $RCMAIL->storage->get_folder(); $mbox_name = $RCMAIL->storage->get_folder();
@ -1677,10 +1682,12 @@ function rcmail_message_error()
$RCMAIL->output->set_env('uid', null); $RCMAIL->output->set_env('uid', null);
} }
// display error message
$RCMAIL->output->show_message('messageopenerror', 'error'); $RCMAIL->output->show_message('messageopenerror', 'error');
// ... display message error page
$RCMAIL->output->send('messageerror'); $RCMAIL->output->send('messageerror');
}
else {
$RCMAIL->raise_error(array('code' => 410), false, true);
}
} }
function rcmail_message_import_form($attrib = array()) function rcmail_message_import_form($attrib = array())

@ -115,6 +115,12 @@ if (!$savedraft) {
'<p><br /></p>', '<p><br /></p>',
), ),
$message_body); $message_body);
rcube_utils::preg_error(array(
'line' => __LINE__,
'file' => __FILE__,
'message' => "Could not format HTML!"
), true);
} }
// Check spelling before send // Check spelling before send
@ -199,6 +205,12 @@ if (is_array($COMPOSE['attachments'])) {
$message_body = preg_replace($dispurl, '"cid:' . $cid . '"', $message_body); $message_body = preg_replace($dispurl, '"cid:' . $cid . '"', $message_body);
rcube_utils::preg_error(array(
'line' => __LINE__,
'file' => __FILE__,
'message' => "Could not replace an image reference!"
), true);
$MAIL_MIME->setHTMLBody($message_body); $MAIL_MIME->setHTMLBody($message_body);
if ($attachment['data']) if ($attachment['data'])

@ -20,11 +20,20 @@
+-----------------------------------------------------------------------+ +-----------------------------------------------------------------------+
*/ */
define('RC_COPYRIGHT', 'Copyright &copy; 2005-2018, The Roundcube Dev Team');
define('RC_LICENSE', 'This program is free software; you can redistribute it and/or modify it under the terms of the '
. '<a href="http://www.gnu.org/licenses/gpl.html" target="_blank">GNU General Public License</a> '
. 'as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.'
. '<br/>Some <a href="https://roundcube.net/license" target="_blank">exceptions</a> for skins &amp; plugins apply.');
$OUTPUT->set_pagetitle($RCMAIL->gettext('about')); $OUTPUT->set_pagetitle($RCMAIL->gettext('about'));
$OUTPUT->add_handler('supportlink', 'rcmail_supportlink'); $OUTPUT->add_handlers(array(
$OUTPUT->add_handler('pluginlist', 'rcmail_plugins_list'); 'supportlink' => 'rcmail_supportlink',
'pluginlist' => 'rcmail_plugins_list',
'copyright' => function() { return RC_COPYRIGHT; },
'license' => function() { return RC_LICENSE; },
));
$OUTPUT->send('about'); $OUTPUT->send('about');

@ -98,11 +98,13 @@ function rcmail_folder_form($attrib)
} }
$foldername = new html_inputfield(array('name' => '_name', 'id' => '_name', 'size' => 30)); $foldername = new html_inputfield(array('name' => '_name', 'id' => '_name', 'size' => 30));
$foldername = $foldername->show($folder); $foldername = '<span class="input-group">' . $foldername->show($folder);
if ($options['special'] && ($sname = $RCMAIL->localize_foldername($mbox, false, true)) != $folder) { if ($options['special'] && ($sname = $RCMAIL->localize_foldername($mbox, false, true)) != $folder) {
$foldername .= '&nbsp;(' . rcube::Q($sname) .')'; $foldername .= ' <span class="input-group-append"><span class="input-group-text">(' . rcube::Q($sname) .')</span></span>';
} }
$foldername .= '</span>';
} }
$form['props']['fieldsets']['location'] = array( $form['props']['fieldsets']['location'] = array(

@ -52,7 +52,7 @@ else {
$OUTPUT->include_script('list.js'); $OUTPUT->include_script('list.js');
$OUTPUT->add_handler('identityform', 'rcube_identity_form'); $OUTPUT->add_handler('identityform', 'rcube_identity_form');
$OUTPUT->set_env('identities_level', IDENTITIES_LEVEL); $OUTPUT->set_env('identities_level', IDENTITIES_LEVEL);
$OUTPUT->add_label('deleteidentityconfirm', 'uploading', 'generate', $OUTPUT->add_label('deleteidentityconfirm', 'generate',
'encryptioncreatekey', 'openmailvelopesettings', 'encryptionprivkeysinmailvelope', 'encryptioncreatekey', 'openmailvelopesettings', 'encryptionprivkeysinmailvelope',
'encryptionnoprivkeysinmailvelope', 'keypaircreatesuccess'); 'encryptionnoprivkeysinmailvelope', 'keypaircreatesuccess');

@ -433,7 +433,9 @@ function rcmail_folder_filter($attrib)
$attrib['id'] = 'rcmfolderfilter'; $attrib['id'] = 'rcmfolderfilter';
} }
if (!rcube_utils::get_boolean($attrib['noevent'])) {
$attrib['onchange'] = rcmail_output::JS_OBJECT_NAME . '.folder_filter(this.value)'; $attrib['onchange'] = rcmail_output::JS_OBJECT_NAME . '.folder_filter(this.value)';
}
$roots = array(); $roots = array();
$select = new html_select($attrib); $select = new html_select($attrib);

@ -242,7 +242,6 @@ function rcmail_prefs_input($name, $regex)
} }
if ($value !== null && strlen($value) && !preg_match($regex, $value)) { if ($value !== null && strlen($value) && !preg_match($regex, $value)) {
rcube::console($name);
$value = $RCMAIL->config->get($name); $value = $RCMAIL->config->get($name);
} }

@ -21,28 +21,8 @@
$rcmail = rcmail::get_instance(); $rcmail = rcmail::get_instance();
// browser is not compatible with this application
if ($ERROR_CODE == 409) {
$user_agent = htmlentities($_SERVER['HTTP_USER_AGENT']);
$__error_title = 'Your browser does not suit the requirements for this application';
$__error_text = <<<EOF
<i>Supported browsers:</i><br />
&raquo; &nbsp;Microsoft Internet Explorer 7+<br />
&raquo; &nbsp;Mozilla Firefox 3+<br />
&raquo; &nbsp;Chrome 10+<br />
&raquo; &nbsp;Safari 4+<br />
&raquo; &nbsp;Opera 8+<br />
<br />
&raquo; &nbsp;JavaScript enabled<br />
&raquo; &nbsp;Support for XMLHTTPRequest<br />
<p><i>Your configuration:</i><br />
$user_agent</p>
EOF;
}
// authorization error // authorization error
else if ($ERROR_CODE == 401) { if ($ERROR_CODE == 401) {
$__error_title = mb_strtoupper($rcmail->gettext('errauthorizationfailed')); $__error_title = mb_strtoupper($rcmail->gettext('errauthorizationfailed'));
$__error_text = nl2br($rcmail->gettext('errunauthorizedexplain') . "\n" . $__error_text = nl2br($rcmail->gettext('errunauthorizedexplain') . "\n" .
$rcmail->gettext('errcontactserveradmin')); $rcmail->gettext('errcontactserveradmin'));
@ -72,6 +52,20 @@ else if ($ERROR_CODE == 404) {
$__error_text .= '<p><i>' . $rcmail->gettext('errfailedrequest') . ": $request_url</i></p>"; $__error_text .= '<p><i>' . $rcmail->gettext('errfailedrequest') . ": $request_url</i></p>";
} }
// browser is not compatible with this application
else if ($ERROR_CODE == 409) {
$user_agent = htmlentities($_SERVER['HTTP_USER_AGENT']);
$__error_title = 'Your browser does not suit the requirements for this application';
$__error_text = "Required features: <i>JavaScript enabled</i> and <i>XMLHTTPRequest support</i>."
. "<p><i>Your configuration:</i><br>$user_agent</p>";
}
// Gone, e.g. message cached but not in the storage
else if ($ERROR_CODE == 410) {
$__error_title = 'INTERNAL ERROR';
$__error_text = $rcmail->gettext('messageopenerror');
}
// invalid compose ID // invalid compose ID
else if ($ERROR_CODE == 450 && $_SERVER['REQUEST_METHOD'] == 'GET' && $rcmail->action == 'compose') { else if ($ERROR_CODE == 450 && $_SERVER['REQUEST_METHOD'] == 'GET' && $rcmail->action == 'compose') {
$url = $rcmail->url('compose'); $url = $rcmail->url('compose');
@ -107,10 +101,12 @@ if ($rcmail && $rcmail->plugins) {
'text' => $__error_text, 'text' => $__error_text,
)); ));
if (!empty($plugin['title'])) if (!empty($plugin['title'])) {
$__error_title = $plugin['title']; $__error_title = $plugin['title'];
if (!empty($plugin['text'])) }
if (!empty($plugin['text'])) {
$__error_text = $plugin['text']; $__error_text = $plugin['text'];
}
} }
$HTTP_ERR_CODE = $ERROR_CODE && $ERROR_CODE < 600 ? $ERROR_CODE : 500; $HTTP_ERR_CODE = $ERROR_CODE && $ERROR_CODE < 600 ? $ERROR_CODE : 500;
@ -131,6 +127,7 @@ EOF;
if ($rcmail->output && $rcmail->output->template_exists('error')) { if ($rcmail->output && $rcmail->output->template_exists('error')) {
$rcmail->output->reset(); $rcmail->output->reset();
$rcmail->output->set_env('error_task', 'error' . (empty($rcmail->user) || empty($rcmail->user->ID) ? '-login' : ''));
$rcmail->output->set_env('server_error', $ERROR_CODE); $rcmail->output->set_env('server_error', $ERROR_CODE);
$rcmail->output->set_env('comm_path', $rcmail->comm_path); $rcmail->output->set_env('comm_path', $rcmail->comm_path);
$rcmail->output->send('error'); $rcmail->output->send('error');

@ -56,6 +56,9 @@ function rcube_splitter(attrib)
// add the mouse event listeners // add the mouse event listeners
$(this.elm).mousedown(onDragStart); $(this.elm).mousedown(onDragStart);
// Update splitter position and elements with on window resize
$(window).resize(function(e) { if (e.target === window) me.resize(); });
if (bw.ie) if (bw.ie)
$(window).resize(onResize); $(window).resize(onResize);
@ -88,9 +91,12 @@ function rcube_splitter(attrib)
} }
} }
else { else {
this.p1.style.width = Math.floor(this.pos - this.p1pos.left - this.layer.width / 2) + 'px'; var max_width = $(window).width() - $(this.p1).offset().left - 150,
this.p2.style.left = Math.ceil(this.pos + this.layer.width / 2) + 'px'; pos = Math.min(this.pos, max_width);
this.layer.move(Math.round(this.pos - this.layer.width / 2 + 1), this.layer.y);
this.p1.style.width = Math.floor(pos - this.p1pos.left - this.layer.width / 2) + 'px';
this.p2.style.left = Math.ceil(pos + this.layer.width / 2) + 'px';
this.layer.move(Math.round(pos - this.layer.width / 2 + 1), this.layer.y);
if (bw.ie) { if (bw.ie) {
var new_width = parseInt(this.p2.parentNode.offsetWidth, 10) - parseInt(this.p2.style.left, 10) ; var new_width = parseInt(this.p2.parentNode.offsetWidth, 10) - parseInt(this.p2.style.left, 10) ;
this.p2.style.width = (new_width > 0 ? new_width : 0) + 'px'; this.p2.style.width = (new_width > 0 ? new_width : 0) + 'px';

@ -20,12 +20,8 @@
<div id="license"> <div id="license">
<roundcube:object name="aboutcontent" /> <roundcube:object name="aboutcontent" />
<h2 class="sysname">Roundcube Webmail <roundcube:object name="version" /></h2> <h2 class="sysname">Roundcube Webmail <roundcube:object name="version" /></h2>
<p class="copyright">Copyright &copy; 2005-2018, The Roundcube Dev Team</p> <p class="copyright"><roundcube:object name="copyright" /></p>
<p class="license">This program is free software; you can redistribute it and/or modify <p class="license"><roundcube:object name="license" /></p>
it under the terms of the <a href="http://www.gnu.org/licenses/gpl.html" target="_blank">GNU General Public License</a>
as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.<br/>
Some <a href="http://roundcube.net/license">exceptions</a> for skins &amp; plugins apply.
</p>
<p class="links"><roundcube:object name="supportlink" label="support" target="_blank" /></p> <p class="links"><roundcube:object name="supportlink" label="support" target="_blank" /></p>
</div> </div>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="9.14 141.8 573.65 573.65">
<style type="text/css">
.st0{fill-rule:evenodd;clip-rule:evenodd;fill:#404F54;}
.st1{fill-rule:evenodd;clip-rule:evenodd;fill:#E5E5E5;}
.st2{fill-rule:evenodd;clip-rule:evenodd;fill:#CCCCCC;}
.st3{fill-rule:evenodd;clip-rule:evenodd;fill:#37BEFF;}
</style>
<polygon class="st3" points="582.79,549.77 295.96,384.1 295.96,207.27 582.79,372.95 "/>
<polygon class="st0" points="9.14,549.77 295.96,384.1 295.96,207.27 9.14,372.95 "/>
<path class="st2" d="M295.96,141.8c109.56,0,198.41,88.85,198.41,198.41c0,109.56-88.85,198.41-198.41,198.41 c-109.56,0-198.41-88.85-198.41-198.41C97.55,230.65,186.4,141.8,295.96,141.8"/>
<path class="st1" d="M295.96,141.8c109.6,0,198.48,88.85,198.48,198.41c0,109.56-88.88,198.41-198.48,198.41 c-62.91-42.34-88.94-127.64-88.94-198.3S233.05,184.22,295.96,141.8"/>
<polygon class="st3" points="582.79,372.95 295.96,538.62 295.96,715.45 582.79,549.77 "/>
<polygon class="st0" points="9.14,372.95 295.96,538.62 295.96,715.45 9.14,549.77 "/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

@ -39,7 +39,7 @@
@color-layout-mobile-footer-background: @color-layout-header-background; @color-layout-mobile-footer-background: @color-layout-header-background;
// Task menu // Task menu
@color-taskmenu-background: #414e54; @color-taskmenu-background: #2f3a3f;
@color-taskmenu-button: #fff; @color-taskmenu-button: #fff;
@color-taskmenu-button-selected: @color-taskmenu-button; @color-taskmenu-button-selected: @color-taskmenu-button;
@color-taskmenu-button-action: @color-main; @color-taskmenu-button-action: @color-main;

@ -248,6 +248,7 @@ html.iframe {
// FIXME: we set background color here not in taskmenu.less, because // FIXME: we set background color here not in taskmenu.less, because
// otherwise background is partially white on Android/iOS // otherwise background is partially white on Android/iOS
background-color: @color-taskmenu-background; background-color: @color-taskmenu-background;
width: @layout-menu-width/2;
} }
} }
@ -258,10 +259,6 @@ html.iframe {
a.toolbar-menu-button { a.toolbar-menu-button {
display: none; display: none;
} }
body > #layout > .menu {
width: @layout-menu-width/2;
}
} }
@media screen and (min-width: (@screen-width-medium + 1px)) { @media screen and (min-width: (@screen-width-medium + 1px)) {

@ -135,13 +135,19 @@ a {
.task-login #layout > .content { .task-login #layout > .content {
text-align: center; text-align: center;
width: 100%; width: 100%;
background: url(../images/watermark.jpg) center -20px no-repeat; display: block;
background-size: auto 40%; }
.task-login #layout .content #logo {
display: inline-block;
position: relative;
top: 18vh;
max-height: 90px;
} }
#login-form { #login-form {
margin: 0 auto; margin: 0 auto;
top: 35vh; top: 22vh;
width: 95%; width: 95%;
max-width: 280px; max-width: 280px;
position: relative; position: relative;
@ -176,6 +182,12 @@ a {
} }
} }
body.task-error-login #layout {
& > .menu,
& > .content > .header {
display: none;
}
}
/*** Addressbook UI ***/ /*** Addressbook UI ***/

@ -210,8 +210,8 @@ html.touch {
} }
&:hover { &:hover {
background: darken(@color-btn-secondary-background, 8%); background: darken(@color-btn-secondary-background, 5%);
border-color: darken(@color-btn-secondary-background, 10%); border-color: darken(@color-btn-secondary-background, 7%);
} }
&.disabled, &.disabled,
@ -221,15 +221,16 @@ html.touch {
opacity: 1; opacity: 1;
} }
&:not(:disabled):not(.disabled):active { &:not(:disabled):not(.disabled) {
background: darken(@color-btn-secondary-background, 11%); &:active,
border-color: darken(@color-btn-secondary-background, 13%); &.active {
background: darken(@color-btn-secondary-background, 10%);
border-color: darken(@color-btn-secondary-background, 12%);
&:focus {
box-shadow: 0 0 0 .2rem fade(@color-btn-secondary-background, 53%); box-shadow: 0 0 0 .2rem fade(@color-btn-secondary-background, 53%);
} }
}
&:not(:disabled):not(.disabled).active {
background: darken(@color-btn-secondary-background, 20%);
border-color: darken(@color-btn-secondary-background, 22%);
} }
} }
@ -243,8 +244,8 @@ html.touch {
} }
&:hover { &:hover {
background: darken(@color-btn-primary-background, 8%); background: darken(@color-btn-primary-background, 5%);
border-color: darken(@color-btn-primary-background, 10%); border-color: darken(@color-btn-primary-background, 7%);
} }
&.disabled, &.disabled,
@ -254,15 +255,16 @@ html.touch {
opacity: 1; opacity: 1;
} }
&:not(:disabled):not(.disabled):active { &:not(:disabled):not(.disabled) {
background: darken(@color-btn-primary-background, 11%); &:active,
border-color: darken(@color-btn-primary-background, 13%); &.active {
background: darken(@color-btn-primary-background, 10%);
border-color: darken(@color-btn-primary-background, 12%);
&:focus {
box-shadow: 0 0 0 .2rem fade(@color-btn-primary-background, 53%); box-shadow: 0 0 0 .2rem fade(@color-btn-primary-background, 53%);
} }
}
&:not(:disabled):not(.disabled).active {
background: darken(@color-btn-primary-background, 20%);
border-color: darken(@color-btn-primary-background, 22%);
} }
} }
@ -276,8 +278,8 @@ html.touch {
} }
&:hover { &:hover {
background: darken(@color-btn-danger-background, 8%); background: darken(@color-btn-danger-background, 5%);
border-color: darken(@color-btn-danger-background, 10%); border-color: darken(@color-btn-danger-background, 7%);
} }
&.disabled, &.disabled,
@ -287,14 +289,15 @@ html.touch {
opacity: 1; opacity: 1;
} }
&:not(:disabled):not(.disabled):active { &:not(:disabled):not(.disabled) {
background: darken(@color-btn-danger-background, 11%); &:active,
border-color: darken(@color-btn-danger-background, 13%); &.active {
background: darken(@color-btn-danger-background, 10%);
border-color: darken(@color-btn-danger-background, 12%);
&:focus {
box-shadow: 0 0 0 .2rem fade(@color-btn-danger-background, 53%); box-shadow: 0 0 0 .2rem fade(@color-btn-danger-background, 53%);
} }
}
&:not(:disabled):not(.disabled).active {
background: darken(@color-btn-danger-background, 20%);
border-color: darken(@color-btn-danger-background, 22%);
} }
} }

@ -189,12 +189,6 @@ fieldset.image-attachment {
overflow-y: auto; overflow-y: auto;
} }
#layout > .content .watermark {
background: url(../images/watermark.jpg) center no-repeat;
width: 100%;
height: 100%;
}
.noselect { .noselect {
user-select: none; user-select: none;
-moz-user-select: none; -moz-user-select: none;

@ -463,6 +463,14 @@
color: @color-toolbarmenu-hover; color: @color-toolbarmenu-hover;
background-color: @color-toolbarmenu-hover-background; background-color: @color-toolbarmenu-hover-background;
} }
.popover-header {
display: none !important;
}
}
div.mce-menubtn.mce-opened {
z-index: 65530 !important; // BUG: https://github.com/tinymce/tinymce/issues/4542
} }
#mce-modal-block.mce-in { #mce-modal-block.mce-in {
@ -615,18 +623,88 @@
} }
.mce-menu { .mce-menu {
width: auto !important; width: @layout-mobile-menu-width !important;
right: 0; right: 0;
left: 0 !important; top: 0 !important;
margin: 0 1rem !important; left: auto !important;
height: 100% !important;
max-height: unset !important;
padding: 0 !important;
margin: 0 !important;
border-radius: 0;
border: 0 !important;
.popover-header {
display: block !important;
a {
font-size: 1.2rem;
line-height: @layout-touch-header-height;
&:before {
content: @fa-var-times;
}
}
}
.mce-container-body { .mce-container-body {
width: 100% !important; width: 100% !important;
} }
.mce-menu-item {
height: @layout-touch-menu-record-height;
line-height: @layout-touch-menu-record-height;
padding: 0 .5rem;
border-left: 0;
border-bottom: 1px solid @color-list-border;
margin: 0;
.mce-ico {
font-size: 150%;
padding: 0 .7rem 0 .25rem;
margin-top: -.2rem;
}
.mce-text {
font-size: 1.2rem;
.font-family;
line-height: @layout-touch-menu-record-height;
color: @color-font;
}
.mce-caret {
display: none;
} }
div.mce-menubtn.mce-opened { &.mce-menu-item-preview {
z-index: 65530 !important; &.mce-active {
border-left: none;
position: relative;
&:after {
.font-icon-class; // :extend() does not work here
content: @fa-var-check;
position: absolute;
right: .5rem;
top: 0;
color: @color-font;
}
}
}
&.mce-menu-item-expand {
position: relative;
&:after {
.font-icon-class; // :extend() does not work here
content: @fa-var-angle-right;
position: absolute;
right: .5rem;
top: 0;
color: @color-font;
}
}
}
} }
.mce-menu-item-sep, .mce-menu-item-sep,

@ -98,6 +98,22 @@ input.smart-upload {
} }
td.rowtargets { td.rowtargets {
.composite {
input, textarea, select, .multi-input, .input-group {
margin-bottom: .5rem;
}
.input-group {
input, textarea, select, .multi-input {
margin-bottom: 0;
}
}
br {
display: block;
}
}
.input-group { .input-group {
margin-bottom: .25rem; margin-bottom: .25rem;
@ -384,11 +400,6 @@ html.ms .propform {
display: flex; display: flex;
} }
} }
@media screen and (max-width: (@screen-width-bs-phone - 1px)) {
.row {
display: block;
}
}
} }
@ -575,6 +586,10 @@ html.ms .propform {
text-decoration: none; text-decoration: none;
padding: .375rem .5rem; padding: .375rem .5rem;
&.input-group-text {
min-width: 2.4rem;
}
&:before { &:before {
&:extend(.font-icon-class); &:extend(.font-icon-class);
margin: 0 !important; margin: 0 !important;
@ -617,16 +632,11 @@ html.ms .propform {
} }
} }
td.editfield { width: 99%; /* TODO */ }
@proplist-record-height: 2rem; @proplist-record-height: 2rem;
.proplist { .proplist {
margin-bottom: 0; margin-bottom: 0;
padding: 0;
.formcontent > &,
fieldset > & {
padding-left: 0;
}
li { li {
list-style-type: none; list-style-type: none;
@ -647,7 +657,7 @@ td.editfield { width: 99%; /* TODO */ }
} }
} }
label { label:not(.input-group-text) {
margin: 0; margin: 0;
line-height: @proplist-record-height; line-height: @proplist-record-height;
} }
@ -1166,6 +1176,10 @@ input.icon-checkbox {
cursor: pointer; cursor: pointer;
width: 1.5em; width: 1.5em;
html.touch & {
font-size: 1.5rem;
}
&:before { &:before {
&:extend(.font-icon-class); &:extend(.font-icon-class);
content: @fa-var-toggle-off; content: @fa-var-toggle-off;
@ -1216,8 +1230,28 @@ input.icon-checkbox {
} }
} }
html.touch input.icon-checkbox + label { .input-group-combo {
font-size: 1.5rem; select:first-of-type {
&.alone {
border-radius: .25rem !important;
}
&:not(.alone) {
flex: unset;
width: auto;
}
}
.input-group {
padding: 0 !important;
flex: 2;
}
select + select,
.input-group :first-child {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
} }

@ -27,6 +27,8 @@
overflow-x: hidden; overflow-x: hidden;
max-height: 400px; max-height: 400px;
border-radius: .3rem; border-radius: .3rem;
z-index: 240;
position: absolute;
.ui-state-active { .ui-state-active {
border: 0 !important; border: 0 !important;
@ -35,6 +37,7 @@
.ui-menu-item { .ui-menu-item {
white-space: nowrap; white-space: nowrap;
cursor: default;
} }
.ui-menu-item-wrapper { .ui-menu-item-wrapper {
@ -117,14 +120,16 @@
padding-right: 0; padding-right: 0;
padding-left: 0; padding-left: 0;
text-decoration: none; text-decoration: none;
color: @color-font;
&:focus { &:focus {
background-color: @color-input-border-focus-shadow; background-color: fade(@color-btn-primary-background, 50%);
} }
&.options { &.options {
order: -1; order: -1;
padding: .375rem .25rem; padding: .375rem .25rem;
margin-right: .3rem;
&:before { &:before {
// this icon is for mobile version // this icon is for mobile version

@ -453,9 +453,9 @@ ul.treelist {
li { li {
&.mailbox { &.mailbox {
&.unread { &.unread {
// TODO
& > a { & > a {
padding-right: 2.8em; padding-right: 2.8em;
font-weight: bold;
} }
} }
@ -483,10 +483,6 @@ ul.treelist {
} }
} }
&.selected .unreadcount {
// todo
}
&.recent > .unreadcount { &.recent > .unreadcount {
background: @color-list-recent-badge-background; background: @color-list-recent-badge-background;
color: @color-list-recent-badge; color: @color-list-recent-badge;

@ -210,6 +210,10 @@
& > i.icon:before { & > i.icon:before {
color: #fff; color: #fff;
} }
&.chat > i.icon:before {
content: @fa-var-comment;
}
} }
.alert-success { .alert-success {

@ -116,7 +116,7 @@
position: absolute; position: absolute;
bottom: 0; bottom: 0;
left: 0; left: 0;
width: @layout-menu-width; background-color: @color-taskmenu-background;
} }
.action-buttons { .action-buttons {
@ -154,6 +154,7 @@
width: @layout-menu-width; width: @layout-menu-width;
height: 1.75rem; height: 1.75rem;
line-height: 1.5; line-height: 1.5;
float: none; // fixed overflowing text in Edge
} }
} }
@ -168,14 +169,19 @@
.menu { .menu {
.popover-header { .popover-header {
height: @layout-header-height; height: @layout-header-height;
display: flex !important; line-height: @layout-header-height;
align-items: center;
justify-content: center;
border: 0; border: 0;
border-radius: 0; border-radius: 0;
text-align: center;
img { img {
max-height: @layout-header-height; max-height: @layout-header-height;
max-width: @layout-menu-width;
padding: .25rem;
@media screen and (min-width: (@screen-width-xs + 1px)) and (max-width: @screen-width-medium) {
max-width: @layout-menu-width/2;
}
} }
@media screen and (min-width: (@screen-width-xs + 1px)) { @media screen and (min-width: (@screen-width-xs + 1px)) {
@ -185,23 +191,15 @@
a { a {
display: none !important; display: none !important;
} }
img {
max-width: @layout-menu-width/2;
}
}
@media screen and (min-width: (@screen-width-medium + 1px)) {
img {
max-width: @layout-menu-width;
}
} }
html.layout-phone & { html.layout-phone & {
display: flex !important;
align-items: center;
justify-content: center;
padding: 0 .5rem; padding: 0 .5rem;
img { img {
max-height: @layout-touch-header-height;
max-width: @layout-mobile-menu-width - 50px; max-width: @layout-mobile-menu-width - 50px;
} }

@ -553,7 +553,7 @@
content: @fa-var-flag; content: @fa-var-flag;
} }
a.select.unread:before { a.select.unread:before {
content: @fa-var-star; content: @fa-var-circle;
} }
a.select.invert:before { a.select.invert:before {
.font-icon-regular(@fa-var-square); .font-icon-regular(@fa-var-square);

@ -6,13 +6,8 @@
<div class="content frame-content"> <div class="content frame-content">
<h2 class="sysname">Roundcube Webmail <roundcube:object name="version" /></h2> <h2 class="sysname">Roundcube Webmail <roundcube:object name="version" /></h2>
<p class="copyright">Copyright &copy; 2005-2018, The Roundcube Dev Team</p> <p class="copyright"><roundcube:object name="copyright" /></p>
<p class="license">This program is free software; <p class="license"><roundcube:object name="license" /></p>
you can redistribute it and/or modify it under the terms of the
<a href="http://www.gnu.org/licenses/gpl.html" target="_blank">GNU General Public License</a>
as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.<br/>
Some <a href="https://roundcube.net/license" target="_blank">exceptions</a> for skins &amp; plugins apply.
</p>
<div class="readtext"> <div class="readtext">
<h3><roundcube:label name="installedplugins" /></h3> <h3><roundcube:label name="installedplugins" /></h3>
<roundcube:object name="pluginlist" id="pluginlist" class="records-table" /> <roundcube:object name="pluginlist" id="pluginlist" class="records-table" />

@ -3,8 +3,9 @@
<roundcube:if condition="!env:framed && !env:extwin" /> <roundcube:if condition="!env:framed && !env:extwin" />
<roundcube:include file="includes/menu.html" /> <roundcube:include file="includes/menu.html" />
<div class="content selected" role="main"> <div class="content selected" role="main">
<div class="header"> <div class="header" role="toolbar">
<a class="button icon menu-button" href="#menu"><span class="inner"><roundcube:label name="menu" /></span></a> <a class="button icon menu-button" href="#menu"><span class="inner"><roundcube:label name="menu" /></span></a>
<span class="header-title"></span>
</div> </div>
<div class="frame-content scroller"> <div class="frame-content scroller">
<roundcube:else /> <roundcube:else />

@ -16,10 +16,10 @@
<div id="foldersearchmenu" class="hidden searchoptions scroller propform formcontainer" aria-labelledby="aria-label-search-menu" aria-controls="subscription-table"> <div id="foldersearchmenu" class="hidden searchoptions scroller propform formcontainer" aria-labelledby="aria-label-search-menu" aria-controls="subscription-table">
<h3 id="aria-label-search-menu" class="voice"><roundcube:label name="searchmod" /></h3> <h3 id="aria-label-search-menu" class="voice"><roundcube:label name="searchmod" /></h3>
<div class="formcontent"> <div class="formcontent">
<roundcube:object name="folderfilter" id="folderlist-filter" noheader="true" /> <roundcube:object name="folderfilter" id="folderlist-filter" noheader="true" noevent="true" />
</div> </div>
<div class="formbuttons"> <div class="formbuttons">
<button class="btn btn-primary icon search" href="#" onclick="return rcmail.command('search')"><roundcube:label name="search" /></button> <button class="btn btn-primary icon search" href="#" onclick="rcmail.folder_filter($('#folderlist-filter').val())"><roundcube:label name="search" /></button>
</div> </div>
</div> </div>
<div class="scroller" tabindex="-1"> <div class="scroller" tabindex="-1">

@ -6,6 +6,7 @@
<roundcube:add_label name="previous" /> <roundcube:add_label name="previous" />
<roundcube:add_label name="next" /> <roundcube:add_label name="next" />
<roundcube:add_label name="select" /> <roundcube:add_label name="select" />
<roundcube:add_label name="close" />
<roundcube:object name="doctype" value="html5" /> <roundcube:object name="doctype" value="html5" />
<roundcube:if condition="!env:framed || env:extwin" /> <roundcube:if condition="!env:framed || env:extwin" />
<html> <html>
@ -25,11 +26,8 @@
<link rel="stylesheet" href="/styles/styles.css"> <link rel="stylesheet" href="/styles/styles.css">
<roundcube:link rel="stylesheet" href="/styles/print.css" condition="env:action == 'print'" /> <roundcube:link rel="stylesheet" href="/styles/print.css" condition="env:action == 'print'" />
<roundcube:endif /> <roundcube:endif />
<roundcube:if condition="template:name == 'error'" />
<script src="program/js/jquery.min.js"></script>
<roundcube:endif />
</head> </head>
<body class="task-<roundcube:exp expression="env:task ?: 'error'">"> <body class="task-<roundcube:exp expression="env:error_task ?: env:task ?: 'error'">">
<roundcube:if condition="!env:framed || env:extwin" /> <roundcube:if condition="!env:framed || env:extwin" />
<div id="<roundcube:exp expression="env:action == 'print' ? 'print-' : ''">layout"> <div id="<roundcube:exp expression="env:action == 'print' ? 'print-' : ''">layout">
<roundcube:endif /> <roundcube:endif />

@ -1,7 +1,7 @@
<div class="menu"> <div class="menu">
<h2 id="aria-label-tasknav" class="voice"><roundcube:label name="arialabeltasknav" /></h2> <h2 id="aria-label-tasknav" class="voice"><roundcube:label name="arialabeltasknav" /></h2>
<div class="popover-header"> <div class="popover-header">
<roundcube:object name="logo" src="/images/logo.png" data-src-small="0" id="logo" alt="Logo" /> <roundcube:object name="logo" src="/images/logo.svg" data-src-small="0" id="logo" alt="Logo" />
<a class="button icon cancel"><span class="inner"><roundcube:label name="close" /></span></a> <a class="button icon cancel"><span class="inner"><roundcube:label name="close" /></span></a>
</div> </div>
<div id="taskmenu" role="navigation" aria-labelledby="aria-label-tasknav"> <div id="taskmenu" role="navigation" aria-labelledby="aria-label-tasknav">

@ -3,6 +3,7 @@
<h1 class="voice"><roundcube:object name="productname" /> <roundcube:label name="login" /></h1> <h1 class="voice"><roundcube:object name="productname" /> <roundcube:label name="login" /></h1>
<div class="content selected no-navbar" role="main"> <div class="content selected no-navbar" role="main">
<roundcube:object name="logo" src="/images/logo.svg" data-src-small="0" id="logo" alt="Logo" />
<roundcube:form id="login-form" name="login-form" method="post" class="propform"> <roundcube:form id="login-form" name="login-form" method="post" class="propform">
<roundcube:object name="loginform" form="login-form" size="40" submit=true /> <roundcube:object name="loginform" form="login-form" size="40" submit=true />
<div id="login-footer" role="contentinfo"> <div id="login-footer" role="contentinfo">

@ -61,13 +61,13 @@
<div class="input-group-prepend"> <div class="input-group-prepend">
<label for="searchfilter" class="input-group-text"><roundcube:label name="type" /></label> <label for="searchfilter" class="input-group-text"><roundcube:label name="type" /></label>
</div> </div>
<roundcube:object name="searchfilter" id="searchfilter" /> <roundcube:object name="searchfilter" id="searchfilter" noevent="true" />
</div> </div>
<div class="input-group"> <div class="input-group">
<div class="input-group-prepend"> <div class="input-group-prepend">
<label for="s_interval" class="input-group-text"><roundcube:label name="date" /></label> <label for="s_interval" class="input-group-text"><roundcube:label name="date" /></label>
</div> </div>
<roundcube:object name="searchinterval" id="s_interval" onchange="rcmail.set_searchinterval($(this).val())" /> <roundcube:object name="searchinterval" id="s_interval" />
</div> </div>
<div class="input-group"> <div class="input-group">
<div class="input-group-prepend"> <div class="input-group-prepend">

@ -1,16 +0,0 @@
<roundcube:include file="includes/layout.html" />
<roundcube:include file="includes/menu.html" condition="!env:framed && !env:extwin" />
<h1 class="voice"><roundcube:label name="messageopenerror" /></h1>
<div class="content selected">
<roundcube:if condition="env:action == 'show'" />
<h2 id="aria-label-toolbar" class="voice"><roundcube:label name="arialabeltoolbar" /></h2>
<div class="header" role="toolbar" aria-labelledby="aria-label-toolbar">
<a class="button icon back-list-button" href="#back"><span class="inner"><roundcube:label name="back" /></span></a>
</div>
<roundcube:endif />
<div class="watermark"></div>
</div>
<roundcube:include file="includes/footer.html" />

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

@ -260,6 +260,8 @@ function rcube_elastic_ui()
$(this).addClass('button ' + name); $(this).addClass('button ' + name);
$('.button-inner', this).addClass('inner'); $('.button-inner', this).addClass('inner');
} }
$(this).on('mouseover', function() { rcube_webmail.long_subject_title(this, 0, $('span.inner', this)); });
}); });
// Some plugins use 'listbubtton' class, we'll replace it with 'button' // Some plugins use 'listbubtton' class, we'll replace it with 'button'
@ -512,8 +514,15 @@ function rcube_elastic_ui()
.on('click', function() { if ($(this).is('.active')) table.toggleClass('withselection'); }) .on('click', function() { if ($(this).is('.active')) table.toggleClass('withselection'); })
.append($('<span class="inner">').text(rcmail.gettext('select'))); .append($('<span class="inner">').text(rcmail.gettext('select')));
if (toolbar.is('.toolbar')) { if (toolbar.is('.toolbar') || toolbar.is('.toolbarmenu')) {
button.prependTo(toolbar).wrap('<li role="menuitem">'); button.prependTo(toolbar).wrap('<li role="menuitem">');
// Add a button to the content toolbar menu too
if (layout.content) {
var button2 = create_cloned_button(button, true, 'hidden-big hidden-large');
$('<li role="menuitem">').append(button2).appendTo('#toolbar-menu');
button = button.add(button2);
}
} }
else { else {
button.appendTo(toolbar).addClass('icon'); button.appendTo(toolbar).addClass('icon');
@ -657,7 +666,7 @@ function rcube_elastic_ui()
$('a.button.attach, a.button.responses')[e.active ? 'addClass' : 'removeClass']('disabled'); $('a.button.attach, a.button.responses')[e.active ? 'addClass' : 'removeClass']('disabled');
}); });
$('.sidebar > .footer > a.button').click(function() { $('.sidebar > .footer:not(.pagenav) > a.button').click(function() {
if ($(this).is('.disabled')) { if ($(this).is('.disabled')) {
rcmail.display_message(rcmail.gettext('nocontactselected'), 'warning'); rcmail.display_message(rcmail.gettext('nocontactselected'), 'warning');
} }
@ -932,6 +941,19 @@ function rcube_elastic_ui()
$('input[type=checkbox]', context).each(function() { pretty_checkbox(this); }); $('input[type=checkbox]', context).each(function() { pretty_checkbox(this); });
} }
// Input-group combo is an element with a select field on the left
// and input(s) on right, and where the whole right side can be hidden
// depending on the select position. This code fixes border radius on select
$('.input-group-combo > select:first', context).on('change', function() {
var select = $(this),
fn = function() {
select[select.next().is(':visible') ? 'removeClass' : 'addClass']('alone');
};
setTimeout(fn, 50);
setTimeout(fn, 2000); // for devel mode
}).trigger('change');
// Make message-objects alerts pretty (the same as UI alerts) // Make message-objects alerts pretty (the same as UI alerts)
$('#message-objects', context).children(':not(.ui.alert)').each(function() { $('#message-objects', context).children(':not(.ui.alert)').each(function() {
// message objects with notice class are really warnings // message objects with notice class are really warnings
@ -974,11 +996,12 @@ function rcube_elastic_ui()
}; };
/** /**
* Detects if the element is TinyMCE dialog window * Detects if the element is TinyMCE dialog/menu
* and adds Elastic styling to it * and adds Elastic styling to it
*/ */
function tinymce_style(elem) function tinymce_style(elem)
{ {
// TinyMCE dialog widnows
if ($(elem).is('.mce-window')) { if ($(elem).is('.mce-window')) {
var body = $(elem).find('.mce-window-body'), var body = $(elem).find('.mce-window-body'),
foot = $(elem).find('.mce-foot > .mce-container-body'); foot = $(elem).find('.mce-foot > .mce-container-body');
@ -1024,6 +1047,32 @@ function rcube_elastic_ui()
} }
} }
} }
// TinyMCE menus on mobile
else if ($(elem).is('.mce-menu')) {
$(elem).prepend(
$('<h3 class="popover-header">').append(
$('<a class="button icon "' + 'cancel' + '">')
.text(rcmail.gettext('close'))
.on('click', function() { $(document.body).click(); })));
if (window.MutationObserver) {
var callback = function() {
if (mode != 'phone') {
return;
}
if (!$('.mce-menu:visible').length) {
$('div.mce-overlay').click();
}
else if (!$('div.mce-overlay').length) {
$('<div>').attr('class', 'popover-overlay mce-overlay')
.appendTo('body')
.click(function() { $(this).remove(); });
}
};
(new MutationObserver(callback)).observe(elem, {attributes: true});
}
}
}; };
/** /**
@ -1054,7 +1103,9 @@ function rcube_elastic_ui()
// display or reset the content frame // display or reset the content frame
var common_content_handler = function(e, href, show, title) var common_content_handler = function(e, href, show, title)
{ {
if (is_mobile()) {
content_frame_navigation(href, e); content_frame_navigation(href, e);
}
if (show && !layout.content.is(':visible')) { if (show && !layout.content.is(':visible')) {
env.last_selected = layout.content[0]; env.last_selected = layout.content[0];
@ -1675,9 +1726,13 @@ function rcube_elastic_ui()
warning: 'alert-warning', warning: 'alert-warning',
error: 'alert-danger', error: 'alert-danger',
loading: 'alert-info loading', loading: 'alert-info loading',
uploading: 'alert-info loading',
vcardattachment: 'alert-info' /* vcard_attachments plugin */ vcardattachment: 'alert-info' /* vcard_attachments plugin */
}; };
// Type can be e.g. 'notice chat'
type = type.split(' ')[0];
if (wrap && addicon && !$(object).is('.aligned-buttons')) { if (wrap && addicon && !$(object).is('.aligned-buttons')) {
// we need the content to be non-text node for best alignment // we need the content to be non-text node for best alignment
tmp = $(object).html(); tmp = $(object).html();
@ -1741,6 +1796,10 @@ function rcube_elastic_ui()
return true; return true;
} }
if (rcmail.task == 'mail' && $('#s_interval').val()) {
return true;
}
if (rcmail.gui_objects.search_filter && $(rcmail.gui_objects.search_filter).val() != 'ALL') { if (rcmail.gui_objects.search_filter && $(rcmail.gui_objects.search_filter).val() != 'ALL') {
return true; return true;
} }
@ -1775,19 +1834,22 @@ function rcube_elastic_ui()
$('button.search', options).off('click.search').on('click.search', function() { $('button.search', options).off('click.search').on('click.search', function() {
options_button.trigger('click'); options_button.trigger('click');
update_func();
}); });
} }
}); });
input.on('input change', update_func) input.on('input change', update_func)
.on('focus', function() { input.attr('placeholder', ''); }) .on('focus blur', function(e) { input.attr('placeholder', e.type == 'blur' ? placeholder : ''); });
.on('blur', function() { input.attr('placeholder', placeholder); });
// Search reset action // Search reset action
$('a.reset', bar).on('click', function(e) { $('a.reset', bar).on('click', function(e) {
// for treelist widget's search setting val and keyup.treelist is needed // for treelist widget's search setting val and keyup.treelist is needed
// in normal search form reset-search command will do the trick // in normal search form reset-search command will do the trick
input.val('').change().trigger('keyup.treelist', {keyCode: 27}); input.val('').change().trigger('keyup.treelist', {keyCode: 27});
if ($(bar).is('.open')) {
options_button.click();
}
// Reset filter // Reset filter
if (rcmail.gui_objects.search_filter) { if (rcmail.gui_objects.search_filter) {
@ -1796,21 +1858,20 @@ function rcube_elastic_ui()
if (rcmail.gui_objects.foldersfilter) { if (rcmail.gui_objects.foldersfilter) {
$(rcmail.gui_objects.foldersfilter).val('---').change(); $(rcmail.gui_objects.foldersfilter).val('---').change();
rcmail.folder_filter('---');
} }
update_func(); update_func();
}); });
rcmail.addEventListener('init', function() { rcmail.addEventListener('init', function() { update_func(); })
update_func(); .addEventListener('beforelist', function() {
if ($(bar).is('.open')) {
if (rcmail.gui_objects.search_filter) { options_button.click(); // close options form on 'list' request
$(rcmail.gui_objects.search_filter).on('change', update_func);
}
if (rcmail.gui_objects.foldersfilter) {
$(rcmail.gui_objects.foldersfilter).on('change', update_func);
} }
})
.addEventListener('responsebeforesearch', function() {
update_func();
}); });
}; };
@ -2335,7 +2396,7 @@ function rcube_elastic_ui()
{ {
var content = $('#listoptions-menu'), var content = $('#listoptions-menu'),
width = content.width() + 25, width = content.width() + 25,
dialog = content.clone(); dialog = content.clone(true);
// set form values // set form values
$('select[name="sort_col"]', dialog).val(rcmail.env.sort_col || ''); $('select[name="sort_col"]', dialog).val(rcmail.env.sort_col || '');
@ -2436,15 +2497,17 @@ function rcube_elastic_ui()
{ {
var n, all, var n, all,
list = $('input[name="s_mods[]"]', obj), list = $('input[name="s_mods[]"]', obj),
scope_select = $('select[name=s_scope]', obj), scope_select = $('#s_scope', obj),
mbox = rcmail.env.mailbox, mbox = rcmail.env.mailbox,
mods = rcmail.env.search_mods, mods = rcmail.env.search_mods,
scope = rcmail.env.search_scope || 'base'; scope = rcmail.env.search_scope || 'base';
if (!$(obj).data('initialized')) { if (!$(obj).data('initialized')) {
list.on('click', function() { set_searchmod(this, obj); });
scope_select.on('click', function() { rcmail.set_searchscope($(this).val()); });
$(obj).data('initialized', true); $(obj).data('initialized', true);
if (list.length) {
list.on('change', function() { set_searchmod(obj, this); });
rcmail.addEventListener('beforesearch', function() { set_searchmod(obj); });
}
} }
if (rcmail.env.search_mods) { if (rcmail.env.search_mods) {
@ -2476,12 +2539,13 @@ function rcube_elastic_ui()
} }
}; };
function set_searchmod(elem, menu) function set_searchmod(menu, elem)
{ {
var all, m, task = rcmail.env.task, var all, m, task = rcmail.env.task,
mods = rcmail.env.search_mods, mods = rcmail.env.search_mods,
mbox = rcmail.env.mailbox, mbox = rcmail.env.mailbox,
scope = $('select[name=s_scope]', menu).val(); scope = $('#s_scope', menu).val(),
interval = $('#s_interval', menu).val();
if (scope == 'all') { if (scope == 'all') {
mbox = '*'; mbox = '*';
@ -2497,12 +2561,19 @@ function rcube_elastic_ui()
} }
m = mods[mbox]; m = mods[mbox];
all = 'text'; all = 'text';
rcmail.env.search_scope = scope;
rcmail.env.search_interval = interval;
} }
else { //addressbook else { //addressbook
m = mods; m = mods;
all = '*'; all = '*';
} }
if (!elem) {
return;
}
if (!elem.checked) { if (!elem.checked) {
delete(m[elem.value]); delete(m[elem.value]);
} }

@ -1,11 +1,12 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<meta charset="utf-8"> <meta charset="UTF-8">
<title></title> <title></title>
<style type="text/css"> <style type="text/css">
html, body { height: 100%; overflow: hidden; } html, body { height: 100%; overflow: hidden; }
body { background: url(images/watermark.jpg) center no-repeat #fff; } body { background: url(images/logo.svg) center no-repeat #fff; background-size: 30%; }
body:before { content: ""; position: absolute; top: 0; bottom: 0; left: 0; right: 0; background: rgba(255, 255, 255, .85); }
</style> </style>
</head> </head>
<body></body> <body></body>

@ -8,15 +8,9 @@
<div class="readtext"> <div class="readtext">
<roundcube:object name="aboutcontent" /> <roundcube:object name="aboutcontent" />
<h2 class="sysname">Roundcube Webmail <roundcube:object name="version" /></h2> <h2 class="sysname">Roundcube Webmail <roundcube:object name="version" /></h2>
<p class="copyright">Copyright &copy; 2005-2018, The Roundcube Dev Team</p> <p class="copyright"><roundcube:object name="copyright" /></p>
<p class="license">This program is free software; you can redistribute it and/or modify <p class="license"><roundcube:object name="license" /></p>
it under the terms of the <a href="http://www.gnu.org/licenses/gpl.html" target="_blank">GNU General Public License</a>
as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.<br/>
Some <a href="https://roundcube.net/license" target="_blank">exceptions</a> for skins &amp; plugins apply.
</p>
</div> </div>
<div class="readtext"> <div class="readtext">

@ -4,8 +4,8 @@
<title><roundcube:object name="pagetitle" /></title> <title><roundcube:object name="pagetitle" /></title>
<roundcube:include file="/includes/links.html" /> <roundcube:include file="/includes/links.html" />
</head> </head>
<roundcube:if condition="!env:framed" /> <roundcube:if condition="!env:framed && env:error_task != 'error-login'" />
<roundcube:if condition="env:extwin" /><body class="error extwin"><roundcube:else /><body class="error"><roundcube:endif /> <body class="error<roundcube:exp expression="env:extwin ? ' extwin' : ''" />">
<roundcube:include file="/includes/header.html" /> <roundcube:include file="/includes/header.html" />
<roundcube:else /> <roundcube:else />
<body class="error iframe"> <body class="error iframe">

@ -75,6 +75,7 @@ class MailFunc extends PHPUnit_Framework_TestCase
$this->assertNotRegExp('/src="skins/', $washed, "Remove local references"); $this->assertNotRegExp('/src="skins/', $washed, "Remove local references");
$this->assertNotRegExp('/\son[a-z]+/', $washed, "Remove on* attributes"); $this->assertNotRegExp('/\son[a-z]+/', $washed, "Remove on* attributes");
$this->assertNotContains('onload', $washed, "Handle invalid style");
$html = rcmail_html4inline($washed, array('container_id' => 'foo')); $html = rcmail_html4inline($washed, array('container_id' => 'foo'));
$this->assertNotRegExp('/onclick="return rcmail.command(\'compose\',\'xss@somehost.net\',this)"/', $html, "Clean mailto links"); $this->assertNotRegExp('/onclick="return rcmail.command(\'compose\',\'xss@somehost.net\',this)"/', $html, "Clean mailto links");

@ -18,5 +18,7 @@ Have a nice Christmas time.<br />
Thomas Thomas
</p> </p>
<html><svg><style><//><body onload=alert(1)>
</body> </body>
</html> </html>

Loading…
Cancel
Save