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>
Options -Indexes
</ifModule>
</IfModule>
<IfModule mod_headers.c>
# Disable page indexing

@ -1,6 +1,27 @@
CHANGELOG Roundcube Webmail
===========================
- Removed referer_check option (#6440)
- Update to TinyMCE 4.8.2
- Plugin API: Added 'raise_error' hook (#6199)
- Managesieve: Added support for 'editheader' extension - RFC5293 (#5954)
- Password: Added 'modoboa' driver (#6361)
- Password: Fix bug where password_dovecotpw_with_method setting could be ignored (#6436)
- Password: Fix bug where new users could skip forced password change (#6434)
- Elastic: Improved UX of search dialogs (#6416)
- Elastic: Fix unwanted thread expanding when selecting a collapsed thread in non-mobile mode (#6445)
- Log errors caused by low pcre.backtrack_limit when sending a mail message (#6433)
- Fix style/navigation on error page depending on authentication state (#6362)
- Fix so invalid smtp_helo_host is never used, fallback to localhost (#6408)
- Fix custom logo size in Elastic (#6424)
- Fix listing the same attachment multiple times on forwarded messages
- Fix compatibility with MySQL 8 - error on 'system' table use
- Managesieve: Fix bug where show_real_foldernames setting wasn't respected (#6422)
- New_user_identity: Fix %fu/%u vars substitution in user specific LDAP params (#6419)
- Fix support for "allow-from <uri>" in "x_frame_options" config option (#6449)
RELEASE 1.4-beta
----------------
- Added new skin with mobile support - the Elastic
- Support Redis cache
- Email Resent (Bounce) feature (#4985)
@ -90,6 +111,15 @@ CHANGELOG Roundcube Webmail
- Fix touch event issue on messages list in IE/Edge (#5781)
- Fix so links over images are not removed in plain text signatures converted from HTML (#4473)
- Fix various issues when downloading files with names containing non-ascii chars, use RFC 2231 (#5772)
- Fix PHP warnings on dummy QUOTA responses in Courier-IMAP 4.17.1 (#6374)
- Fix so fallback from BINARY to BODY FETCH is used also on [PARSE] errors in dovecot 2.3 (#6383)
- Enigma: Fix deleting keys with authentication subkeys (#6381)
- Fix invalid regular expressions that throw warnings on PHP 7.3 (#6398)
- Fix so Classic skin splitter does not escape out of window (#6397)
- Fix XSS issue in handling invalid style tag content (#6410)
RELEASE 1.3.7
-------------
- Fix PHP Warning: Use of undefined constant IDNA_DEFAULT on systems without php-intl (#6244)
- Fix bug where some parts of quota information could have been ignored (#6280)
- Fix bug where some escape sequences in html styles could bypass security checks

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

@ -25,8 +25,8 @@ which are pure XHTML and CSS 2.
The code is mainly written in PHP and is designed to run on a webserver.
It includes other open-source classes/libraries from [PEAR][pear],
an IMAP library derived from [IlohaMail][iloha] the [TinyMCE][tinymce] rich
text editor, [Googiespell][googiespell] library for spell checking or
the [WASHTML][washtml] sanitizer by Frederic Motte.
text editor, [Googiespell][googiespell] (archive) library for spell checking or
the [HTML5-PHP][html5-php] sanitizer by Masterminds.
The current default skin 'Larry' was kindly created by FLINT / Büro für
Gestaltung, Berne, Switzerland.
@ -41,6 +41,19 @@ If you're updating an older version of Roundcube please follow the steps
described in the UPGRADING file.
BROWSER SUPPORT
---------------
Roundcube uses jQuery 3.x for its client and therefore inherits the browser
support from there. This currently includes:
- Chrome: (Current - 1) and Current
- Edge: (Current - 1) and Current
- Firefox: (Current - 1) and Current, ESR
- Internet Explorer: 9+
- Safari: (Current - 1) and Current
- Opera: Current
LICENSE
-------
This program is free software: you can redistribute it and/or modify
@ -89,13 +102,12 @@ You're always welcome to send a message to the project admin:
hello(at)roundcube(dot)net
[pear]: http://pear.php.net
[iloha]: http://sourceforge.net/projects/ilohamail/
[tinymce]: http://www.tinymce.com/
[googiespell]: http://orangoo.com/labs/GoogieSpell/
[washtml]: http://www.ubixis.com/washtml/
[kmgerich]: http://kmgerich.com/
[gpl]: http://www.gnu.org/licenses/
[pear]: https://pear.php.net/
[iloha]: https://sourceforge.net/projects/ilohamail/
[tinymce]: https://www.tiny.cloud/
[googiespell]: https://web.archive.org/web/20170314162746/orangoo.com/labs/GoogieSpell/
[html5-php]: https://github.com/Masterminds/html5-php
[gpl]: https://www.gnu.org/licenses/
[license]: https://roundcube.net/license
[contrib]: https://roundcube.net/contribute
[support]: https://roundcube.net/support

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

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

@ -15,7 +15,7 @@
"pear/auth_sasl": "~1.1.0",
"pear/net_idna2": "~0.2.0",
"pear/mail_mime": "~1.10.0",
"pear/net_smtp": "~1.7.1",
"pear/net_smtp": "~1.8.0",
"pear/crypt_gpg": "~1.6.3",
"pear/net_sieve": "~1.4.3",
"roundcube/plugin-installer": "~0.1.6",

@ -1,11 +1,15 @@
<?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 |
| 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 |
| any later version with exceptions for skins & plugins. |
@ -493,9 +497,6 @@ $config['trusted_host_patterns'] = array();
// check client IP in session authorization
$config['ip_check'] = false;
// check referer of incoming requests
$config['referer_check'] = false;
// X-Frame-Options HTTP header value sent to prevent from Clickjacking.
// Possible values: sameorigin|deny|allow-from <uri>.
// Set to false in order to disable sending the header.

@ -36,10 +36,10 @@
},
{
"lib": "tinymce",
"version": "4.7.13",
"version": "4.8.2",
"url": "http://download.ephox.com/tinymce/community/tinymce_$v.zip",
"dest": "program/js",
"sha1": "7f988f3899aebee6d49bd55e981331da07eee6c5",
"sha1": "d7fced05acdeeb78299585ea9909b0de2b3d759d",
"license": "LGPL",
"copyright": "Copyright (c) 1999-2015 Ephox Corp. All rights reserved",
"rm": "program/js/tinymce",
@ -56,7 +56,7 @@
},
{
"lib": "tinymce-langs",
"version": "4.7.13",
"version": "4.8.2",
"url": "https://tinymce-services.azurewebsites.net/1/i18n/download?langs=ar,hy,az,eu,be,bs,bg_BG,ca,zh_CN,zh_TW,hr,cs,cs_CZ,da,nl,en_CA,en_GB,eo,et,fo,fi,fr_FR,fr_CH,gd,gl,ka_GE,de,de_AT,el,he_IL,hi_IN,hu_HU,is_IS,id,ga,it,ja,kab,km_KH,ko_KR,ku,ku_IQ,lv,lt,lb,mk_MK,ml_IN,nb_NO,oc,fa,fa_IR,pl,pt_BR,pt_PT,ro,ru,sk,sl_SI,es,es_MX,sv_SE,tg,ta,ta_IN,tt,th_TH,tr,tr_TR,ug,uk,uk_UA,vi,vi_VN,cy&extension=.zip",
"dest": "program/js/tinymce"
},
@ -88,10 +88,10 @@
{
"lib": "bootstrap",
"name": "Bootstrap",
"version": "4.1.0",
"version": "4.1.2",
"url": "https://github.com/twbs/bootstrap/releases/download/v$v/bootstrap-$v-dist.zip",
"dest": "skins/elastic/deps",
"sha1": "90abd51cf27a3dc4823eed6c1de620725f068b6c",
"sha1": "d43982739f8f2bb05ef2a2fa55c358ce7e30a72b",
"license": "MIT",
"flat": true,
"map": {

@ -442,15 +442,14 @@ class enigma extends rcube_plugin
// find private keys for this identity
if ($p['record']['email']) {
$listing = array();
$engine = $this->load_engine();
$keys = (array)$engine->list_keys($p['record']['email']);
$engine = $this->load_engine();
$keys = (array)$engine->list_keys($p['record']['email']);
foreach ($keys as $key) {
if ($key->get_type() === enigma_key::TYPE_KEYPAIR) {
$listing[] = html::tag('li', null,
html::tag('span', 'identity', html::quote($key->name)) .
' ' .
html::tag('strong', 'uid', html::quote($key->id))
. ' ' . html::tag('span', 'identity', html::quote($key->name))
);
}
}
@ -464,16 +463,12 @@ class enigma extends rcube_plugin
}
// add button linking to enigma key management
$content .= html::p(
null,
html::a(
array(
'class' => 'button',
'href' => $this->rc->url(array('action' => 'plugin.enigmakeys')),
'target' => '_parent',
),
$this->gettext('managekeys'))
$button_attr = array(
'class' => 'button',
'href' => $this->rc->url(array('action' => 'plugin.enigmakeys')),
'target' => '_parent',
);
$content .= html::p(null, html::a($button_attr, $this->gettext('managekeys')));
// rename class to avoid Mailvelope key management to kick in
$p['form']['encryption']['attrs'] = array('class' => 'enigma-identity-encryption');

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

@ -1119,6 +1119,11 @@ class enigma_ui
*/
function message_ready($p)
{
// The message might have been already encrypted by Mailvelope
if (strpos($p['message']->getParam('ctype'), 'multipart/encrypted') === 0) {
return $p;
}
$savedraft = !empty($_POST['_draft']) && empty($_GET['_saveonly']);
$sign_enable = (bool) rcube_utils::get_input_value('_enigma_sign', rcube_utils::INPUT_POST);
$encrypt_enable = (bool) rcube_utils::get_input_value('_enigma_encrypt', rcube_utils::INPUT_POST);

@ -14,7 +14,8 @@
<roundcube:object name="searchform" id="keysearch" wrapper="searchbar toolbar"
label="keysearchform" buttontitle="findkeys" ariatag="h2" />
<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 class="pagenav footer toolbar small" role="toolbar">
<roundcube:button command="firstpage" type="link"

@ -114,6 +114,9 @@ class krb_authentication extends rcube_plugin
function managesieve_connect($args)
{
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();
$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]
-----------------------------------------------------------
- Added GSSAPI support (#5779)

@ -313,14 +313,10 @@ class rcube_sieve
if ($this->script) {
$supported = $this->script->get_extensions();
foreach ($ext as $idx => $ext_name) {
if (!in_array($ext_name, $supported)) {
unset($ext[$idx]);
}
}
$ext = array_values(array_intersect($ext, $supported));
}
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);
$notifyfrom = rcube_utils::get_input_value('_action_notifyfrom', 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
foreach ($sizeitems as $item)
@ -1091,6 +1100,47 @@ class rcube_sieve_engine
$this->form['actions'][$i]['target'] = $_target;
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':
$reason = $this->strip_value($reasons[$idx]);
$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),
$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);
$target = '';
@ -1796,7 +1814,7 @@ class rcube_sieve_engine
$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,
$rule['test'] != 'size' && $rule['test'] != 'exists' && $rule['test'] != 'duplicate',
$this->error_class($id, 'test', 'target', 'rule_target')) . "\n";
@ -1854,18 +1872,10 @@ class rcube_sieve_engine
$mout .= '</div>';
// Advanced modifiers (comparators)
$select_comp = new html_select(array('name' => "_rule_comp[]", 'id' => 'rule_comp_op'.$id));
$select_comp->add(rcube::Q($this->plugin->gettext('default')), '');
$select_comp->add(rcube::Q($this->plugin->gettext('octet')), 'i;octet');
$select_comp->add(rcube::Q($this->plugin->gettext('asciicasemap')), 'i;ascii-casemap');
if (in_array('comparator-i;ascii-numeric', $this->exts)) {
$select_comp->add(rcube::Q($this->plugin->gettext('asciinumeric')), 'i;ascii-numeric');
}
$need_comp = $rule['test'] != 'size' && $rule['test'] != 'duplicate';
$mout .= '<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 .= $select_comp->show($rule['comparator']);
$mout .= $this->comparator_selector($rule['comparator'], 'rule_comp', $id);
$mout .= '</div>';
// Advanced modifiers (mime)
@ -2096,32 +2106,36 @@ class rcube_sieve_engine
$select_action = new html_select(array('name' => "_action_type[$id]", 'id' => 'action_type'.$id,
'onchange' => 'action_type_select(' .$id .')'));
if (in_array('fileinto', $this->exts))
$select_action->add(rcube::Q($this->plugin->gettext('messagemoveto')), 'fileinto');
$select_action->add($this->plugin->gettext('messagemoveto'), 'fileinto');
if (in_array('fileinto', $this->exts) && in_array('copy', $this->exts))
$select_action->add(rcube::Q($this->plugin->gettext('messagecopyto')), 'fileinto_copy');
$select_action->add(rcube::Q($this->plugin->gettext('messageredirect')), 'redirect');
$select_action->add($this->plugin->gettext('messagecopyto'), 'fileinto_copy');
$select_action->add($this->plugin->gettext('messageredirect'), 'redirect');
if (in_array('copy', $this->exts))
$select_action->add(rcube::Q($this->plugin->gettext('messagesendcopy')), 'redirect_copy');
$select_action->add($this->plugin->gettext('messagesendcopy'), 'redirect_copy');
if (in_array('reject', $this->exts))
$select_action->add(rcube::Q($this->plugin->gettext('messagediscard')), 'reject');
$select_action->add($this->plugin->gettext('messagediscard'), 'reject');
else if (in_array('ereject', $this->exts))
$select_action->add(rcube::Q($this->plugin->gettext('messagediscard')), 'ereject');
$select_action->add($this->plugin->gettext('messagediscard'), 'ereject');
if (in_array('vacation', $this->exts))
$select_action->add(rcube::Q($this->plugin->gettext('messagereply')), 'vacation');
$select_action->add(rcube::Q($this->plugin->gettext('messagedelete')), 'discard');
$select_action->add($this->plugin->gettext('messagereply'), 'vacation');
$select_action->add($this->plugin->gettext('messagedelete'), 'discard');
if (in_array('imapflags', $this->exts) || in_array('imap4flags', $this->exts)) {
$select_action->add(rcube::Q($this->plugin->gettext('setflags')), 'setflag');
$select_action->add(rcube::Q($this->plugin->gettext('addflags')), 'addflag');
$select_action->add(rcube::Q($this->plugin->gettext('removeflags')), 'removeflag');
$select_action->add($this->plugin->gettext('setflags'), 'setflag');
$select_action->add($this->plugin->gettext('addflags'), 'addflag');
$select_action->add($this->plugin->gettext('removeflags'), 'removeflag');
}
if (in_array('editheader', $this->exts)) {
$select_action->add($this->plugin->gettext('addheader'), 'addheader');
$select_action->add($this->plugin->gettext('deleteheader'), 'deleteheader');
}
if (in_array('variables', $this->exts)) {
$select_action->add(rcube::Q($this->plugin->gettext('setvariable')), 'set');
$select_action->add($this->plugin->gettext('setvariable'), 'set');
}
if (in_array('enotify', $this->exts) || in_array('notify', $this->exts)) {
$select_action->add(rcube::Q($this->plugin->gettext('notify')), 'notify');
$select_action->add($this->plugin->gettext('notify'), 'notify');
}
$select_action->add(rcube::Q($this->plugin->gettext('messagekeep')), 'keep');
$select_action->add(rcube::Q($this->plugin->gettext('rulestop')), 'stop');
$select_action->add($this->plugin->gettext('messagekeep'), 'keep');
$select_action->add($this->plugin->gettext('rulestop'), 'stop');
$select_type = $action['type'];
if (in_array($action['type'], array('fileinto', 'redirect')) && $action['copy']) {
@ -2190,7 +2204,7 @@ class rcube_sieve_engine
}
}
$out .= '<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 .= html::tag('textarea', array(
'name' => '_action_reason[' . $id . ']',
@ -2353,7 +2367,7 @@ class rcube_sieve_engine
}
// @TODO: nice UI for mailto: (other methods too) URI parameters
$out .= '<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 .= '<div class="input-group">';
$out .= $select_method->show($method);
@ -2393,6 +2407,85 @@ class rcube_sieve_engine
$this->error_class($id, 'action', 'options', 'action_notifyoption'), 30) . '</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
if ($action['type'] == 'fileinto') {
// make sure non-existing (or unsubscribed) mailbox is listed (#1489956)
@ -2405,7 +2498,6 @@ class rcube_sieve_engine
}
$select = $this->rc->folder_selector(array(
'realnames' => false,
'maxlength' => 100,
'id' => 'action_mailbox' . $id,
'name' => "_action_mailbox[$id]",
@ -2935,4 +3027,63 @@ class rcube_sieve_engine
return $headers;
}
/**
* Match type selector
*/
protected function match_type_selector($name, $id, $test, $rule = null, $mode = 'all')
{
// matching type select (operator)
$select_op = new html_select(array(
'name' => "_{$name}[]",
'id' => "{$name}{$id}",
'style' => 'display:' .(!in_array($rule, array('size', 'duplicate')) ? 'inline' : 'none'),
'class' => 'operator_selector',
'onchange' => "{$name}_select(this, '{$id}')",
));
$select_op->add(rcube::Q($this->plugin->gettext('filtercontains')), 'contains');
$select_op->add(rcube::Q($this->plugin->gettext('filternotcontains')), 'notcontains');
$select_op->add(rcube::Q($this->plugin->gettext('filteris')), 'is');
$select_op->add(rcube::Q($this->plugin->gettext('filterisnot')), 'notis');
if ($mode == 'all') {
$select_op->add(rcube::Q($this->plugin->gettext('filterexists')), 'exists');
$select_op->add(rcube::Q($this->plugin->gettext('filternotexists')), 'notexists');
}
$select_op->add(rcube::Q($this->plugin->gettext('filtermatches')), 'matches');
$select_op->add(rcube::Q($this->plugin->gettext('filternotmatches')), 'notmatches');
if (in_array('regex', $this->exts)) {
$select_op->add(rcube::Q($this->plugin->gettext('filterregex')), 'regex');
$select_op->add(rcube::Q($this->plugin->gettext('filternotregex')), 'notregex');
}
if ($mode == 'all' && in_array('relational', $this->exts)) {
$select_op->add(rcube::Q($this->plugin->gettext('countisgreaterthan')), 'count-gt');
$select_op->add(rcube::Q($this->plugin->gettext('countisgreaterthanequal')), 'count-ge');
$select_op->add(rcube::Q($this->plugin->gettext('countislessthan')), 'count-lt');
$select_op->add(rcube::Q($this->plugin->gettext('countislessthanequal')), 'count-le');
$select_op->add(rcube::Q($this->plugin->gettext('countequals')), 'count-eq');
$select_op->add(rcube::Q($this->plugin->gettext('countnotequals')), 'count-ne');
$select_op->add(rcube::Q($this->plugin->gettext('valueisgreaterthan')), 'value-gt');
$select_op->add(rcube::Q($this->plugin->gettext('valueisgreaterthanequal')), 'value-ge');
$select_op->add(rcube::Q($this->plugin->gettext('valueislessthan')), 'value-lt');
$select_op->add(rcube::Q($this->plugin->gettext('valueislessthanequal')), 'value-le');
$select_op->add(rcube::Q($this->plugin->gettext('valueequals')), 'value-eq');
$select_op->add(rcube::Q($this->plugin->gettext('valuenotequals')), 'value-ne');
}
return $select_op->show($test);
}
protected function comparator_selector($comparator, $name, $id)
{
$select_comp = new html_select(array('name' => "_{$name}[]", 'id' => "{$name}_op{$id}"));
$select_comp->add(rcube::Q($this->plugin->gettext('default')), '');
$select_comp->add(rcube::Q($this->plugin->gettext('octet')), 'i;octet');
$select_comp->add(rcube::Q($this->plugin->gettext('asciicasemap')), 'i;ascii-casemap');
if (in_array('comparator-i;ascii-numeric', $this->exts)) {
$select_comp->add(rcube::Q($this->plugin->gettext('asciinumeric')), 'i;ascii-numeric');
}
return $select_comp->show($comparator);
}
}

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

@ -31,6 +31,7 @@ class rcube_sieve_script
'copy', // RFC3894
'date', // RFC5260
'duplicate', // RFC7352
'editheader', // RFC5293
'enotify', // RFC5435
'envelope', // RFC5228
'ereject', // RFC5429
@ -422,6 +423,26 @@ class rcube_sieve_script
. self::escape_string($action['target']);
break;
case 'addheader':
case 'deleteheader':
array_push($exts, 'editheader');
$action_script .= $action['type'];
if (!empty($action['index'])) {
$action_script .= " :index " . intval($action['index']);
}
if (!empty($action['last']) && (!empty($action['index']) || $action['type'] == 'addheader')) {
$action_script .= " :last";
}
if ($action['type'] == 'deleteheader') {
$action['type'] = $action['match-type'];
$this->add_operator($action, $action_script, $exts);
}
$action_script .= " " . self::escape_string($action['name']);
if ((is_string($action['value']) && strlen($action['value']) > 0) || (is_array($action['value']) && !empty($action['value']))) {
$action_script .= " " . self::escape_string($action['value']);
}
break;
case 'keep':
case 'discard':
case 'stop':
@ -908,6 +929,21 @@ class rcube_sieve_script
$result[] = $action;
break;
case 'addheader':
case 'deleteheader':
$args = $this->test_tokens($tokens);
if ($token == 'deleteheader') {
$args['match-type'] = $args['type'];
}
if (($index = array_search(':last', $tokens)) !== false) {
$args['last'] = true;
unset($tokens[$index]);
}
$action = array('type' => $token, 'name' => array_shift($tokens), 'value' => array_shift($tokens));
$result[] = $action + $args;
break;
case 'reject':
case 'ereject':
case 'setflag':

@ -381,7 +381,7 @@ class rcube_sieve_vacation extends rcube_sieve_engine
if ($this->rc->config->get('managesieve_vacation') != 2 && !empty($this->vacation['list'])) {
$after = new html_select(array('name' => 'vacation_after', 'id' => 'vacation_after'));
$after->add('', '');
$after->add('---', '');
foreach ($this->vacation['list'] as $idx => $rule) {
$after->add($rule, $idx);
}
@ -461,7 +461,7 @@ class rcube_sieve_vacation extends rcube_sieve_engine
}
// redirect target
$action_target = ' <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"'
. ' value="' .($redirect ? rcube::Q($this->vacation['target'], 'strict', false) : '') . '"'
. (!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('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));

@ -113,6 +113,18 @@ $labels['flagdeleted'] = 'Deleted';
$labels['flaganswered'] = 'Answered';
$labels['flagflagged'] = 'Flagged';
$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['setvarname'] = 'Variable name:';
$labels['setvarvalue'] = 'Variable value:';
@ -264,5 +276,6 @@ $messages['forwardsaved'] = 'Forward data saved successfully.';
$messages['emptyvacationbody'] = 'Body of vacation message is required!';
$messages['duplicate.conflict.err'] = 'Both header and unique identifier are not allowed.';
$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),
forward: document.getElementById('action_forward' + 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') {
@ -781,17 +783,8 @@ function action_type_select(id)
else if (v.match(/^(add|set|remove)flag$/)) {
enabled.flags = 1;
}
else if (v == 'vacation') {
enabled.vacation = 1;
}
else if (v == 'forward') {
enabled.forward = 1;
}
else if (v == 'set') {
enabled.set = 1;
}
else if (v == 'notify') {
enabled.notify = 1;
else if (v.match(/^(vacation|forward|set|notify|addheader|deleteheader)$/)) {
enabled[v] = 1;
}
for (var x in elems) {

@ -244,12 +244,6 @@ td.rowtargets div.adv input
margin-bottom: 1px;
}
html.mozilla #filter-form select
{
padding-top: 3px;
padding-bottom: 3px;
}
input.disabled, input.disabled:hover
{
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)
{
if ($this->init_ldap($args['host'])) {
if ($this->init_ldap($args['host'], $args['user'])) {
$results = $this->ldap->search('*', $args['user'], true);
if (count($results->records) == 1) {
@ -96,7 +96,7 @@ class new_user_identity extends rcube_plugin
return $args;
}
private function init_ldap($host)
private function init_ldap($host, $user)
{
if ($this->ldap) {
return $this->ldap->ready;
@ -112,11 +112,17 @@ class new_user_identity extends rcube_plugin
return false;
}
$this->ldap = new new_user_identity_ldap_backend(
$ldap_config[$addressbook],
$this->rc->config->get('ldap_debug'),
$this->rc->config->mail_domain($host),
$match);
$debug = $this->rc->config->get('ldap_debug');
$domain = $this->rc->config->mail_domain($host);
$props = $ldap_config[$addressbook];
// 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;
}

@ -46,6 +46,7 @@
2.19. Vpopmail daemon (vpopmaild)
2.20. Plesk (Plesk RPC-API)
2.21. Kpasswd
2.22. Modoboa
3. Driver API
4. Sudo setup
@ -355,13 +356,20 @@
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.
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,7 +3,7 @@
"type": "roundcube-plugin",
"description": "Password Change for Roundcube. Plugin adds a possibility to change user password using many methods (drivers) via Settings/Password tab.",
"license": "GPLv3+",
"version": "4.4",
"version": "4.5",
"authors": [
{
"name": "Aleksander Machniak",

@ -482,3 +482,8 @@ $config['password_plesk_rpc_path'] = 'enterprise/control/agent.php';
// ---------------------
// Command to use
$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
{
public $task = 'settings|login';
public $task = '?(?!logout).*';
public $noframe = 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-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('login_after', array($this, 'login_after'));
}
@ -179,6 +186,10 @@ class password extends rcube_plugin
// Reset session password
$_SESSION['password'] = $rcmail->encrypt($plugin['new_pass']);
if ($rcmail->config->get('newuserpassword')) {
$rcmail->user->save_prefs(array('newuserpassword' => false));
}
// Log password change
if ($rcmail->config->get('password_log')) {
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)
{
if ($this->newuser && $this->check_host_login_exceptions()) {
$rcmail = rcmail::get_instance();
$rcmail->user->save_prefs(array('newuserpassword' => true));
$args['_task'] = 'settings';
$args['_action'] = 'plugin.password';
$args['_first'] = 'true';
@ -428,12 +442,10 @@ class password extends rcube_plugin
$rcmail = rcmail::get_instance();
$prefix = '';
$crypted = '';
$default = false;
if (empty($method) || $method == 'default') {
$method = $rcmail->config->get('password_algorithm');
$prefixed = $rcmail->config->get('password_algorithm_prefix');
$default = true;
}
else if ($method == 'crypt') { // deprecated
if (!($method = $rcmail->config->get('password_crypt_hash'))) {
@ -622,7 +634,7 @@ class password extends rcube_plugin
return false;
}
if (!$default) {
if (!$prefixed) {
$prefixed = (bool) $rcmail->config->get('password_dovecotpw_with_method');
}

@ -458,7 +458,7 @@ class rcmail extends rcube
// add some basic labels to client
$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');
return $this->output;
@ -926,12 +926,6 @@ class rcmail extends rcube
$error = array('code' => 403, 'message' => "Request security check failed");
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 |
| |
| 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 |
| any later version with exceptions for skins & plugins. |
@ -33,7 +33,7 @@ class rcmail_install
public $bool_config_props = array();
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(
'skin_path' => 'skin',
'locale_string' => 'language',

@ -483,8 +483,26 @@ EOF;
public function reset($all = false)
{
$framed = $this->framed;
$task = $this->env['task'];
$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();
// let some env variables survive
@ -492,16 +510,23 @@ EOF;
$this->framed = $framed || $this->env['framed'];
$this->js_labels = array();
$this->js_commands = array();
$this->script_files = array();
$this->scripts = array();
$this->header = '';
$this->footer = '';
$this->body = '';
$this->css_files = array();
$this->script_files = array();
// load defaults
if (!$all) {
$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
// 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->script_files = array();
$this->header = '';
@ -588,7 +614,7 @@ EOF;
// allow (legal) iframe content to be loaded
$iframe = $this->framed || $this->env['framed'];
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);
}
}
@ -1263,7 +1289,7 @@ EOF;
}
}
// execute object handler function
else if (function_exists($handler)) {
else if (is_callable($handler)) {
$this->prepare_object_attribs($attrib);
$content = call_user_func($handler, $attrib);
}
@ -1649,11 +1675,11 @@ EOF;
* Link an external script file
*
* @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);
}
@ -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
$page_title = html::quote($this->pagetitle);
$page_header = '';
@ -1757,40 +1796,15 @@ EOF;
$page_header.= $this->charset . '" />'."\n";
}
// definition of the code to be placed in the document header and footer
if (is_array($this->script_files['head'])) {
foreach ($this->script_files['head'] as $file) {
$page_header .= html::script($file);
}
}
$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";
}
// include scripts into header/footer
$page_header .= array_reduce((array) $this->script_files['head'], $merge_script_files);
$page_header .= array_reduce(array($this->scripts['head_top'] . $this->scripts['head']), $merge_scripts);
$page_header .= $this->header . "\n";
$page_header .= array_reduce((array) $this->script_files['head_bottom'], $merge_script_files);
if (!empty($this->scripts['foot'])) {
$page_footer .= html::script(array(), $this->scripts['foot']);
}
$page_footer .= array_reduce((array) $this->script_files['foot'], $merge_script_files);
$page_footer .= $this->footer . "\n";
$page_footer .= array_reduce((array) $this->scripts['foot'], $merge_scripts);
// find page header
if ($hpos = stripos($output, '</head>')) {

@ -1526,7 +1526,7 @@ function rcube_webmail()
if (task == 'mail')
url += '&_mbox=INBOX';
else if (task == 'logout' && !this.env.server_error) {
else if (task == 'logout') {
url = this.secure_url(url);
this.clear_compose_data();
}
@ -4109,8 +4109,8 @@ function rcube_webmail()
var ul = $('<ul>').addClass('keylist').appendTo(content);
$.each(private_keys, function(i, key) {
$('<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 {
$('<p>').text(ref.get_label('encryptionnoprivkeysinmailvelope')).appendTo(content);
@ -9680,7 +9680,7 @@ function rcube_webmail()
// catch Error: Permission denied to access property rcmail
try {
if (win && !win.closed) {
if (win && !win.closed && win !== window) {
// try parent of the opener window, e.g. preview frame
if (deep && (!win.rcmail || win.rcmail.env.framed) && win.parent && win.parent.rcmail)
win = win.parent;
@ -10016,12 +10016,12 @@ function rcube_webmail()
// some static methods
rcube_webmail.long_subject_title = function(elem, indent)
rcube_webmail.long_subject_title = function(elem, indent, text_elem)
{
if (!elem.title) {
var $elem = $(elem);
var $elem = $(text_elem || elem);
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(/\/$/, ''),
conf = {
selector: '#' + ($('#' + id).is('.mce_editor') ? id : 'fake-editor-id'),
cache_suffix: 's=4071300',
cache_suffix: 's=4080200',
theme: 'modern',
language: config.lang,
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)
{
// handle PHP exceptions
if ($arg instanceof Exception) {
$arg = array(
'code' => $arg->getCode(),
@ -1328,6 +1329,13 @@ class rcube
$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
if (!$cli && class_exists('rcmail_install', false)) {
$rci = rcmail_install::get_instance();
@ -1590,14 +1598,15 @@ class rcube
/**
* Send the given message using the configured method.
*
* @param object $message Reference to Mail_MIME object
* @param string $from Sender address string
* @param array $mailto Array of recipient address strings
* @param array $error SMTP error array (reference)
* @param string $body_file Location of file with saved message body (reference),
* used when delay_file_io is enabled
* @param array $options SMTP options (e.g. DSN request)
* @param bool $disconnect Close SMTP connection ASAP
* @param object $message Reference to Mail_MIME object
* @param string $from Sender address string
* @param array|string $mailto Either a comma-separated list of recipients (RFC822 compliant),
* or an array of recipients, each RFC822 valid
* @param array $error SMTP error array (reference)
* @param string $body_file Location of file with saved message body (reference),
* used when delay_file_io is enabled
* @param array $options SMTP options (e.g. DSN request)
* @param bool $disconnect Close SMTP connection ASAP
*
* @return boolean Send status.
*/

@ -2822,7 +2822,7 @@ class rcube_imap_generic
}
// 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;
continue;
}
@ -3131,6 +3131,11 @@ class rcube_imap_generic
list(, , $quota_root) = $this->tokenizeResponse($line, 3);
$quotas = $this->tokenizeResponse($line, 1);
if (empty($quotas)) {
continue;
}
foreach (array_chunk($quotas, 3) as $quota) {
list($type, $used, $total) = $quota;
$type = strtolower($type);

@ -300,7 +300,7 @@ class rcube_ldap extends rcube_addressbook
}
// 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);
}
else {

@ -948,9 +948,11 @@ class rcube_message
private function add_part($part, $type = null)
{
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) {
case 'inline': $this->inline_parts[] = $part; break;
case 'attachment': $this->attachments[] = $part; break;
case 'inline': $this->inline_parts[(string) $part->mime_id] = $part; break;
case 'attachment': $this->attachments[(string) $part->mime_id] = $part; break;
default: $this->parts[] = $part; break;
}
}

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

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

@ -153,19 +153,6 @@ class rcube_utils
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
*
@ -1364,4 +1351,41 @@ class rcube_utils
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
$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'),
$vcard);
// Cleanup
$vcard = preg_replace(array(
// 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*\./mi', // remove item1.ADR instead of ADR
'/\n+/', // remove empty lines

@ -615,7 +615,12 @@ class rcube_washtml
'<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
$badwordchars = array(
@ -638,24 +643,6 @@ class rcube_washtml
$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"
$html = preg_replace_callback('/(<(?!\!)[\/]*)([^\s>]+)([^>]*)/', array($this, 'html_tag_callback'), $html);

@ -75,13 +75,13 @@ if (!is_array($COMPOSE)) {
// add some labels to client
$OUTPUT->add_label('notuploadedwarning', 'savingmessage', 'siginserted', 'responseinserted',
'messagesaved', 'converting', 'editorwarning', 'uploading', 'uploadingmany',
'messagesaved', 'converting', 'editorwarning', 'discard',
'fileuploaderror', 'sendmessage', 'newresponse', 'responsename', 'responsetext', 'save',
'savingresponse', 'restoresavedcomposedata', 'restoremessage', 'delete', 'restore', 'ignore',
'selectimportfile', 'messageissent', 'loadingdata', 'nopubkeyfor', 'nopubkeyforsender',
'encryptnoattachments','encryptedsendialog','searchpubkeyservers', 'importpubkeys',
'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'));

@ -1003,7 +1003,8 @@ function rcmail_washtml_callback($tagname, $attrib, $content, $washtml)
}
// 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()
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;
}
else {
$out = html::tag('style', array('type' => 'text/css'), $content);
$out = html::tag('style', array('type' => 'text/css'), $decoded);
}
break;
}
@ -1606,7 +1607,9 @@ function rcmail_search_filter($attrib)
$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
// the same as in app.js:add_message_row()
@ -1669,18 +1672,22 @@ function rcmail_message_error()
{
global $RCMAIL;
// Set env variables for messageerror.html template
if ($RCMAIL->action == 'show') {
$mbox_name = $RCMAIL->storage->get_folder();
// ... display message error page
if ($RCMAIL->output->template_exists('messageerror')) {
// 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('uid', null);
}
$RCMAIL->output->set_env('mailbox', $mbox_name);
$RCMAIL->output->set_env('uid', null);
}
// display error message
$RCMAIL->output->show_message('messageopenerror', 'error');
// ... display message error page
$RCMAIL->output->send('messageerror');
$RCMAIL->output->show_message('messageopenerror', 'error');
$RCMAIL->output->send('messageerror');
}
else {
$RCMAIL->raise_error(array('code' => 410), false, true);
}
}
function rcmail_message_import_form($attrib = array())

@ -115,6 +115,12 @@ if (!$savedraft) {
'<p><br /></p>',
),
$message_body);
rcube_utils::preg_error(array(
'line' => __LINE__,
'file' => __FILE__,
'message' => "Could not format HTML!"
), true);
}
// Check spelling before send
@ -199,6 +205,12 @@ if (is_array($COMPOSE['attachments'])) {
$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);
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->add_handler('supportlink', 'rcmail_supportlink');
$OUTPUT->add_handler('pluginlist', 'rcmail_plugins_list');
$OUTPUT->add_handlers(array(
'supportlink' => 'rcmail_supportlink',
'pluginlist' => 'rcmail_plugins_list',
'copyright' => function() { return RC_COPYRIGHT; },
'license' => function() { return RC_LICENSE; },
));
$OUTPUT->send('about');

@ -98,11 +98,13 @@ function rcmail_folder_form($attrib)
}
$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) {
$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(

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

@ -433,7 +433,9 @@ function rcmail_folder_filter($attrib)
$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();
$select = new html_select($attrib);

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

@ -21,28 +21,8 @@
$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
else if ($ERROR_CODE == 401) {
if ($ERROR_CODE == 401) {
$__error_title = mb_strtoupper($rcmail->gettext('errauthorizationfailed'));
$__error_text = nl2br($rcmail->gettext('errunauthorizedexplain') . "\n" .
$rcmail->gettext('errcontactserveradmin'));
@ -72,6 +52,20 @@ else if ($ERROR_CODE == 404) {
$__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
else if ($ERROR_CODE == 450 && $_SERVER['REQUEST_METHOD'] == 'GET' && $rcmail->action == 'compose') {
$url = $rcmail->url('compose');
@ -102,15 +96,17 @@ else {
// inform plugins
if ($rcmail && $rcmail->plugins) {
$plugin = $rcmail->plugins->exec_hook('error_page', array(
'code' => $ERROR_CODE,
'code' => $ERROR_CODE,
'title' => $__error_title,
'text' => $__error_text,
'text' => $__error_text,
));
if (!empty($plugin['title']))
if (!empty($plugin['title'])) {
$__error_title = $plugin['title'];
if (!empty($plugin['text']))
}
if (!empty($plugin['text'])) {
$__error_text = $plugin['text'];
}
}
$HTTP_ERR_CODE = $ERROR_CODE && $ERROR_CODE < 600 ? $ERROR_CODE : 500;
@ -131,6 +127,7 @@ EOF;
if ($rcmail->output && $rcmail->output->template_exists('error')) {
$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('comm_path', $rcmail->comm_path);
$rcmail->output->send('error');

@ -56,6 +56,9 @@ function rcube_splitter(attrib)
// add the mouse event listeners
$(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)
$(window).resize(onResize);
@ -88,9 +91,12 @@ function rcube_splitter(attrib)
}
}
else {
this.p1.style.width = Math.floor(this.pos - this.p1pos.left - this.layer.width / 2) + 'px';
this.p2.style.left = Math.ceil(this.pos + this.layer.width / 2) + 'px';
this.layer.move(Math.round(this.pos - this.layer.width / 2 + 1), this.layer.y);
var max_width = $(window).width() - $(this.p1).offset().left - 150,
pos = Math.min(this.pos, max_width);
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) {
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';

@ -20,12 +20,8 @@
<div id="license">
<roundcube:object name="aboutcontent" />
<h2 class="sysname">Roundcube Webmail <roundcube:object name="version" /></h2>
<p class="copyright">Copyright &copy; 2005-2018, The Roundcube Dev Team</p>
<p class="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="http://roundcube.net/license">exceptions</a> for skins &amp; plugins apply.
</p>
<p class="copyright"><roundcube:object name="copyright" /></p>
<p class="license"><roundcube:object name="license" /></p>
<p class="links"><roundcube:object name="supportlink" label="support" target="_blank" /></p>
</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;
// Task menu
@color-taskmenu-background: #414e54;
@color-taskmenu-background: #2f3a3f;
@color-taskmenu-button: #fff;
@color-taskmenu-button-selected: @color-taskmenu-button;
@color-taskmenu-button-action: @color-main;

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

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

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

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

@ -463,6 +463,14 @@
color: @color-toolbarmenu-hover;
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 {
@ -615,18 +623,88 @@
}
.mce-menu {
width: auto !important;
width: @layout-mobile-menu-width !important;
right: 0;
left: 0 !important;
margin: 0 1rem !important;
top: 0 !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 {
width: 100% !important;
}
}
div.mce-menubtn.mce-opened {
z-index: 65530 !important;
.mce-menu-item {
height: @layout-touch-menu-record-height;
line-height: @layout-touch-menu-record-height;
padding: 0 .5rem;
border-left: 0;
border-bottom: 1px solid @color-list-border;
margin: 0;
.mce-ico {
font-size: 150%;
padding: 0 .7rem 0 .25rem;
margin-top: -.2rem;
}
.mce-text {
font-size: 1.2rem;
.font-family;
line-height: @layout-touch-menu-record-height;
color: @color-font;
}
.mce-caret {
display: none;
}
&.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,

@ -98,6 +98,22 @@ input.smart-upload {
}
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 {
margin-bottom: .25rem;
@ -384,11 +400,6 @@ html.ms .propform {
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;
padding: .375rem .5rem;
&.input-group-text {
min-width: 2.4rem;
}
&:before {
&:extend(.font-icon-class);
margin: 0 !important;
@ -617,16 +632,11 @@ html.ms .propform {
}
}
td.editfield { width: 99%; /* TODO */ }
@proplist-record-height: 2rem;
.proplist {
margin-bottom: 0;
.formcontent > &,
fieldset > & {
padding-left: 0;
}
padding: 0;
li {
list-style-type: none;
@ -647,7 +657,7 @@ td.editfield { width: 99%; /* TODO */ }
}
}
label {
label:not(.input-group-text) {
margin: 0;
line-height: @proplist-record-height;
}
@ -1166,6 +1176,10 @@ input.icon-checkbox {
cursor: pointer;
width: 1.5em;
html.touch & {
font-size: 1.5rem;
}
&:before {
&:extend(.font-icon-class);
content: @fa-var-toggle-off;
@ -1216,8 +1230,28 @@ input.icon-checkbox {
}
}
html.touch input.icon-checkbox + label {
font-size: 1.5rem;
.input-group-combo {
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;
max-height: 400px;
border-radius: .3rem;
z-index: 240;
position: absolute;
.ui-state-active {
border: 0 !important;
@ -35,6 +37,7 @@
.ui-menu-item {
white-space: nowrap;
cursor: default;
}
.ui-menu-item-wrapper {
@ -117,14 +120,16 @@
padding-right: 0;
padding-left: 0;
text-decoration: none;
color: @color-font;
&:focus {
background-color: @color-input-border-focus-shadow;
background-color: fade(@color-btn-primary-background, 50%);
}
&.options {
order: -1;
padding: .375rem .25rem;
margin-right: .3rem;
&:before {
// this icon is for mobile version

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

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

@ -116,7 +116,7 @@
position: absolute;
bottom: 0;
left: 0;
width: @layout-menu-width;
background-color: @color-taskmenu-background;
}
.action-buttons {
@ -154,6 +154,7 @@
width: @layout-menu-width;
height: 1.75rem;
line-height: 1.5;
float: none; // fixed overflowing text in Edge
}
}
@ -168,14 +169,19 @@
.menu {
.popover-header {
height: @layout-header-height;
display: flex !important;
align-items: center;
justify-content: center;
line-height: @layout-header-height;
border: 0;
border-radius: 0;
text-align: center;
img {
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)) {
@ -185,23 +191,15 @@
a {
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 & {
display: flex !important;
align-items: center;
justify-content: center;
padding: 0 .5rem;
img {
max-height: @layout-touch-header-height;
max-width: @layout-mobile-menu-width - 50px;
}

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

@ -6,13 +6,8 @@
<div class="content frame-content">
<h2 class="sysname">Roundcube Webmail <roundcube:object name="version" /></h2>
<p class="copyright">Copyright &copy; 2005-2018, The Roundcube Dev Team</p>
<p class="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.
</p>
<p class="copyright"><roundcube:object name="copyright" /></p>
<p class="license"><roundcube:object name="license" /></p>
<div class="readtext">
<h3><roundcube:label name="installedplugins" /></h3>
<roundcube:object name="pluginlist" id="pluginlist" class="records-table" />

@ -3,8 +3,9 @@
<roundcube:if condition="!env:framed && !env:extwin" />
<roundcube:include file="includes/menu.html" />
<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>
<span class="header-title"></span>
</div>
<div class="frame-content scroller">
<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">
<h3 id="aria-label-search-menu" class="voice"><roundcube:label name="searchmod" /></h3>
<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 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 class="scroller" tabindex="-1">

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

@ -1,7 +1,7 @@
<div class="menu">
<h2 id="aria-label-tasknav" class="voice"><roundcube:label name="arialabeltasknav" /></h2>
<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>
</div>
<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>
<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:object name="loginform" form="login-form" size="40" submit=true />
<div id="login-footer" role="contentinfo">

@ -61,13 +61,13 @@
<div class="input-group-prepend">
<label for="searchfilter" class="input-group-text"><roundcube:label name="type" /></label>
</div>
<roundcube:object name="searchfilter" id="searchfilter" />
<roundcube:object name="searchfilter" id="searchfilter" noevent="true" />
</div>
<div class="input-group">
<div class="input-group-prepend">
<label for="s_interval" class="input-group-text"><roundcube:label name="date" /></label>
</div>
<roundcube:object name="searchinterval" id="s_interval" onchange="rcmail.set_searchinterval($(this).val())" />
<roundcube:object name="searchinterval" id="s_interval" />
</div>
<div class="input-group">
<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);
$('.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'
@ -512,8 +514,15 @@ function rcube_elastic_ui()
.on('click', function() { if ($(this).is('.active')) table.toggleClass('withselection'); })
.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">');
// 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 {
button.appendTo(toolbar).addClass('icon');
@ -657,7 +666,7 @@ function rcube_elastic_ui()
$('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')) {
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-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)
$('#message-objects', context).children(':not(.ui.alert)').each(function() {
// 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
*/
function tinymce_style(elem)
{
// TinyMCE dialog widnows
if ($(elem).is('.mce-window')) {
var body = $(elem).find('.mce-window-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
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')) {
env.last_selected = layout.content[0];
@ -1675,9 +1726,13 @@ function rcube_elastic_ui()
warning: 'alert-warning',
error: 'alert-danger',
loading: 'alert-info loading',
uploading: 'alert-info loading',
vcardattachment: 'alert-info' /* vcard_attachments plugin */
};
// Type can be e.g. 'notice chat'
type = type.split(' ')[0];
if (wrap && addicon && !$(object).is('.aligned-buttons')) {
// we need the content to be non-text node for best alignment
tmp = $(object).html();
@ -1741,6 +1796,10 @@ function rcube_elastic_ui()
return true;
}
if (rcmail.task == 'mail' && $('#s_interval').val()) {
return true;
}
if (rcmail.gui_objects.search_filter && $(rcmail.gui_objects.search_filter).val() != 'ALL') {
return true;
}
@ -1775,19 +1834,22 @@ function rcube_elastic_ui()
$('button.search', options).off('click.search').on('click.search', function() {
options_button.trigger('click');
update_func();
});
}
});
input.on('input change', update_func)
.on('focus', function() { input.attr('placeholder', ''); })
.on('blur', function() { input.attr('placeholder', placeholder); });
.on('focus blur', function(e) { input.attr('placeholder', e.type == 'blur' ? placeholder : ''); });
// Search reset action
$('a.reset', bar).on('click', function(e) {
// for treelist widget's search setting val and keyup.treelist is needed
// in normal search form reset-search command will do the trick
input.val('').change().trigger('keyup.treelist', {keyCode: 27});
if ($(bar).is('.open')) {
options_button.click();
}
// Reset filter
if (rcmail.gui_objects.search_filter) {
@ -1796,22 +1858,21 @@ function rcube_elastic_ui()
if (rcmail.gui_objects.foldersfilter) {
$(rcmail.gui_objects.foldersfilter).val('---').change();
rcmail.folder_filter('---');
}
update_func();
});
rcmail.addEventListener('init', function() {
update_func();
if (rcmail.gui_objects.search_filter) {
$(rcmail.gui_objects.search_filter).on('change', update_func);
}
if (rcmail.gui_objects.foldersfilter) {
$(rcmail.gui_objects.foldersfilter).on('change', update_func);
}
});
rcmail.addEventListener('init', function() { update_func(); })
.addEventListener('beforelist', function() {
if ($(bar).is('.open')) {
options_button.click(); // close options form on 'list' request
}
})
.addEventListener('responsebeforesearch', function() {
update_func();
});
};
/**
@ -2335,7 +2396,7 @@ function rcube_elastic_ui()
{
var content = $('#listoptions-menu'),
width = content.width() + 25,
dialog = content.clone();
dialog = content.clone(true);
// set form values
$('select[name="sort_col"]', dialog).val(rcmail.env.sort_col || '');
@ -2436,15 +2497,17 @@ function rcube_elastic_ui()
{
var n, all,
list = $('input[name="s_mods[]"]', obj),
scope_select = $('select[name=s_scope]', obj),
scope_select = $('#s_scope', obj),
mbox = rcmail.env.mailbox,
mods = rcmail.env.search_mods,
scope = rcmail.env.search_scope || 'base';
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);
if (list.length) {
list.on('change', function() { set_searchmod(obj, this); });
rcmail.addEventListener('beforesearch', function() { set_searchmod(obj); });
}
}
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,
mods = rcmail.env.search_mods,
mbox = rcmail.env.mailbox,
scope = $('select[name=s_scope]', menu).val();
scope = $('#s_scope', menu).val(),
interval = $('#s_interval', menu).val();
if (scope == 'all') {
mbox = '*';
@ -2497,12 +2561,19 @@ function rcube_elastic_ui()
}
m = mods[mbox];
all = 'text';
rcmail.env.search_scope = scope;
rcmail.env.search_interval = interval;
}
else { //addressbook
m = mods;
all = '*';
}
if (!elem) {
return;
}
if (!elem.checked) {
delete(m[elem.value]);
}

@ -1,11 +1,12 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta charset="UTF-8">
<title></title>
<style type="text/css">
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>
</head>
<body></body>

@ -8,15 +8,9 @@
<div class="readtext">
<roundcube:object name="aboutcontent" />
<h2 class="sysname">Roundcube Webmail <roundcube:object name="version" /></h2>
<p class="copyright">Copyright &copy; 2005-2018, The Roundcube Dev Team</p>
<p class="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.
</p>
<p class="copyright"><roundcube:object name="copyright" /></p>
<p class="license"><roundcube:object name="license" /></p>
</div>
<div class="readtext">

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

@ -75,6 +75,7 @@ class MailFunc extends PHPUnit_Framework_TestCase
$this->assertNotRegExp('/src="skins/', $washed, "Remove local references");
$this->assertNotRegExp('/\son[a-z]+/', $washed, "Remove on* attributes");
$this->assertNotContains('onload', $washed, "Handle invalid style");
$html = rcmail_html4inline($washed, array('container_id' => 'foo'));
$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
</p>
<html><svg><style><//><body onload=alert(1)>
</body>
</html>

Loading…
Cancel
Save