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": {

@ -442,15 +442,14 @@ class enigma extends rcube_plugin
// find private keys for this identity // find private keys for this identity
if ($p['record']['email']) { if ($p['record']['email']) {
$listing = array(); $listing = array();
$engine = $this->load_engine(); $engine = $this->load_engine();
$keys = (array)$engine->list_keys($p['record']['email']); $keys = (array)$engine->list_keys($p['record']['email']);
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, 'class' => 'button',
html::a( 'href' => $this->rc->url(array('action' => 'plugin.enigmakeys')),
array( 'target' => '_parent',
'class' => 'button',
'href' => $this->rc->url(array('action' => 'plugin.enigmakeys')),
'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
@ -355,13 +356,20 @@
Set the RPC-Path in $config['password_plesk_rpc_path']. Normally this is: enterprise/control/agent.php. Set the RPC-Path in $config['password_plesk_rpc_path']. Normally this is: enterprise/control/agent.php.
2.21. Kpasswd 2.21. Kpasswd
----------------------------------- -----------------------------------
Driver to change the password in Kerberos environments via the 'kpasswd' command. Driver to change the password in Kerberos environments via the 'kpasswd' command.
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
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";
}
if (!empty($this->scripts['foot'])) { $page_footer .= array_reduce((array) $this->script_files['foot'], $merge_script_files);
$page_footer .= html::script(array(), $this->scripts['foot']); $page_footer .= $this->footer . "\n";
} $page_footer .= array_reduce((array) $this->scripts['foot'], $merge_scripts);
// 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();
@ -1590,14 +1598,15 @@ class rcube
/** /**
* Send the given message using the configured method. * Send the given message using the configured method.
* *
* @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),
* @param array $error SMTP error array (reference) * or an array of recipients, each RFC822 valid
* @param string $body_file Location of file with saved message body (reference), * @param array $error SMTP error array (reference)
* used when delay_file_io is enabled * @param string $body_file Location of file with saved message body (reference),
* @param array $options SMTP options (e.g. DSN request) * used when delay_file_io is enabled
* @param bool $disconnect Close SMTP connection ASAP * @param array $options SMTP options (e.g. DSN request)
* @param bool $disconnect Close SMTP connection ASAP
* *
* @return boolean Send status. * @return boolean Send status.
*/ */

@ -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';
} }
$attrib['onchange'] = rcmail_output::JS_OBJECT_NAME.'.filter_mailbox(this.value)'; if (!rcube_utils::get_boolean($attrib['noevent'])) {
$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,18 +1672,22 @@ function rcmail_message_error()
{ {
global $RCMAIL; global $RCMAIL;
// Set env variables for messageerror.html template // ... display message error page
if ($RCMAIL->action == 'show') { if ($RCMAIL->output->template_exists('messageerror')) {
$mbox_name = $RCMAIL->storage->get_folder(); // Set env variables for messageerror.html template
if ($RCMAIL->action == 'show') {
$mbox_name = $RCMAIL->storage->get_folder();
$RCMAIL->output->set_env('mailbox', $mbox_name); $RCMAIL->output->set_env('mailbox', $mbox_name);
$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'); $RCMAIL->output->send('messageerror');
// ... display message error page }
$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';
} }
$attrib['onchange'] = rcmail_output::JS_OBJECT_NAME . '.folder_filter(this.value)'; if (!rcube_utils::get_boolean($attrib['noevent'])) {
$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');
@ -102,15 +96,17 @@ else {
// inform plugins // inform plugins
if ($rcmail && $rcmail->plugins) { if ($rcmail && $rcmail->plugins) {
$plugin = $rcmail->plugins->exec_hook('error_page', array( $plugin = $rcmail->plugins->exec_hook('error_page', array(
'code' => $ERROR_CODE, 'code' => $ERROR_CODE,
'title' => $__error_title, 'title' => $__error_title,
'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 {
box-shadow: 0 0 0 .2rem fade(@color-btn-secondary-background, 53%); background: darken(@color-btn-secondary-background, 10%);
} border-color: darken(@color-btn-secondary-background, 12%);
&:not(:disabled):not(.disabled).active { &:focus {
background: darken(@color-btn-secondary-background, 20%); box-shadow: 0 0 0 .2rem fade(@color-btn-secondary-background, 53%);
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 {
box-shadow: 0 0 0 .2rem fade(@color-btn-primary-background, 53%); background: darken(@color-btn-primary-background, 10%);
} border-color: darken(@color-btn-primary-background, 12%);
&:not(:disabled):not(.disabled).active { &:focus {
background: darken(@color-btn-primary-background, 20%); box-shadow: 0 0 0 .2rem fade(@color-btn-primary-background, 53%);
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 {
box-shadow: 0 0 0 .2rem fade(@color-btn-danger-background, 53%); background: darken(@color-btn-danger-background, 10%);
} border-color: darken(@color-btn-danger-background, 12%);
&:not(:disabled):not(.disabled).active { &:focus {
background: darken(@color-btn-danger-background, 20%); box-shadow: 0 0 0 .2rem fade(@color-btn-danger-background, 53%);
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;
} }
}
div.mce-menubtn.mce-opened { .mce-menu-item {
z-index: 65530 !important; 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;
}
&.mce-menu-item-preview {
&.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)
{ {
content_frame_navigation(href, e); if (is_mobile()) {
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,22 +1858,21 @@ 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); }
} })
.addEventListener('responsebeforesearch', function() {
if (rcmail.gui_objects.foldersfilter) { update_func();
$(rcmail.gui_objects.foldersfilter).on('change', 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