summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG12
-rw-r--r--config/defaults.inc.php2
-rw-r--r--index.php2
-rw-r--r--plugins/password/README3
-rw-r--r--plugins/password/config.inc.php.dist3
-rw-r--r--plugins/password/drivers/vpopmaild.php2
-rw-r--r--program/include/rcmail_output_html.php127
-rw-r--r--program/js/app.js71
-rw-r--r--program/js/treelist.js226
-rw-r--r--program/lib/Mail/mime.php27
-rw-r--r--program/lib/Mail/mimePart.php9
-rw-r--r--program/lib/Roundcube/rcube.php5
-rw-r--r--program/lib/Roundcube/rcube_html2text.php2
-rw-r--r--program/lib/Roundcube/rcube_imap.php51
-rw-r--r--program/lib/Roundcube/rcube_imap_search.php25
-rw-r--r--program/lib/Roundcube/rcube_ldap_generic.php3
-rw-r--r--program/lib/Roundcube/rcube_result_multifolder.php27
-rw-r--r--program/lib/Roundcube/rcube_storage.php13
-rw-r--r--program/lib/Roundcube/rcube_string_replacer.php2
-rw-r--r--program/lib/Roundcube/rcube_text2html.php4
-rw-r--r--program/steps/addressbook/save.inc4
-rw-r--r--program/steps/mail/check_recent.inc4
-rw-r--r--program/steps/mail/list.inc2
-rw-r--r--program/steps/mail/move_del.inc2
-rw-r--r--program/steps/mail/search.inc10
-rw-r--r--program/steps/utils/html2text.inc5
-rw-r--r--program/steps/utils/text2html.inc5
-rw-r--r--skins/classic/templates/about.html2
-rw-r--r--skins/larry/images/buttons.pngbin39455 -> 16644 bytes
-rw-r--r--skins/larry/images/listicons.pngbin29530 -> 13514 bytes
-rw-r--r--skins/larry/mail.css21
-rw-r--r--skins/larry/styles.css29
-rw-r--r--skins/larry/templates/about.html2
-rw-r--r--skins/larry/templates/compose.html6
-rw-r--r--tests/Framework/StringReplacer.php4
35 files changed, 509 insertions, 203 deletions
diff --git a/CHANGELOG b/CHANGELOG
index b265b1053..d4cc0991e 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -6,6 +6,7 @@ CHANGELOG Roundcube Webmail
- Make identity name field optional (#1489510)
- Utility script to remove user records from the local database
- Plugin API: Added message_saved hook (#1489752)
+- Plugin API: Added imap_search_before hook
- Support messages import from zip archives
- Zipdownload: Added mbox format support (#1486069)
- Drop support for IE6, move IE7/IE8 support to legacy_browser plugin
@@ -16,9 +17,18 @@ CHANGELOG Roundcube Webmail
- Set In-Reply-To and References for forwarded messages (#1489593)
- Removed redundant default_folders config option (#1489737)
- Implemented IMAP SPECIAL-USE extension support [RFC6154] (#1487830)
+- Add configurable LDAP_OPT_DEREF option (#1489864)
+- Optimize some framed pages content for better performance (#1489792)
+- Fix mbox files import
+- Fix unintentional draft autosave request if autosave is disabled (#1489882)
+- Fix malformed References: header in send/saved mail (#1489891)
+- Fix handling unicode characters in links (#1489898)
+
+RELEASE 1.0.1
+-------------
- Support 'error' and 'body_file' return attribs in 'message_before_send' hook (#1489595)
- Apply user-specific replacements to group's base_dn property (#1489779)
-- Fix mbox files import
+- Fix missing email address when importing contacts from outlook csv (#1489830)
- Fix bug where "With attachment" option in search filter wasn't selected after return from mail view (#1489774)
- Fix "washing" of unicoded style attributes (#1489777)
- Fix unintentional redirect from compose page in Webkit browsers (#1489789)
diff --git a/config/defaults.inc.php b/config/defaults.inc.php
index a76eec6dc..85190eb0a 100644
--- a/config/defaults.inc.php
+++ b/config/defaults.inc.php
@@ -784,6 +784,8 @@ $config['ldap_public']['Verisign'] = array(
'sizelimit' => '0', // Enables you to limit the count of entries fetched. Setting this to 0 means no limit.
'timelimit' => '0', // Sets the number of seconds how long is spend on the search. Setting this to 0 means no limit.
'referrals' => false, // Sets the LDAP_OPT_REFERRALS option. Mostly used in multi-domain Active Directory setups
+ 'dereference' => 0, // Sets the LDAP_OPT_DEREF option. One of: LDAP_DEREF_NEVER, LDAP_DEREF_SEARCHING, LDAP_DEREF_FINDING, LDAP_DEREF_ALWAYS
+ // Used where addressbook contains aliases to objects elsewhere in the LDAP tree.
// definition for contact groups (uncomment if no groups are supported)
// for the groups base_dn, the user replacements %fu, %u, $d and %dc work as for base_dn (see above)
diff --git a/index.php b/index.php
index 239dc004f..3674db1d7 100644
--- a/index.php
+++ b/index.php
@@ -211,7 +211,7 @@ if (empty($RCMAIL->user->ID)) {
$OUTPUT->show_message('sessionerror', 'error', null, true, -1);
}
- if ($OUTPUT->ajax_call || !empty($_REQUEST['_framed'])) {
+ if ($OUTPUT->ajax_call || $OUTPUT->framed) {
$OUTPUT->command('session_error', $RCMAIL->url(array('_err' => 'session')));
$OUTPUT->send('iframe');
}
diff --git a/plugins/password/README b/plugins/password/README
index 262ebfd86..c50eb0bf3 100644
--- a/plugins/password/README
+++ b/plugins/password/README
@@ -310,6 +310,9 @@
Set $config['password_vpopmaild_port'] to the port of vpopmaild.
+ Set $config['password_vpopmaild_timeout'] to the timeout used for the TCP
+ connection to vpopmaild (You may want to set it higher on busy servers).
+
3. Driver API
-------------
diff --git a/plugins/password/config.inc.php.dist b/plugins/password/config.inc.php.dist
index 8c83dd703..8f7a57f9a 100644
--- a/plugins/password/config.inc.php.dist
+++ b/plugins/password/config.inc.php.dist
@@ -256,6 +256,9 @@ $config['password_vpopmaild_host'] = 'localhost';
// TCP port used for vpopmaild connections
$config['password_vpopmaild_port'] = 89;
+// Timout used for the connection to vpopmaild (in seconds)
+$config['password_vpopmaild_timeout'] = 10;
+
// cPanel Driver options
// --------------------------
diff --git a/plugins/password/drivers/vpopmaild.php b/plugins/password/drivers/vpopmaild.php
index 6c1a9ee9d..40731206a 100644
--- a/plugins/password/drivers/vpopmaild.php
+++ b/plugins/password/drivers/vpopmaild.php
@@ -22,6 +22,8 @@ class rcube_vpopmaild_password
$rcmail->config->get('password_vpopmaild_port'), null))) {
return PASSWORD_CONNECT_ERROR;
}
+
+ $vpopmaild->setTimeout($rcmail->config->get('password_vpopmaild_timeout'),0);
$result = $vpopmaild->readLine();
if(!preg_match('/^\+OK/', $result)) {
diff --git a/program/include/rcmail_output_html.php b/program/include/rcmail_output_html.php
index 42b208e89..2a90f6a01 100644
--- a/program/include/rcmail_output_html.php
+++ b/program/include/rcmail_output_html.php
@@ -30,16 +30,16 @@ class rcmail_output_html extends rcmail_output
{
public $type = 'html';
- protected $message = null;
- protected $js_env = array();
- protected $js_labels = array();
- protected $js_commands = array();
- protected $skin_paths = array();
+ protected $message;
protected $template_name;
+ protected $js_env = array();
+ protected $js_labels = array();
+ protected $js_commands = array();
+ protected $skin_paths = array();
protected $scripts_path = '';
protected $script_files = array();
- protected $css_files = array();
- protected $scripts = array();
+ protected $css_files = array();
+ protected $scripts = array();
protected $default_template = "<html>\n<head><title></title></head>\n<body></body>\n</html>";
protected $header = '';
protected $footer = '';
@@ -58,8 +58,6 @@ class rcmail_output_html extends rcmail_output
/**
* Constructor
- *
- * @todo Replace $this->config with the real rcube_config object
*/
public function __construct($task = null, $framed = false)
{
@@ -67,7 +65,6 @@ class rcmail_output_html extends rcmail_output
$this->devel_mode = $this->config->get('devel_mode');
- //$this->framed = $framed;
$this->set_env('task', $task);
$this->set_env('x_frame_options', $this->config->get('x_frame_options', 'sameorigin'));
$this->set_env('standard_windows', (bool) $this->config->get('standard_windows'));
@@ -84,7 +81,7 @@ class rcmail_output_html extends rcmail_output
if (!empty($_REQUEST['_extwin']))
$this->set_env('extwin', 1);
- if ($this->framed || !empty($_REQUEST['_framed']))
+ if ($this->framed || $framed)
$this->set_env('framed', 1);
$lic = <<<EOF
@@ -256,8 +253,9 @@ EOF;
public function get_skin_file($file, &$skin_path = null, $add_path = null)
{
$skin_paths = $this->skin_paths;
- if ($add_path)
+ if ($add_path) {
array_unshift($skin_paths, $add_path);
+ }
foreach ($skin_paths as $skin_path) {
$path = realpath($skin_path . $file);
@@ -291,9 +289,9 @@ EOF;
{
$cmd = func_get_args();
if (strpos($cmd[0], 'plugin.') !== false)
- $this->js_commands[] = array('triggerEvent', $cmd[0], $cmd[1]);
+ $this->js_commands[] = array('triggerEvent', $cmd[0], $cmd[1]);
else
- $this->js_commands[] = $cmd;
+ $this->js_commands[] = $cmd;
}
/**
@@ -303,7 +301,7 @@ EOF;
{
$args = func_get_args();
if (count($args) == 1 && is_array($args[0]))
- $args = $args[0];
+ $args = $args[0];
foreach ($args as $name) {
$this->js_labels[$name] = $this->app->gettext($name);
@@ -344,13 +342,13 @@ EOF;
public function reset($all = false)
{
$framed = $this->framed;
- $env = $all ? null : array_intersect_key($this->env, array('extwin'=>1, 'framed'=>1));
+ $env = $all ? null : array_intersect_key($this->env, array('extwin'=>1, 'framed'=>1));
parent::reset();
// let some env variables survive
- $this->env = $this->js_env = $env;
- $this->framed = $framed || $this->env['framed'];
+ $this->env = $this->js_env = $env;
+ $this->framed = $framed || $this->env['framed'];
$this->js_labels = array();
$this->js_commands = array();
$this->script_files = array();
@@ -429,20 +427,27 @@ EOF;
array_unshift($this->js_commands, array('hide_message', $unlock));
}
- if (!empty($this->script_files))
- $this->set_env('request_token', $this->app->get_request_token());
+ if (!empty($this->script_files)) {
+ $this->set_env('request_token', $this->app->get_request_token());
+ }
+
+ $commands = $this->get_js_commands($framed);
- // write all env variables to client
- if ($commands = $this->get_js_commands()) {
- $js = $this->framed ? "if (window.parent) {\n" : '';
- $js .= $commands . ($this->framed ? ' }' : '');
- $this->add_script($js, 'head_top');
+ // if all js commands go to parent window we can ignore all
+ // script files and skip rcube_webmail initialization (#1489792)
+ if ($framed) {
+ $this->scripts = array();
+ $this->script_files = array();
}
+ // write all javascript commands
+ $this->add_script($commands, 'head_top');
+
// send clickjacking protection headers
- $iframe = $this->framed || !empty($_REQUEST['_framed']);
- if (!headers_sent() && ($xframe = $this->app->config->get('x_frame_options', 'sameorigin')))
+ $iframe = $this->framed || $this->env['framed'];
+ if (!headers_sent() && ($xframe = $this->app->config->get('x_frame_options', 'sameorigin'))) {
header('X-Frame-Options: ' . ($iframe && $xframe == 'deny' ? 'sameorigin' : $xframe));
+ }
// call super method
$this->_write($template, $this->config->get('skin_path'));
@@ -459,15 +464,15 @@ EOF;
*/
function parse($name = 'main', $exit = true, $write = true)
{
- $plugin = false;
- $realname = $name;
+ $plugin = false;
+ $realname = $name;
$this->template_name = $realname;
$temp = explode('.', $name, 2);
if (count($temp) > 1) {
- $plugin = $temp[0];
- $name = $temp[1];
- $skin_dir = $plugin . '/skins/' . $this->config->get('skin');
+ $plugin = $temp[0];
+ $name = $temp[1];
+ $skin_dir = $plugin . '/skins/' . $this->config->get('skin');
// apply skin search escalation list to plugin directory
$plugin_skin_paths = array();
@@ -564,27 +569,47 @@ EOF;
*
* @return string $out
*/
- protected function get_js_commands()
+ protected function get_js_commands(&$framed = null)
{
- $out = '';
if (!$this->framed && !empty($this->js_env)) {
- $out .= self::JS_OBJECT_NAME . '.set_env('.self::json_serialize($this->js_env).");\n";
+ $this->command('set_env', $this->js_env);
}
+
if (!empty($this->js_labels)) {
$this->command('add_label', $this->js_labels);
}
+
+ $out = '';
+ $parent_commands = 0;
+
foreach ($this->js_commands as $i => $args) {
$method = array_shift($args);
+ $parent = $this->framed || preg_match('/^parent\./', $method);
+
foreach ($args as $i => $arg) {
$args[$i] = self::json_serialize($arg);
}
- $parent = $this->framed || preg_match('/^parent\./', $method);
- $out .= sprintf(
- "%s.%s(%s);\n",
- ($parent ? 'if(window.parent && parent.'.self::JS_OBJECT_NAME.') parent.' : '') . self::JS_OBJECT_NAME,
- preg_replace('/^parent\./', '', $method),
- implode(',', $args)
- );
+
+ if ($parent) {
+ $parent_commands++;
+ $method = preg_replace('/^parent\./', '', $method);
+ $parent_prefix = 'if (window.parent && parent.' . self::JS_OBJECT_NAME . ') parent.';
+ $method = $parent_prefix . self::JS_OBJECT_NAME . '.' . $method;
+ }
+ else {
+ $method = self::JS_OBJECT_NAME . '.' . $method;
+ }
+
+ $out .= sprintf("%s(%s);\n", $method, implode(',', $args));
+ }
+
+ $framed = $parent_prefix && $parent_commands == count($this->js_commands);
+
+ // make the output more compact if all commands go to parent window
+ if ($framed) {
+ $out = "if (window.parent && parent." . self::JS_OBJECT_NAME . ") {\n"
+ . str_replace($parent_prefix, "\tparent.", $out)
+ . "}\n";
}
return $out;
@@ -600,13 +625,14 @@ EOF;
public function abs_url($str, $search_path = false)
{
if ($str[0] == '/') {
- if ($search_path && ($file_url = $this->get_skin_file($str, $skin_path)))
+ if ($search_path && ($file_url = $this->get_skin_file($str, $skin_path))) {
return $file_url;
+ }
return $this->base_path . $str;
}
- else
- return $str;
+
+ return $str;
}
/**
@@ -1322,15 +1348,10 @@ EOF;
$output = trim($templ);
if (empty($output)) {
- $output = $this->default_template;
+ $output = html::doctype('html5') . "\n" . $this->default_template;
$is_empty = true;
}
- // set default page title
- if (empty($this->pagetitle)) {
- $this->pagetitle = 'Roundcube Mail';
- }
-
// replace specialchars in content
$page_title = html::quote($this->pagetitle);
$page_header = '';
@@ -1478,7 +1499,7 @@ EOF;
*/
public function form_tag($attrib, $content = null)
{
- if ($this->framed || !empty($_REQUEST['_framed'])) {
+ if ($this->framed || $this->env['framed']) {
$hiddenfield = new html_hiddenfield(array('name' => '_framed', 'value' => '1'));
$hidden = $hiddenfield->show();
}
@@ -1518,7 +1539,7 @@ EOF;
// we already have a <form> tag
if ($attrib['form']) {
- if ($this->framed || !empty($_REQUEST['_framed']))
+ if ($this->framed || $this->env['framed'])
$hidden->add(array('name' => '_framed', 'value' => '1'));
return $hidden->show() . $content;
}
diff --git a/program/js/app.js b/program/js/app.js
index 46d748062..97b8192b2 100644
--- a/program/js/app.js
+++ b/program/js/app.js
@@ -77,7 +77,7 @@ function rcube_webmail()
});
// unload fix
- $(window).bind('beforeunload', function() { rcmail.unload = true; });
+ $(window).bind('beforeunload', function() { ref.unload = true; });
// set environment variable(s)
this.set_env = function(p, value)
@@ -241,7 +241,7 @@ function rcube_webmail()
// load messages
this.command('list');
- $(this.gui_objects.qsearchbox).val(this.env.search_text).focusin(function() { rcmail.message_list.blur(); });
+ $(this.gui_objects.qsearchbox).val(this.env.search_text).focusin(function() { ref.message_list.blur(); });
}
this.set_button_titles();
@@ -479,7 +479,7 @@ function rcube_webmail()
case 'login':
var input_user = $('#rcmloginuser');
- input_user.bind('keyup', function(e){ return rcmail.login_user_keyup(e); });
+ input_user.bind('keyup', function(e){ return ref.login_user_keyup(e); });
if (input_user.val() == '')
input_user.focus();
@@ -499,8 +499,8 @@ function rcube_webmail()
// display 'loading' message on form submit, lock submit button
$('form').submit(function () {
$('input[type=submit]', this).prop('disabled', true);
- rcmail.clear_messages();
- rcmail.display_message('', 'loading');
+ ref.clear_messages();
+ ref.display_message('', 'loading');
});
this.enable_command('login', true);
@@ -1376,7 +1376,7 @@ function rcube_webmail()
if (this.is_framed())
parent.rcmail.reload(delay);
else if (delay)
- setTimeout(function(){ rcmail.reload(); }, delay);
+ setTimeout(function() { ref.reload(); }, delay);
else if (window.location)
location.href = this.env.comm_path + (this.env.action ? '&_action='+this.env.action : '');
};
@@ -2525,8 +2525,8 @@ function rcube_webmail()
$('#'+r.id+' .leaf:first')
.attr('id', 'rcmexpando' + r.id)
.attr('class', (r.obj.style.display != 'none' ? 'expanded' : 'collapsed'))
- .bind('mousedown', {uid:r.uid, p:this},
- function(e) { return e.data.p.expand_message_row(e, e.data.uid); });
+ .bind('mousedown', {uid: r.uid},
+ function(e) { return ref.expand_message_row(e, e.data.uid); });
r.unread_children = 0;
roots.push(r);
@@ -3633,8 +3633,8 @@ function rcube_webmail()
else if ((ed = this.env.spellcheck) && ed.state)
active = ed.state != 'ready' && ed.state != 'no_error_found';
- if (rcmail.buttons.spellcheck)
- $('#'+rcmail.buttons.spellcheck[0].id)[active ? 'addClass' : 'removeClass']('selected');
+ if (this.buttons.spellcheck)
+ $('#'+this.buttons.spellcheck[0].id)[active ? 'addClass' : 'removeClass']('selected');
return active;
};
@@ -3709,12 +3709,13 @@ function rcube_webmail()
this.auto_save_start = function()
{
- if (this.env.draft_autosave)
+ if (this.env.draft_autosave) {
this.draft_autosave_submit = false;
this.save_timer = setTimeout(function(){
ref.draft_autosave_submit = true; // set auto-saved flag (#1489789)
ref.command("savedraft");
}, this.env.draft_autosave * 1000);
+ }
// save compose form content to local storage every 5 seconds
if (!this.local_save_timer && window.localStorage) {
@@ -4065,17 +4066,17 @@ function rcube_webmail()
} else if (this.contentWindow) {
d = this.contentWindow.document;
}
- content = d.childNodes[0].innerHTML;
+ content = d.childNodes[1].innerHTML;
} catch (err) {}
- if (!content.match(/add2attachment/) && (!bw.opera || (rcmail.env.uploadframe && rcmail.env.uploadframe == e.data.ts))) {
+ if (!content.match(/add2attachment/) && (!bw.opera || (ref.env.uploadframe && ref.env.uploadframe == e.data.ts))) {
if (!content.match(/display_message/))
- rcmail.display_message(rcmail.get_label('fileuploaderror'), 'error');
- rcmail.remove_from_attachment_list(e.data.ts);
+ ref.display_message(ref.get_label('fileuploaderror'), 'error');
+ ref.remove_from_attachment_list(e.data.ts);
}
// Opera hack: handle double onload
if (bw.opera)
- rcmail.env.uploadframe = e.data.ts;
+ ref.env.uploadframe = e.data.ts;
});
// display upload indicator and cancel button
@@ -4160,7 +4161,7 @@ function rcube_webmail()
this.upload_progress_start = function(action, name)
{
- setTimeout(function() { rcmail.http_request(action, {_progress: name}); },
+ setTimeout(function() { ref.http_request(action, {_progress: name}); },
this.env.upload_progress_time * 1000);
};
@@ -4230,7 +4231,7 @@ function rcube_webmail()
};
// build URL params for search
- this.search_params = function(search, filter, smods)
+ this.search_params = function(search, filter)
{
var n, url = {}, mods_arr = [],
mods = this.env.search_mods,
@@ -4249,7 +4250,7 @@ function rcube_webmail()
if (search) {
url._q = search;
- if (!smods && mods && this.message_list)
+ if (mods && this.message_list)
mods = mods[mbox] || mods['*'];
if (mods) {
@@ -4299,7 +4300,7 @@ function rcube_webmail()
this.set_searchmods = function(mods)
{
- var mbox = rcmail.env.mailbox,
+ var mbox = this.env.mailbox,
scope = this.env.search_scope || 'base';
if (scope == 'all')
@@ -5139,7 +5140,7 @@ function rcube_webmail()
if (!this.name_input) {
this.enable_command('list', 'listgroup', false);
this.name_input = $('<input>').attr('type', 'text').val(this.env.contactgroups['G'+this.env.source+this.env.group].name);
- this.name_input.bind('keydown', function(e){ return rcmail.add_input_keydown(e); });
+ this.name_input.bind('keydown', function(e) { return ref.add_input_keydown(e); });
this.env.group_renaming = true;
var link, li = this.get_folder_li('G'+this.env.source+this.env.group,'',true);
@@ -5180,7 +5181,7 @@ function rcube_webmail()
if (!this.name_input) {
this.name_input = $('<input>').attr('type', 'text').data('tt', type);
- this.name_input.bind('keydown', function(e){ return rcmail.add_input_keydown(e); });
+ this.name_input.bind('keydown', function(e) { return ref.add_input_keydown(e); });
this.name_input_li = $('<li>').addClass(type).append(this.name_input);
var ul, li;
@@ -5283,7 +5284,7 @@ function rcube_webmail()
var key = 'G'+prop.source+prop.id,
link = $('<a>').attr('href', '#')
.attr('rel', prop.source+':'+prop.id)
- .click(function() { return rcmail.command('listgroup', prop, this); })
+ .click(function() { return ref.command('listgroup', prop, this); })
.html(prop.name);
this.env.contactfolders[key] = this.env.contactgroups[key] = prop;
@@ -5318,7 +5319,7 @@ function rcube_webmail()
newnode.id = newkey;
newnode.html = $('<a>').attr('href', '#')
.attr('rel', prop.source+':'+prop.newid)
- .click(function() { return rcmail.command('listgroup', newprop, this); })
+ .click(function() { return ref.command('listgroup', newprop, this); })
.html(prop.name);
}
// update displayed group name
@@ -5495,7 +5496,7 @@ function rcube_webmail()
{
if (form && form.elements._photo.value) {
this.async_upload_form(form, 'upload-photo', function(e) {
- rcmail.set_busy(false, null, rcmail.file_upload_id);
+ ref.set_busy(false, null, ref.file_upload_id);
});
// display upload indicator
@@ -5560,7 +5561,7 @@ function rcube_webmail()
var key = 'S'+id,
link = $('<a>').attr('href', '#')
.attr('rel', id)
- .click(function() { return rcmail.command('listsearch', id, this); })
+ .click(function() { return ref.command('listsearch', id, this); })
.html(name),
prop = { name:name, id:id };
@@ -6144,14 +6145,14 @@ function rcube_webmail()
elm._command = cmd;
elm._id = prop.id;
if (prop.sel) {
- elm.onmousedown = function(e){ return rcmail.button_sel(this._command, this._id); };
- elm.onmouseup = function(e){ return rcmail.button_out(this._command, this._id); };
+ elm.onmousedown = function(e) { return ref.button_sel(this._command, this._id); };
+ elm.onmouseup = function(e) { return ref.button_out(this._command, this._id); };
if (preload)
new Image().src = prop.sel;
}
if (prop.over) {
- elm.onmouseover = function(e){ return rcmail.button_over(this._command, this._id); };
- elm.onmouseout = function(e){ return rcmail.button_out(this._command, this._id); };
+ elm.onmouseover = function(e) { return ref.button_over(this._command, this._id); };
+ elm.onmouseout = function(e) { return ref.button_out(this._command, this._id); };
if (preload)
new Image().src = prop.over;
}
@@ -6682,7 +6683,7 @@ function rcube_webmail()
$(elem).removeClass('show-headers').addClass('hide-headers');
$(this.gui_objects.all_headers_row).show();
- elem.onclick = function() { rcmail.command('hide-headers', '', elem); };
+ elem.onclick = function() { ref.command('hide-headers', '', elem); };
// fetch headers only once
if (!this.gui_objects.all_headers_box.innerHTML) {
@@ -6700,7 +6701,7 @@ function rcube_webmail()
$(elem).removeClass('hide-headers').addClass('show-headers');
$(this.gui_objects.all_headers_row).hide();
- elem.onclick = function() { rcmail.command('show-headers', '', elem); };
+ elem.onclick = function() { ref.command('show-headers', '', elem); };
};
// create folder selector popup, position and display it
@@ -7705,7 +7706,7 @@ function rcube_webmail()
$(elem).parent().find('.mailtoprotohandler-status').html(status);
}
else {
- $(elem).click(function() { rcmail.register_protocol_handler(name); return false; });
+ $(elem).click(function() { ref.register_protocol_handler(name); return false; });
}
}
};
@@ -7744,8 +7745,8 @@ function rcube_webmail()
{
var img = new Image();
- img.onload = function() { rcmail.env.browser_capabilities.tif = 1; };
- img.onerror = function() { rcmail.env.browser_capabilities.tif = 0; };
+ img.onload = function() { ref.env.browser_capabilities.tif = 1; };
+ img.onerror = function() { ref.env.browser_capabilities.tif = 0; };
img.src = 'program/resources/blank.tif';
};
diff --git a/program/js/treelist.js b/program/js/treelist.js
index 353eb6be7..85d8aa928 100644
--- a/program/js/treelist.js
+++ b/program/js/treelist.js
@@ -44,6 +44,7 @@ function rcube_treelist_widget(node, p)
scroll_delay: 500,
scroll_step: 5,
scroll_speed: 20,
+ save_state: false,
check_droptarget: function(node){ return !node.virtual }
}, p || {});
@@ -52,6 +53,8 @@ function rcube_treelist_widget(node, p)
indexbyid = {},
selection = null,
drag_active = false,
+ search_active = false,
+ last_search = '',
box_coords = {},
item_coords = [],
autoexpand_timer,
@@ -59,6 +62,9 @@ function rcube_treelist_widget(node, p)
body_scroll_top = 0,
list_scroll_top = 0,
scroll_timer,
+ searchfield,
+ tree_state,
+ list_id = (container.attr('id') || p.id_prefix || '0'),
me = this;
@@ -77,6 +83,7 @@ function rcube_treelist_widget(node, p)
this.insert = insert;
this.remove = remove;
this.get_item = get_item;
+ this.get_node = get_node;
this.get_selection = get_selection;
/////// startup code (constructor)
@@ -105,6 +112,43 @@ function rcube_treelist_widget(node, p)
}
});
+ // activate search function
+ if (p.searchbox) {
+ searchfield = $(p.searchbox).on('keyup', function(e) {
+ var key = rcube_event.get_keycode(e),
+ mod = rcube_event.get_modifier(e);
+
+ switch (key) {
+ case 9: // tab
+ break;
+
+ case 13: // enter
+ search(this.value, true);
+ return rcube_event.cancel(e);
+
+ case 27: // escape
+ reset_search();
+ break;
+
+ case 38: // arrow up
+ case 37: // left
+ case 39: // right
+ case 40: // arrow down
+ return; // ignore arrow keys
+
+ default:
+ search(this.value, false);
+ break;
+ }
+ }).attr('autocomplete', 'off');
+
+ // find the reset button for this search field
+ searchfield.parent().find('a.reset').click(function(e) {
+ reset_search();
+ return false;
+ })
+ }
+
/////// private methods
@@ -126,6 +170,7 @@ function rcube_treelist_widget(node, p)
}
me.triggerEvent(node.collapsed ? 'collapse' : 'expand', node);
+ save_state(id, node.collapsed);
}
}
@@ -180,9 +225,17 @@ function rcube_treelist_widget(node, p)
/**
* Return the DOM element of the list item with the given ID
*/
- function get_item(id)
+ function get_node(id)
{
- return id2dom(id).get(0);
+ return indexbyid[id];
+ }
+
+ /**
+ * Return the DOM element of the list item with the given ID
+ */
+ function get_item(id, real)
+ {
+ return id2dom(id, real).get(0);
}
/**
@@ -191,13 +244,26 @@ function rcube_treelist_widget(node, p)
function insert(node, parent_id, sort)
{
var li, parent_li,
- parent_node = parent_id ? indexbyid[parent_id] : null;
+ parent_node = parent_id ? indexbyid[parent_id] : null
+ search_ = search_active;
+
+ // ignore, already exists
+ if (indexbyid[node.id]) {
+ return;
+ }
+
+ // apply saved state
+ state = get_state(node.id, node.collapsed);
+ if (state !== undefined) {
+ node.collapsed = state;
+ }
// insert as child of an existing node
if (parent_node) {
if (!parent_node.children)
parent_node.children = [];
+ search_active = false;
parent_node.children.push(node);
parent_li = id2dom(parent_id);
@@ -210,6 +276,21 @@ function rcube_treelist_widget(node, p)
// append new node to parent's child list
li = render_node(node, parent_li.children('ul').first());
}
+
+ // list is in search mode
+ if (search_) {
+ search_active = search_;
+
+ // add clone to current search results (top level)
+ if (!li.is(':visible')) {
+ $('<li>')
+ .attr('id', li.attr('id') + '--xsR')
+ .attr('class', li.attr('class'))
+ .addClass('searchresult__')
+ .append(li.children().first().clone(true, true))
+ .appendTo(container);
+ }
+ }
}
// insert at top level
else {
@@ -276,7 +357,7 @@ function rcube_treelist_widget(node, p)
if (sibling) {
li.insertAfter(sibling);
}
- else if (first.id != myid) {
+ else if (first && first.id != myid) {
li.insertBefore(first);
}
@@ -292,7 +373,7 @@ function rcube_treelist_widget(node, p)
var node, li;
if (node = indexbyid[id]) {
- li = id2dom(id);
+ li = id2dom(id, true);
li.remove();
node.deleted = true;
@@ -335,6 +416,80 @@ function rcube_treelist_widget(node, p)
drag_active = false;
container.html('');
+
+ reset_search();
+ }
+
+ /**
+ *
+ */
+ function search(q, enter)
+ {
+ q = String(q).toLowerCase();
+
+ if (!q.length)
+ return reset_search();
+ else if (q == last_search && !enter)
+ return 0;
+
+ var hits = [];
+ var search_tree = function(items) {
+ $.each(items, function(i, node) {
+ var li, sli;
+ if (!node.virtual && !node.deleted && String(node.text).toLowerCase().indexOf(q) >= 0 && hits.indexOf(node.id) < 0) {
+ li = id2dom(node.id);
+ sli = $('<li>')
+ .attr('id', li.attr('id') + '--xsR')
+ .attr('class', li.attr('class'))
+ .addClass('searchresult__')
+ .append(li.children().first().clone(true, true))
+ .appendTo(container);
+ hits.push(node.id);
+ }
+
+ if (node.children && node.children.length) {
+ search_tree(node.children);
+ }
+ });
+ };
+
+ // reset old search results
+ if (search_active) {
+ $(container).children('li.searchresult__').remove();
+ search_active = false;
+ }
+
+ // hide all list items
+ $(container).children('li').hide().removeClass('selected');
+
+ // search recursively in tree (to keep sorting order)
+ search_tree(data);
+ search_active = true;
+ last_search = q;
+
+ me.triggerEvent('search', { query: q, last: last_search, count: hits.length, ids: hits, execute: enter||false });
+
+ return hits.count;
+ }
+
+ /**
+ *
+ */
+ function reset_search()
+ {
+ if (searchfield)
+ searchfield.val('');
+
+ $(container).children('li.searchresult__').remove();
+ $(container).children('li').show();
+
+ search_active = false;
+
+ me.triggerEvent('search', { query: false, last: last_search });
+ last_search = '';
+
+ if (selection)
+ select(selection);
}
/**
@@ -378,6 +533,9 @@ function rcube_treelist_widget(node, p)
else if (typeof node.html == 'object')
li.append(node.html);
+ if (!node.text)
+ node.text = li.children().first().text();
+
if (node.virtual)
li.addClass('virtual');
if (node.id == selection)
@@ -406,12 +564,13 @@ function rcube_treelist_widget(node, p)
{
var result = [];
ul.children('li').each(function(i,e){
- var li = $(e), sublist = li.children('ul');
+ var state, li = $(e), sublist = li.children('ul');
var node = {
id: dom2id(li),
- classes: li.attr('class').split(' '),
+ classes: String(li.attr('class')).split(' '),
virtual: li.hasClass('virtual'),
html: li.children().first().get(0).outerHTML,
+ text: li.children().first().text(),
children: walk_list(sublist)
}
@@ -420,6 +579,16 @@ function rcube_treelist_widget(node, p)
}
if (node.children.length) {
node.collapsed = sublist.css('display') == 'none';
+
+ // apply saved state
+ state = get_state(node.id, node.collapsed);
+ if (state !== undefined) {
+ node.collapsed = state;
+ sublist[(state?'hide':'show')]();
+ }
+
+ if (!li.children('div.treetoggle').length)
+ $('<div class="treetoggle '+(node.collapsed ? 'collapsed' : 'expanded') + '">&nbsp;</div>').appendTo(li);
}
if (li.hasClass('selected')) {
selection = node.id;
@@ -450,17 +619,18 @@ function rcube_treelist_widget(node, p)
*/
function dom2id(li)
{
- var domid = li.attr('id').replace(new RegExp('^' + (p.id_prefix) || '%'), '');
+ var domid = li.attr('id').replace(new RegExp('^' + (p.id_prefix) || '%'), '').replace(/--xsR$/, '');
return p.id_decode ? p.id_decode(domid) : domid;
}
/**
* Get the <li> element for the given node ID
*/
- function id2dom(id)
+ function id2dom(id, real)
{
- var domid = p.id_encode ? p.id_encode(id) : id;
- return $('#' + p.id_prefix + domid);
+ var domid = p.id_encode ? p.id_encode(id) : id,
+ suffix = search_active && !real ? '--xsR' : '';
+ return $('#' + p.id_prefix + domid + suffix, container);
}
/**
@@ -476,6 +646,40 @@ function rcube_treelist_widget(node, p)
scroller.scrollTop(rel_offset + current_offset);
}
+ /**
+ * Save node collapse state to localStorage
+ */
+ function save_state(id, collapsed)
+ {
+ if (p.save_state && window.rcmail) {
+ var key = 'treelist-' + list_id;
+ if (!tree_state) {
+ tree_state = rcmail.local_storage_get_item(key, {});
+ }
+
+ if (tree_state[id] != collapsed) {
+ tree_state[id] = collapsed;
+ rcmail.local_storage_set_item(key, tree_state);
+ }
+ }
+ }
+
+ /**
+ * Read node collapse state from localStorage
+ */
+ function get_state(id)
+ {
+ if (p.save_state && window.rcmail) {
+ if (!tree_state) {
+ tree_state = rcmail.local_storage_get_item('treelist-' + list_id, {});
+ }
+ return tree_state[id];
+ }
+
+ return undefined;
+ }
+
+
///// drag & drop support
/**
diff --git a/program/lib/Mail/mime.php b/program/lib/Mail/mime.php
index e079af7e9..50297dd3e 100644
--- a/program/lib/Mail/mime.php
+++ b/program/lib/Mail/mime.php
@@ -491,13 +491,13 @@ class Mail_mime
* returns it during the build process.
*
* @param mixed &$obj The object to add the part to, or
- * null if a new object is to be created.
+ * anything else if a new object is to be created.
* @param string $text The text to add.
*
* @return object The text mimePart object
* @access private
*/
- function &_addTextPart(&$obj = null, $text = '')
+ function &_addTextPart(&$obj, $text = '')
{
$params['content_type'] = 'text/plain';
$params['encoding'] = $this->_build_params['text_encoding'];
@@ -518,12 +518,12 @@ class Mail_mime
* returns it during the build process.
*
* @param mixed &$obj The object to add the part to, or
- * null if a new object is to be created.
+ * anything else if a new object is to be created.
*
* @return object The html mimePart object
* @access private
*/
- function &_addHtmlPart(&$obj = null)
+ function &_addHtmlPart(&$obj)
{
$params['content_type'] = 'text/html';
$params['encoding'] = $this->_build_params['html_encoding'];
@@ -563,12 +563,12 @@ class Mail_mime
* the build process.
*
* @param mixed &$obj The object to add the part to, or
- * null if a new object is to be created.
+ * anything else if a new object is to be created.
*
* @return object The multipart/mixed mimePart object
* @access private
*/
- function &_addAlternativePart(&$obj = null)
+ function &_addAlternativePart(&$obj)
{
$params['content_type'] = 'multipart/alternative';
$params['eol'] = $this->_build_params['eol'];
@@ -588,12 +588,12 @@ class Mail_mime
* the build process.
*
* @param mixed &$obj The object to add the part to, or
- * null if a new object is to be created
+ * anything else if a new object is to be created
*
* @return object The multipart/mixed mimePart object
* @access private
*/
- function &_addRelatedPart(&$obj = null)
+ function &_addRelatedPart(&$obj)
{
$params['content_type'] = 'multipart/related';
$params['eol'] = $this->_build_params['eol'];
@@ -878,11 +878,11 @@ class Mail_mime
$this->_checkParams();
- $null = null;
- $attachments = count($this->_parts) ? true : false;
- $html_images = count($this->_html_images) ? true : false;
- $html = strlen($this->_htmlbody) ? true : false;
- $text = (!$html && strlen($this->_txtbody)) ? true : false;
+ $null = -1;
+ $attachments = count($this->_parts) > 0;
+ $html_images = count($this->_html_images) > 0;
+ $html = strlen($this->_htmlbody) > 0;
+ $text = !$html && strlen($this->_txtbody);
switch (true) {
case $text && !$attachments:
@@ -991,7 +991,6 @@ class Mail_mime
$this->_addAttachmentPart($message, $this->_parts[$i]);
}
break;
-
}
if (!isset($message)) {
diff --git a/program/lib/Mail/mimePart.php b/program/lib/Mail/mimePart.php
index c6e9f4aa8..93e891bc6 100644
--- a/program/lib/Mail/mimePart.php
+++ b/program/lib/Mail/mimePart.php
@@ -839,7 +839,7 @@ class Mail_mimePart
// Simple e-mail address regexp
$email_regexp = '([^\s<]+|("[^\r\n"]+"))@\S+';
- $parts = Mail_mimePart::_explodeQuotedString($separator, $value);
+ $parts = Mail_mimePart::_explodeQuotedString("[\t$separator]", $value);
$value = '';
foreach ($parts as $part) {
@@ -850,7 +850,7 @@ class Mail_mimePart
continue;
}
if ($value) {
- $value .= $separator==',' ? $separator.' ' : ' ';
+ $value .= $separator == ',' ? $separator . ' ' : ' ';
} else {
$value = $name . ': ';
}
@@ -869,7 +869,7 @@ class Mail_mimePart
// check if phrase requires quoting
if ($word) {
// non-ASCII: require encoding
- if (preg_match('#([\x80-\xFF]){1}#', $word)) {
+ if (preg_match('#([^\s\x21-\x7E]){1}#', $word)) {
if ($word[0] == '"' && $word[strlen($word)-1] == '"') {
// de-quote quoted-string, encoding changes
// string to atom
@@ -908,11 +908,10 @@ class Mail_mimePart
$value = preg_replace(
'/^'.$name.':('.preg_quote($eol, '/').')* /', '', $value
);
-
} else {
// Unstructured header
// non-ASCII: require encoding
- if (preg_match('#([\x80-\xFF]){1}#', $value)) {
+ if (preg_match('#([^\s\x21-\x7E]){1}#', $value)) {
if ($value[0] == '"' && $value[strlen($value)-1] == '"') {
// de-quote quoted-string, encoding changes
// string to atom
diff --git a/program/lib/Roundcube/rcube.php b/program/lib/Roundcube/rcube.php
index f2aeda7f0..d618fb64d 100644
--- a/program/lib/Roundcube/rcube.php
+++ b/program/lib/Roundcube/rcube.php
@@ -1132,6 +1132,11 @@ class rcube
return true;
}
+ // add session ID to the log
+ if ($sess = session_id()) {
+ $line = '<' . substr($sess, 0, 8) . '> ' . $line;
+ }
+
if ($log_driver == 'syslog') {
$prio = $name == 'errors' ? LOG_ERR : LOG_INFO;
syslog($prio, $line);
diff --git a/program/lib/Roundcube/rcube_html2text.php b/program/lib/Roundcube/rcube_html2text.php
index 8628371d7..499c4b05c 100644
--- a/program/lib/Roundcube/rcube_html2text.php
+++ b/program/lib/Roundcube/rcube_html2text.php
@@ -423,7 +423,7 @@ class rcube_html2text
// Variables used for building the link list
$this->_link_list = array();
- $text = trim(stripslashes($this->html));
+ $text = $this->html;
// Convert HTML to TXT
$this->_converter($text);
diff --git a/program/lib/Roundcube/rcube_imap.php b/program/lib/Roundcube/rcube_imap.php
index 4204354b3..4b32c466b 100644
--- a/program/lib/Roundcube/rcube_imap.php
+++ b/program/lib/Roundcube/rcube_imap.php
@@ -1489,23 +1489,39 @@ class rcube_imap extends rcube_storage
* Invoke search request to IMAP server
*
* @param string $folder Folder name to search in
- * @param string $str Search criteria
+ * @param string $search Search criteria
* @param string $charset Search charset
* @param string $sort_field Header field to sort by
+ *
* @return rcube_result_index Search result object
* @todo: Search criteria should be provided in non-IMAP format, eg. array
*/
- public function search($folder='', $str='ALL', $charset=NULL, $sort_field=NULL)
+ public function search($folder = '', $search = 'ALL', $charset = null, $sort_field = null)
{
- if (!$str) {
- $str = 'ALL';
+ if (!$search) {
+ $search = 'ALL';
}
- // multi-folder search
- if (is_array($folder) && count($folder) > 1 && $str != 'ALL') {
- new rcube_result_index; // trigger autoloader and make these classes available for threaded context
- new rcube_result_thread;
+ if ((is_array($folder) && empty($folder)) || (!is_array($folder) && !strlen($folder))) {
+ $folder = $this->folder;
+ }
+
+ $plugin = rcube::get_instance()->plugins->exec_hook('imap_search_before', array(
+ 'folder' => $folder,
+ 'search' => $search,
+ 'charset' => $charset,
+ 'sort_field' => $sort_field,
+ 'threading' => $this->threading,
+ ));
+
+ $folder = $plugin['folder'];
+ $search = $plugin['search'];
+ $charset = $plugin['charset'];
+ $sort_field = $plugin['sort_field'];
+ $results = $plugin['result'];
+ // multi-folder search
+ if (!$results && is_array($folder) && count($folder) > 1 && $search != 'ALL') {
// connect IMAP to have all the required classes and settings loaded
$this->check_connection();
@@ -1518,29 +1534,28 @@ class rcube_imap extends rcube_storage
$searcher->set_timelimit(60);
// continue existing incomplete search
- if (!empty($this->search_set) && $this->search_set->incomplete && $str == $this->search_string) {
+ if (!empty($this->search_set) && $this->search_set->incomplete && $search == $this->search_string) {
$searcher->set_results($this->search_set);
}
// execute the search
$results = $searcher->exec(
$folder,
- $str,
+ $search,
$charset ? $charset : $this->default_charset,
$sort_field && $this->get_capability('SORT') ? $sort_field : null,
$this->threading
);
}
- else {
- $folder = is_array($folder) ? $folder[0] : $folder;
- if (!strlen($folder)) {
- $folder = $this->folder;
- }
- $results = $this->search_index($folder, $str, $charset, $sort_field);
+ else if (!$results) {
+ $folder = is_array($folder) ? $folder[0] : $folder;
+ $search = is_array($search) ? $search[$folder] : $search;
+ $results = $this->search_index($folder, $search, $charset, $sort_field);
}
- $this->set_search_set(array($str, $results, $charset, $sort_field,
- $this->threading || $this->search_sorted ? true : false));
+ $sorted = $this->threading || $this->search_sorted || $plugin['search_sorted'] ? true : false;
+
+ $this->set_search_set(array($search, $results, $charset, $sort_field, $sorted));
return $results;
}
diff --git a/program/lib/Roundcube/rcube_imap_search.php b/program/lib/Roundcube/rcube_imap_search.php
index 0c44daf1b..365d78f76 100644
--- a/program/lib/Roundcube/rcube_imap_search.php
+++ b/program/lib/Roundcube/rcube_imap_search.php
@@ -29,7 +29,7 @@ class rcube_imap_search
{
public $options = array();
- protected $jobs = array();
+ protected $jobs = array();
protected $timelimit = 0;
protected $results;
protected $conn;
@@ -40,7 +40,7 @@ class rcube_imap_search
public function __construct($options, $conn)
{
$this->options = $options;
- $this->conn = $conn;
+ $this->conn = $conn;
}
/**
@@ -54,7 +54,7 @@ class rcube_imap_search
*/
public function exec($folders, $str, $charset = null, $sort_field = null, $threading=null)
{
- $start = floor(microtime(true));
+ $start = floor(microtime(true));
$results = new rcube_result_multifolder($folders);
// start a search job for every folder to search in
@@ -65,7 +65,8 @@ class rcube_imap_search
$results->add($result);
}
else {
- $job = new rcube_imap_search_job($folder, $str, $charset, $sort_field, $threading);
+ $search = is_array($str) && $str[$folder] ? $str[$folder] : $str;
+ $job = new rcube_imap_search_job($folder, $search, $charset, $sort_field, $threading);
$job->worker = $this;
$this->jobs[] = $job;
}
@@ -129,11 +130,11 @@ class rcube_imap_search_job /* extends Stackable */
public function __construct($folder, $str, $charset = null, $sort_field = null, $threading=false)
{
- $this->folder = $folder;
- $this->search = $str;
- $this->charset = $charset;
+ $this->folder = $folder;
+ $this->search = $str;
+ $this->charset = $charset;
$this->sort_field = $sort_field;
- $this->threading = $threading;
+ $this->threading = $threading;
$this->result = new rcube_result_index($folder);
$this->result->incomplete = true;
@@ -150,9 +151,8 @@ class rcube_imap_search_job /* extends Stackable */
protected function search_index()
{
$criteria = $this->search;
- $charset = $this->charset;
-
- $imap = $this->worker->get_imap();
+ $charset = $this->charset;
+ $imap = $this->worker->get_imap();
if (!$imap->connected()) {
trigger_error("No IMAP connection for $this->folder", E_USER_WARNING);
@@ -228,7 +228,4 @@ class rcube_imap_search_job /* extends Stackable */
{
return $this->result;
}
-
}
-
-
diff --git a/program/lib/Roundcube/rcube_ldap_generic.php b/program/lib/Roundcube/rcube_ldap_generic.php
index f1048ef39..f6f9884d8 100644
--- a/program/lib/Roundcube/rcube_ldap_generic.php
+++ b/program/lib/Roundcube/rcube_ldap_generic.php
@@ -190,6 +190,9 @@ class rcube_ldap_generic
if (isset($this->config['referrals']))
ldap_set_option($lc, LDAP_OPT_REFERRALS, $this->config['referrals']);
+
+ if (isset($this->config['dereference']))
+ ldap_set_option($lc, LDAP_OPT_DEREF, $this->config['dereference']);
}
else {
$this->_debug("S: NOT OK");
diff --git a/program/lib/Roundcube/rcube_result_multifolder.php b/program/lib/Roundcube/rcube_result_multifolder.php
index 4bbd2188d..786ee85f6 100644
--- a/program/lib/Roundcube/rcube_result_multifolder.php
+++ b/program/lib/Roundcube/rcube_result_multifolder.php
@@ -26,16 +26,16 @@
*/
class rcube_result_multifolder
{
- public $multi = true;
- public $sets = array();
+ public $multi = true;
+ public $sets = array();
public $incomplete = false;
public $folder;
- protected $meta = array();
- protected $index = array();
+ protected $meta = array();
+ protected $index = array();
protected $folders = array();
+ protected $order = 'ASC';
protected $sorting;
- protected $order = 'ASC';
/**
@@ -44,7 +44,7 @@ class rcube_result_multifolder
public function __construct($folders = array())
{
$this->folders = $folders;
- $this->meta = array('count' => 0);
+ $this->meta = array('count' => 0);
}
@@ -74,7 +74,8 @@ class rcube_result_multifolder
// append UIDs to global index
$folder = $result->get_parameters('MAILBOX');
- $index = array_map(function($uid) use ($folder) { return $uid . '-' . $folder; }, $result->get());
+ $index = array_map(function($uid) use ($folder) { return $uid . '-' . $folder; }, $result->get());
+
$this->index = array_merge($this->index, $index);
}
@@ -89,7 +90,7 @@ class rcube_result_multifolder
}
$this->sorting = $sort_field;
- $this->order = $sort_order;
+ $this->order = $sort_order;
}
/**
@@ -150,8 +151,10 @@ class rcube_result_multifolder
if ($this->order != $set->get_parameters('ORDER')) {
$set->revert();
}
+
$folder = $set->get_parameters('MAILBOX');
- $index = array_map(function($uid) use ($folder) { return $uid . '-' . $folder; }, $set->get());
+ $index = array_map(function($uid) use ($folder) { return $uid . '-' . $folder; }, $set->get());
+
$this->index = array_merge($this->index, $index);
}
}
@@ -171,6 +174,7 @@ class rcube_result_multifolder
if (!empty($this->folder)) {
$msgid .= '-' . $this->folder;
}
+
return array_search($msgid, $this->index);
}
@@ -188,6 +192,7 @@ class rcube_result_multifolder
if ($set->get_parameters('MAILBOX') == $folder) {
$set->filter($ids);
}
+
$this->meta['count'] += $set->count();
}
}
@@ -267,8 +272,8 @@ class rcube_result_multifolder
public function get_parameters($param=null)
{
$params = array(
- 'SORT' => $this->sorting,
- 'ORDER' => $this->order,
+ 'SORT' => $this->sorting,
+ 'ORDER' => $this->order,
'MAILBOX' => $this->folders,
);
diff --git a/program/lib/Roundcube/rcube_storage.php b/program/lib/Roundcube/rcube_storage.php
index 69d6d2fae..c1293961c 100644
--- a/program/lib/Roundcube/rcube_storage.php
+++ b/program/lib/Roundcube/rcube_storage.php
@@ -152,6 +152,19 @@ abstract class rcube_storage
/**
+ * Get connection/class option
+ *
+ * @param string $name Option name
+ *
+ * @param mixed Option value
+ */
+ public function get_option($name)
+ {
+ return $this->options[$name];
+ }
+
+
+ /**
* Activate/deactivate debug mode.
*
* @param boolean $dbg True if conversation with the server should be logged
diff --git a/program/lib/Roundcube/rcube_string_replacer.php b/program/lib/Roundcube/rcube_string_replacer.php
index 77b91d18b..ce61e5367 100644
--- a/program/lib/Roundcube/rcube_string_replacer.php
+++ b/program/lib/Roundcube/rcube_string_replacer.php
@@ -42,7 +42,7 @@ class rcube_string_replacer
// Support unicode/punycode in top-level domain part
$utf_domain = '[^?&@"\'\\/()<>\s\r\t\n]+\\.?([^\\x00-\\x2f\\x3b-\\x40\\x5b-\\x60\\x7b-\\x7f]{2,}|xn--[a-zA-Z0-9]{2,})';
$url1 = '.:;,';
- $url2 = 'a-zA-Z0-9%=#$@+?|!&\\/_~\\[\\]\\(\\){}\*-';
+ $url2 = 'a-zA-Z0-9%=#$@+?|!&\\/_~\\[\\]\\(\\){}\*\x80-\xFE-';
$this->link_pattern = "/([\w]+:\/\/|\W[Ww][Ww][Ww]\.|^[Ww][Ww][Ww]\.)($utf_domain([$url1]*[$url2]+)*)/";
$this->mailto_pattern = "/("
diff --git a/program/lib/Roundcube/rcube_text2html.php b/program/lib/Roundcube/rcube_text2html.php
index 363f1b21f..46c2b7e9a 100644
--- a/program/lib/Roundcube/rcube_text2html.php
+++ b/program/lib/Roundcube/rcube_text2html.php
@@ -127,10 +127,8 @@ class rcube_text2html
*/
protected function _convert()
{
- $text = stripslashes($this->text);
-
// Convert TXT to HTML
- $this->html = $this->_converter($text);
+ $this->html = $this->_converter($this->text);
$this->_converted = true;
}
diff --git a/program/steps/addressbook/save.inc b/program/steps/addressbook/save.inc
index 94556f96b..7451f433b 100644
--- a/program/steps/addressbook/save.inc
+++ b/program/steps/addressbook/save.inc
@@ -165,6 +165,10 @@ if (!empty($cid)) {
$a_js_cols[] = rcube::Q((string)$record[$col]);
}
+ // performance: unset some big data items we don't need here
+ $record = array_intersect_key($record, array('ID' => 1,'email' => 1,'name' => 1));
+ $record['_type'] = 'person';
+
// update the changed col in list
$OUTPUT->command('parent.update_contact_row', $cid, $a_js_cols, $newcid, $source, $record);
diff --git a/program/steps/mail/check_recent.inc b/program/steps/mail/check_recent.inc
index cfdcda605..70f4c03a6 100644
--- a/program/steps/mail/check_recent.inc
+++ b/program/steps/mail/check_recent.inc
@@ -85,7 +85,7 @@ foreach ($a_mailboxes as $mbox_name) {
$OUTPUT->command('set_quota', $RCMAIL->quota_content());
}
- $OUTPUT->set_env('exists', $RCMAIL->storage->count($mbox_name, 'EXISTS'));
+ $OUTPUT->set_env('exists', $RCMAIL->storage->count($mbox_name, 'EXISTS', true));
// "No-list" mode, don't get messages
if (empty($_POST['_list'])) {
@@ -146,7 +146,7 @@ foreach ($a_mailboxes as $mbox_name) {
// set trash folder state
if ($mbox_name === $trash) {
- $OUTPUT->command('set_trash_count', $RCMAIL->storage->count($mbox_name, 'EXISTS'));
+ $OUTPUT->command('set_trash_count', $RCMAIL->storage->count($mbox_name, 'EXISTS', true));
}
}
diff --git a/program/steps/mail/list.inc b/program/steps/mail/list.inc
index c4a6df57b..929dda299 100644
--- a/program/steps/mail/list.inc
+++ b/program/steps/mail/list.inc
@@ -93,7 +93,7 @@ rcmail_send_unread_count($mbox_name, !empty($_REQUEST['_refresh']), $unseen);
// update message count display
$pages = ceil($count/$RCMAIL->storage->get_pagesize());
-$exists = $RCMAIL->storage->count($mbox_name, 'EXISTS');
+$exists = $RCMAIL->storage->count($mbox_name, 'EXISTS', true);
$OUTPUT->set_env('messagecount', $count);
$OUTPUT->set_env('pagecount', $pages);
diff --git a/program/steps/mail/move_del.inc b/program/steps/mail/move_del.inc
index c29985875..d98d49d1f 100644
--- a/program/steps/mail/move_del.inc
+++ b/program/steps/mail/move_del.inc
@@ -166,7 +166,7 @@ else {
$OUTPUT->command('set_trash_count', $exists);
}
else if ($target !== null && $target === $trash) {
- $OUTPUT->command('set_trash_count', $RCMAIL->storage->count($trash, 'EXISTS'));
+ $OUTPUT->command('set_trash_count', $RCMAIL->storage->count($trash, 'EXISTS', true));
}
}
diff --git a/program/steps/mail/search.inc b/program/steps/mail/search.inc
index e610e9137..4aa22e14b 100644
--- a/program/steps/mail/search.inc
+++ b/program/steps/mail/search.inc
@@ -162,9 +162,11 @@ if (!empty($result_h)) {
// remember last HIGHESTMODSEQ value (if supported)
// we need it for flag updates in check-recent
- $data = $RCMAIL->storage->folder_data($mbox_name);
- if (!empty($data['HIGHESTMODSEQ'])) {
- $_SESSION['list_mod_seq'] = $data['HIGHESTMODSEQ'];
+ if ($mbox !== null) {
+ $data = $RCMAIL->storage->folder_data($mbox);
+ if (!empty($data['HIGHESTMODSEQ'])) {
+ $_SESSION['list_mod_seq'] = $data['HIGHESTMODSEQ'];
+ }
}
}
// handle IMAP errors (e.g. #1486905)
@@ -189,7 +191,7 @@ $OUTPUT->set_env('search_filter', $_SESSION['search_filter']);
$OUTPUT->set_env('threading', $RCMAIL->storage->get_threading());
$OUTPUT->set_env('messagecount', $count);
$OUTPUT->set_env('pagecount', ceil($count/$RCMAIL->storage->get_pagesize()));
-$OUTPUT->set_env('exists', $RCMAIL->storage->count($mbox_name, 'EXISTS'));
+$OUTPUT->set_env('exists', $mbox === null ? 0 : $RCMAIL->storage->count($mbox, 'EXISTS'));
$OUTPUT->command('set_rowcount', rcmail_get_messagecount_text($count, 1), $mbox);
$OUTPUT->set_pagetitle($RCMAIL->gettext(array('name' => 'searchfor', 'vars' => array('q' => $str))));
$OUTPUT->send();
diff --git a/program/steps/utils/html2text.inc b/program/steps/utils/html2text.inc
index 58df34bd0..f6e2bec4d 100644
--- a/program/steps/utils/html2text.inc
+++ b/program/steps/utils/html2text.inc
@@ -21,6 +21,11 @@
$html = stream_get_contents(fopen('php://input', 'r'));
+// strip slashes if magic_quotes enabled
+if (get_magic_quotes_gpc() || get_magic_quotes_runtime()) {
+ $html = stripslashes($html);
+}
+
// Replace emoticon images with its text representation
$html = $RCMAIL->replace_emoticons($html);
diff --git a/program/steps/utils/text2html.inc b/program/steps/utils/text2html.inc
index 167243694..56d15fa19 100644
--- a/program/steps/utils/text2html.inc
+++ b/program/steps/utils/text2html.inc
@@ -21,6 +21,11 @@
$text = stream_get_contents(fopen('php://input', 'r'));
+// strip slashes if magic_quotes enabled
+if (get_magic_quotes_gpc() || get_magic_quotes_runtime()) {
+ $html = stripslashes($html);
+}
+
$converter = new rcube_text2html($text, false, array('wrap' => true));
header('Content-Type: text/html; charset=' . RCUBE_CHARSET);
diff --git a/skins/classic/templates/about.html b/skins/classic/templates/about.html
index 429dfcf5f..36fb65739 100644
--- a/skins/classic/templates/about.html
+++ b/skins/classic/templates/about.html
@@ -20,7 +20,7 @@
<div id="license">
<roundcube:object name="aboutcontent" />
<h2 class="sysname">Roundcube Webmail <roundcube:object name="version" /></h2>
-<p class="copyright">Copyright &copy; 2005-2013, The Roundcube Dev Team</p>
+<p class="copyright">Copyright &copy; 2005-2014, 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/>
diff --git a/skins/larry/images/buttons.png b/skins/larry/images/buttons.png
index df4b3afd5..21298ab59 100644
--- a/skins/larry/images/buttons.png
+++ b/skins/larry/images/buttons.png
Binary files differ
diff --git a/skins/larry/images/listicons.png b/skins/larry/images/listicons.png
index 551639876..77a263aef 100644
--- a/skins/larry/images/listicons.png
+++ b/skins/larry/images/listicons.png
Binary files differ
diff --git a/skins/larry/mail.css b/skins/larry/mail.css
index 8306afd9f..329acb205 100644
--- a/skins/larry/mail.css
+++ b/skins/larry/mail.css
@@ -38,6 +38,9 @@
left: 0;
right: 0;
bottom: 0px;
+}
+
+html.ie #mailview-top {
overflow: visible; /* fixes display issues of fixed list header in IE */
}
@@ -92,6 +95,7 @@ html>/**/body #messagelist {
background: -ms-linear-gradient(top, #ebebeb 0%, #c6c6c6 100%);
background: linear-gradient(top, #ebebeb 0%, #c6c6c6 100%);
border-radius: 0 0 4px 4px;
+ white-space: nowrap;
}
#messagelistfooter.rightalign {
@@ -1271,23 +1275,6 @@ div.message-partheaders .headers-table td.header {
bottom: 0;
}
-#composequicksearch {
- position: relative;
- padding: 4px;
- background: #c7e3ef;
-}
-
-#composequicksearch .searchbox input {
- width: 100%;
- height: 26px;
- -moz-box-sizing: border-box;
- box-sizing: border-box;
-}
-
-#composequicksearch #searchmenulink {
- width: 15px;
-}
-
#compose-contacts #directorylist {
border-bottom: 4px solid #c7e3ef;
}
diff --git a/skins/larry/styles.css b/skins/larry/styles.css
index 1baca0963..0ddb29653 100644
--- a/skins/larry/styles.css
+++ b/skins/larry/styles.css
@@ -433,11 +433,16 @@ a.iconbutton.disabled {
cursor: default;
}
+a.iconbutton.searchicon,
a.iconbutton.searchoptions {
width: 24px;
background-position: -2px -317px;
}
+a.iconbutton.searchicon {
+ width: 15px;
+}
+
a.iconbutton.reset {
width: 24px;
background-position: -25px -317px;
@@ -1857,6 +1862,7 @@ ul.proplist li {
font-size: 11px;
}
+.searchbox .searchicon,
.searchbox #searchmenulink,
#quicksearchbar #searchmenulink {
position: absolute;
@@ -1865,12 +1871,25 @@ ul.proplist li {
}
.searchbox #searchreset,
+.searchbox .iconbutton.reset,
#quicksearchbar #searchreset {
position: absolute;
top: 4px;
right: 1px;
}
+.listsearchbox {
+ position: relative;
+ padding: 4px;
+ background: #c7e3ef;
+}
+
+.listsearchbox input {
+ width: 100%;
+ height: 26px;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+}
/*** toolbar ***/
@@ -2489,6 +2508,11 @@ ul.toolbarmenu li span.copy {
margin-bottom: 1px;
}
+.attachmentslist li.txt,
+.attachmentslist li.text {
+ background-position: 0 -416px;
+}
+
.attachmentslist li.pdf {
background-position: 0 -26px;
}
@@ -2548,11 +2572,6 @@ ul.toolbarmenu li span.copy {
background-position: 0 -338px;
}
-.attachmentslist li.txt,
-.attachmentslist li.text {
- background-position: 0 -416px;
-}
-
.attachmentslist li.ics,
.attachmentslist li.calendar {
background-position: 0 -364px;
diff --git a/skins/larry/templates/about.html b/skins/larry/templates/about.html
index e2bd0b019..b0d36d177 100644
--- a/skins/larry/templates/about.html
+++ b/skins/larry/templates/about.html
@@ -14,7 +14,7 @@
<roundcube:object name="aboutcontent" />
<h2 class="sysname">Roundcube Webmail <roundcube:object name="version" /></h2>
-<p class="copyright">Copyright &copy; 2005-2013, The Roundcube Dev Team</p>
+<p class="copyright">Copyright &copy; 2005-2014, 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/>
diff --git a/skins/larry/templates/compose.html b/skins/larry/templates/compose.html
index d0338292b..90df4f3a8 100644
--- a/skins/larry/templates/compose.html
+++ b/skins/larry/templates/compose.html
@@ -42,11 +42,11 @@
<!-- inline address book -->
<div id="compose-contacts" class="uibox listbox">
<h2 class="boxtitle"><roundcube:label name="contacts" /></h2>
- <div id="composequicksearch">
+ <div class="listsearchbox">
<div class="searchbox">
<roundcube:object name="searchform" id="contactsearchbox" />
- <a id="searchmenulink" class="iconbutton searchoptions"> </a>
- <roundcube:button command="reset-search" id="searchreset" class="iconbutton reset" title="resetsearch" content=" " />
+ <a id="searchmenulink" class="iconbutton searchicon"> </a>
+ <roundcube:button command="reset-search" class="iconbutton reset" title="resetsearch" content=" " />
</div>
</div>
<roundcube:object name="addressbooks" id="directorylist" class="listing" />
diff --git a/tests/Framework/StringReplacer.php b/tests/Framework/StringReplacer.php
index 0fa7fae34..7d9600a78 100644
--- a/tests/Framework/StringReplacer.php
+++ b/tests/Framework/StringReplacer.php
@@ -42,6 +42,10 @@ class Framework_StringReplacer extends PHPUnit_Framework_TestCase
array('1@1.com www.domain.tld', '<a href="mailto:1@1.com">1@1.com</a> <a href="http://www.domain.tld">www.domain.tld</a>'),
array(' www.domain.tld ', ' <a href="http://www.domain.tld">www.domain.tld</a> '),
array(' www.domain.tld/#!download|856p1|2 ', ' <a href="http://www.domain.tld/#!download|856p1|2">www.domain.tld/#!download|856p1|2</a> '),
+ // #1489898: allow some unicode characters
+ array('https://www.google.com/maps/place/New+York,+État+de+New+York/@40.7056308,-73.9780035,11z/data=!3m1!4b1!4m2!3m1!1s0x89c24fa5d33f083b:0xc80b8f06e177fe62',
+ '<a href="https://www.google.com/maps/place/New+York,+État+de+New+York/@40.7056308,-73.9780035,11z/data=!3m1!4b1!4m2!3m1!1s0x89c24fa5d33f083b:0xc80b8f06e177fe62">https://www.google.com/maps/place/New+York,+État+de+New+York/@40.7056308,-73.9780035,11z/data=!3m1!4b1!4m2!3m1!1s0x89c24fa5d33f083b:0xc80b8f06e177fe62</a>'
+ ),
);
}