summaryrefslogtreecommitdiff
path: root/program
diff options
context:
space:
mode:
Diffstat (limited to 'program')
-rw-r--r--program/include/rcmail.php25
-rw-r--r--program/include/rcmail_output_html.php17
-rw-r--r--program/include/rcmail_output_json.php7
-rw-r--r--program/js/app.js50
-rw-r--r--program/lib/Roundcube/bootstrap.php12
-rw-r--r--program/lib/Roundcube/rcube_addressbook.php24
-rw-r--r--program/lib/Roundcube/rcube_config.php222
-rw-r--r--program/lib/Roundcube/rcube_contacts.php4
-rw-r--r--program/lib/Roundcube/rcube_csv2vcard.php2
-rw-r--r--program/lib/Roundcube/rcube_db.php36
-rw-r--r--program/lib/Roundcube/rcube_db_mysql.php25
-rw-r--r--program/lib/Roundcube/rcube_imap.php5
-rw-r--r--program/lib/Roundcube/rcube_imap_cache.php45
-rw-r--r--program/lib/Roundcube/rcube_ldap.php1
-rw-r--r--program/lib/Roundcube/rcube_mime.php18
-rw-r--r--program/lib/Roundcube/rcube_utils.php38
-rw-r--r--program/lib/Roundcube/rcube_vcard.php9
-rw-r--r--program/localization/en_US/labels.inc5
-rw-r--r--program/steps/addressbook/export.inc106
-rw-r--r--program/steps/addressbook/import.inc71
-rw-r--r--program/steps/addressbook/save.inc25
-rw-r--r--program/steps/mail/attachments.inc5
-rw-r--r--program/steps/mail/compose.inc33
-rw-r--r--program/steps/mail/func.inc6
-rw-r--r--program/steps/mail/sendmail.inc21
-rw-r--r--program/steps/mail/show.inc5
-rw-r--r--program/steps/settings/edit_prefs.inc9
-rw-r--r--program/steps/settings/func.inc19
28 files changed, 605 insertions, 240 deletions
diff --git a/program/include/rcmail.php b/program/include/rcmail.php
index 02287d312..0483f0e18 100644
--- a/program/include/rcmail.php
+++ b/program/include/rcmail.php
@@ -481,15 +481,22 @@ class rcmail extends rcube
$port = $config['default_port'];
}
- /* Modify username with domain if required
- Inspired by Marco <P0L0_notspam_binware.org>
- */
- // Check if we need to add domain
- if (!empty($config['username_domain']) && strpos($username, '@') === false) {
- if (is_array($config['username_domain']) && isset($config['username_domain'][$host]))
- $username .= '@'.rcube_utils::parse_host($config['username_domain'][$host], $host);
- else if (is_string($config['username_domain']))
- $username .= '@'.rcube_utils::parse_host($config['username_domain'], $host);
+ // Check if we need to add/force domain to username
+ if (!empty($config['username_domain'])) {
+ $domain = is_array($config['username_domain']) ? $config['username_domain'][$host] : $config['username_domain'];
+
+ if ($domain = rcube_utils::parse_host((string)$domain, $host)) {
+ $pos = strpos($username, '@');
+
+ // force configured domains
+ if (!empty($config['username_domain_forced']) && $pos !== false) {
+ $username = substr($username, 0, $pos) . '@' . $domain;
+ }
+ // just add domain if not specified
+ else if ($pos === false) {
+ $username .= '@' . $domain;
+ }
+ }
}
if (!isset($config['login_lc'])) {
diff --git a/program/include/rcmail_output_html.php b/program/include/rcmail_output_html.php
index a2ec29ca3..6db559358 100644
--- a/program/include/rcmail_output_html.php
+++ b/program/include/rcmail_output_html.php
@@ -924,8 +924,21 @@ class rcmail_output_html extends rcmail_output
}
else if ($object == 'logo') {
$attrib += array('alt' => $this->xml_command(array('', 'object', 'name="productname"')));
- if ($logo = $this->config->get('skin_logo'))
- $attrib['src'] = $logo;
+
+ if ($logo = $this->config->get('skin_logo')) {
+ if (is_array($logo)) {
+ if ($template_logo = $logo[$this->template_name]) {
+ $attrib['src'] = $template_logo;
+ }
+ elseif ($template_logo = $logo['*']) {
+ $attrib['src'] = $template_logo;
+ }
+ }
+ else {
+ $attrib['src'] = $logo;
+ }
+ }
+
$content = html::img($attrib);
}
else if ($object == 'productname') {
diff --git a/program/include/rcmail_output_json.php b/program/include/rcmail_output_json.php
index def6ee42c..d0e1eec64 100644
--- a/program/include/rcmail_output_json.php
+++ b/program/include/rcmail_output_json.php
@@ -227,6 +227,13 @@ class rcmail_output_json extends rcmail_output
if (!empty($this->callbacks))
$response['callbacks'] = $this->callbacks;
+ // trigger generic hook where plugins can put additional content to the response
+ $hook = $this->app->plugins->exec_hook("render_response", array('response' => $response));
+
+ // save some memory
+ $response = $hook['response'];
+ unset($hook['response']);
+
echo self::json_serialize($response);
}
diff --git a/program/js/app.js b/program/js/app.js
index dedad37d2..337a12156 100644
--- a/program/js/app.js
+++ b/program/js/app.js
@@ -1784,7 +1784,6 @@ function rcube_webmail()
+ (!flags.seen ? ' unread' : '')
+ (flags.deleted ? ' deleted' : '')
+ (flags.flagged ? ' flagged' : '')
- + (flags.unread_children && flags.seen && !this.env.autoexpand_threads ? ' unroot' : '')
+ (message.selected ? ' selected' : ''),
row = { cols:[], style:{}, id:'rcmrow'+uid };
@@ -1834,6 +1833,9 @@ function rcube_webmail()
expando = '<div id="rcmexpando' + uid + '" class="' + (message.expanded ? 'expanded' : 'collapsed') + '">&nbsp;&nbsp;</div>';
row_class += ' thread' + (message.expanded? ' expanded' : '');
}
+
+ if (flags.unread_children && flags.seen && !message.expanded)
+ row_class += ' unroot';
}
tree += '<span id="msgicn'+uid+'" class="'+css_class+'">&nbsp;</span>';
@@ -1879,7 +1881,7 @@ function rcube_webmail()
html = expando;
else if (c == 'subject') {
if (bw.ie) {
- col.onmouseover = function() { rcube_webmail.long_subject_title_ie(this, message.depth+1); };
+ col.onmouseover = function() { rcube_webmail.long_subject_title_ex(this, message.depth+1); };
if (bw.ie8)
tree = '<span></span>' + tree; // #1487821
}
@@ -3425,7 +3427,8 @@ function rcube_webmail()
message = input_message.val(),
is_html = ($("input[name='_is_html']").val() == '1'),
sig = this.env.identity,
- delim = this.env.recipients_delimiter,
+ delim = this.env.recipients_separator,
+ rx_delim = RegExp.escape(delim),
headers = ['replyto', 'bcc'];
// update reply-to/bcc fields with addresses defined in identities
@@ -3442,16 +3445,18 @@ function rcube_webmail()
}
// cleanup
- rx = new RegExp(RegExp.escape(delim) + '\\s*' + RegExp(delim), 'g');
- input_val = input_val.replace(rx, delim)
- rx = new RegExp('^\\s*' + RegExp.escape(delim) + '\\s*$');
- input_val = input_val.replace(rx, '')
+ rx = new RegExp(rx_delim + '\\s*' + rx_delim, 'g');
+ input_val = input_val.replace(rx, delim);
+ rx = new RegExp('^[\\s' + rx_delim + ']+');
+ input_val = input_val.replace(rx, '');
// add new address(es)
- if (new_val) {
- rx = new RegExp(RegExp.escape(delim) + '\\s*$');
- if (input_val && !rx.test(input_val))
- input_val += delim + ' ';
+ if (new_val && input_val.indexOf(new_val) == -1 && input_val.indexOf(new_val.replace(/"/g, '')) == -1) {
+ if (input_val) {
+ rx = new RegExp('[' + rx_delim + '\\s]+$')
+ input_val = input_val.replace(rx, '') + delim + ' ';
+ }
+
input_val += new_val + delim + ' ';
}
@@ -3637,7 +3642,12 @@ function rcube_webmail()
att.html = '<a title="'+this.get_label('cancel')+'" onclick="return rcmail.cancel_attachment_upload(\''+name+'\', \''+att.frame+'\');" href="#cancelupload" class="cancelupload">'
+ (this.env.cancelicon ? '<img src="'+this.env.cancelicon+'" alt="" />' : this.get_label('cancel')) + '</a>' + att.html;
- var indicator, li = $('<li>').attr('id', name).addClass(att.classname).html(att.html);
+ var indicator, li = $('<li>');
+
+ li.attr('id', name)
+ .addClass(att.classname)
+ .html(att.html)
+ .on('mouseover', function() { rcube_webmail.long_subject_title_ex(this, 0); });
// replace indicator's li
if (upload_id && (indicator = document.getElementById(upload_id))) {
@@ -3783,7 +3793,7 @@ function rcube_webmail()
this.env.search_id = null;
};
- this.sent_successfully = function(type, msg, target)
+ this.sent_successfully = function(type, msg, folders)
{
this.display_message(msg, type);
@@ -3792,9 +3802,11 @@ function rcube_webmail()
this.lock_form(this.gui_objects.messageform);
if (rc) {
rc.display_message(msg, type);
- // refresh the folder where sent message was saved
- if (target && rc.env.task == 'mail' && rc.env.action == '' && rc.env.mailbox == target)
- rc.command('checkmail');
+ // refresh the folder where sent message was saved or replied message comes from
+ if (folders && rc.env.task == 'mail' && rc.env.action == '' && $.inArray(rc.env.mailbox, folders) >= 0) {
+ // @TODO: try with 'checkmail' here when #1485186 is fixed. See also #1489249.
+ rc.command('list', rc.env.mailbox);
+ }
}
setTimeout(function(){ window.close() }, 1000);
}
@@ -4341,7 +4353,7 @@ function rcube_webmail()
boxtitle.append('&nbsp;&raquo;&nbsp;');
}
- boxtitle.append($('<span>'+prop.name+'</span>'));
+ boxtitle.append($('<span>').text(prop.name));
}
this.triggerEvent('groupupdate', prop);
@@ -6982,11 +6994,11 @@ rcube_webmail.long_subject_title = function(elem, indent)
if (!elem.title) {
var $elem = $(elem);
if ($elem.width() + indent * 15 > $elem.parent().width())
- elem.title = $elem.html();
+ elem.title = $elem.text();
}
};
-rcube_webmail.long_subject_title_ie = function(elem, indent)
+rcube_webmail.long_subject_title_ex = function(elem, indent)
{
if (!elem.title) {
var $elem = $(elem),
diff --git a/program/lib/Roundcube/bootstrap.php b/program/lib/Roundcube/bootstrap.php
index 5d5a22387..6e5143382 100644
--- a/program/lib/Roundcube/bootstrap.php
+++ b/program/lib/Roundcube/bootstrap.php
@@ -26,25 +26,25 @@
*/
$config = array(
- 'error_reporting' => E_ALL &~ (E_NOTICE | E_STRICT),
+ 'error_reporting' => E_ALL & ~E_NOTICE & ~E_STRICT,
// Some users are not using Installer, so we'll check some
// critical PHP settings here. Only these, which doesn't provide
// an error/warning in the logs later. See (#1486307).
'mbstring.func_overload' => 0,
- 'magic_quotes_runtime' => 0,
- 'magic_quotes_sybase' => 0, // #1488506
+ 'magic_quotes_runtime' => false,
+ 'magic_quotes_sybase' => false, // #1488506
);
// check these additional ini settings if not called via CLI
if (php_sapi_name() != 'cli') {
$config += array(
- 'suhosin.session.encrypt' => 0,
- 'file_uploads' => 1,
+ 'suhosin.session.encrypt' => false,
+ 'file_uploads' => true,
);
}
foreach ($config as $optname => $optval) {
- $ini_optval = filter_var(ini_get($optname), FILTER_VALIDATE_BOOLEAN);
+ $ini_optval = filter_var(ini_get($optname), is_bool($optval) ? FILTER_VALIDATE_BOOLEAN : FILTER_VALIDATE_INT);
if ($optval != $ini_optval && @ini_set($optname, $optval) === false) {
$error = "ERROR: Wrong '$optname' option value and it wasn't possible to set it to required value ($optval).\n"
. "Check your PHP configuration (including php_admin_flag).";
diff --git a/program/lib/Roundcube/rcube_addressbook.php b/program/lib/Roundcube/rcube_addressbook.php
index 4ed139c45..9301211ff 100644
--- a/program/lib/Roundcube/rcube_addressbook.php
+++ b/program/lib/Roundcube/rcube_addressbook.php
@@ -3,7 +3,7 @@
/*
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
- | Copyright (C) 2006-2012, The Roundcube Dev Team |
+ | Copyright (C) 2006-2013, The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
@@ -35,6 +35,7 @@ abstract class rcube_addressbook
/** public properties (mandatory) */
public $primary_key;
public $groups = false;
+ public $export_groups = true;
public $readonly = true;
public $searchonly = false;
public $undelete = false;
@@ -423,7 +424,7 @@ abstract class rcube_addressbook
* @param boolean True to return one array with all values, False for hash array with values grouped by type
* @return array List of column values
*/
- function get_col_values($col, $data, $flat = false)
+ public static function get_col_values($col, $data, $flat = false)
{
$out = array();
foreach ((array)$data as $c => $values) {
@@ -476,7 +477,8 @@ abstract class rcube_addressbook
$fn = trim(join(' ', array_filter(array($contact['prefix'], $contact['firstname'], $contact['middlename'], $contact['surname'], $contact['suffix']))));
// use email address part for name
- $email = is_array($contact['email']) ? $contact['email'][0] : $contact['email'];
+ $email = self::get_col_values('email', $contact, true);
+ $email = $email[0];
if ($email && (empty($fn) || $fn == $email)) {
// return full email
@@ -523,9 +525,9 @@ abstract class rcube_addressbook
$fn = $contact['name'];
// fallback to email address
- $email = is_array($contact['email']) ? $contact['email'][0] : $contact['email'];
- if (empty($fn) && $email)
- return $email;
+ if (empty($fn) && ($email = self::get_col_values('email', $contact, true)) && !empty($email)) {
+ return $email[0];
+ }
return $fn;
}
@@ -538,8 +540,8 @@ abstract class rcube_addressbook
$key = $contact[$sort_col] . ':' . $contact['sourceid'];
// add email to a key to not skip contacts with the same name (#1488375)
- if (!empty($contact['email'])) {
- $key .= ':' . implode(':', (array)$contact['email']);
+ if (($email = self::get_col_values('email', $contact, true)) && !empty($email)) {
+ $key .= ':' . implode(':', (array)$email);
}
return $key;
@@ -561,9 +563,9 @@ abstract class rcube_addressbook
// use only strict comparison (mode = 1)
// @TODO: partial search, e.g. match only day and month
if (in_array($colname, $this->date_cols)) {
- return (($value = rcube_utils::strtotime($value))
- && ($search = rcube_utils::strtotime($search))
- && date('Ymd', $value) == date('Ymd', $search));
+ return (($value = rcube_utils::anytodatetime($value))
+ && ($search = rcube_utils::anytodatetime($search))
+ && $value->format('Ymd') == $search->format('Ymd'));
}
// composite field, e.g. address
diff --git a/program/lib/Roundcube/rcube_config.php b/program/lib/Roundcube/rcube_config.php
index 90e1394cf..a3741758f 100644
--- a/program/lib/Roundcube/rcube_config.php
+++ b/program/lib/Roundcube/rcube_config.php
@@ -3,7 +3,7 @@
/*
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
- | Copyright (C) 2008-2012, The Roundcube Dev Team |
+ | Copyright (C) 2008-2013, The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
@@ -27,7 +27,7 @@ class rcube_config
const DEFAULT_SKIN = 'larry';
private $env = '';
- private $basedir = 'config/';
+ private $paths = array();
private $prop = array();
private $errors = array();
private $userprefs = array();
@@ -58,7 +58,32 @@ class rcube_config
public function __construct($env = '')
{
$this->env = $env;
- $this->basedir = RCUBE_CONFIG_DIR;
+
+ if ($paths = getenv('RCUBE_CONFIG_PATH')) {
+ $this->paths = explode(PATH_SEPARATOR, $paths);
+ // make all paths absolute
+ foreach ($this->paths as $i => $path) {
+ if (!$this->_is_absolute($path)) {
+ if ($realpath = realpath(RCUBE_INSTALL_PATH . $path)) {
+ $this->paths[$i] = unslashify($realpath) . '/';
+ }
+ else {
+ unset($this->paths[$i]);
+ }
+ }
+ else {
+ $this->paths[$i] = unslashify($path) . '/';
+ }
+ }
+ }
+
+ if (defined('RCUBE_CONFIG_DIR') && !in_array(RCUBE_CONFIG_DIR, $this->paths)) {
+ $this->paths[] = RCUBE_CONFIG_DIR;
+ }
+
+ if (empty($this->paths)) {
+ $this->paths[] = RCUBE_INSTALL_PATH . 'config/';
+ }
$this->load();
@@ -138,17 +163,6 @@ class rcube_config
// enable display_errors in 'show' level, but not for ajax requests
ini_set('display_errors', intval(empty($_REQUEST['_remote']) && ($this->prop['debug_level'] & 4)));
- // set timezone auto settings values
- if ($this->prop['timezone'] == 'auto') {
- $this->prop['_timezone_value'] = $this->client_timezone();
- }
- else if (is_numeric($this->prop['timezone']) && ($tz = timezone_name_from_abbr("", $this->prop['timezone'] * 3600, 0))) {
- $this->prop['timezone'] = $tz;
- }
- else if (empty($this->prop['timezone'])) {
- $this->prop['timezone'] = 'UTC';
- }
-
// remove deprecated properties
unset($this->prop['dst_active']);
@@ -186,47 +200,73 @@ class rcube_config
*/
public function load_from_file($file)
{
- $fpath = $this->resolve_path($file);
- if ($fpath && is_file($fpath) && is_readable($fpath)) {
- // use output buffering, we don't need any output here
- ob_start();
- include($fpath);
- ob_end_clean();
-
- if (is_array($config)) {
- $this->merge($config);
- return true;
- }
- // deprecated name of config variable
- else if (is_array($rcmail_config)) {
- $this->merge($rcmail_config);
- return true;
+ $success = false;
+
+ foreach ($this->resolve_paths($file) as $fpath) {
+ if ($fpath && is_file($fpath) && is_readable($fpath)) {
+ // use output buffering, we don't need any output here
+ ob_start();
+ include($fpath);
+ ob_end_clean();
+
+ if (is_array($config)) {
+ $this->merge($config);
+ $success = true;
+ }
+ // deprecated name of config variable
+ if (is_array($rcmail_config)) {
+ $this->merge($rcmail_config);
+ $success = true;
+ }
}
}
- return false;
+ return $success;
}
/**
- * Helper method to resolve the absolute path to the given config file.
+ * Helper method to resolve absolute paths to the given config file.
* This also takes the 'env' property into account.
+ *
+ * @param string Filename or absolute file path
+ * @param boolean Return -$env file path if exists
+ * @return array List of candidates in config dir path(s)
*/
- public function resolve_path($file, $use_env = true)
+ public function resolve_paths($file, $use_env = true)
{
- if (strpos($file, '/') === false) {
- $file = realpath($this->basedir . '/' . $file);
- }
+ $files = array();
+ $abs_path = $this->_is_absolute($file);
- // check if <file>-env.ini exists
- if ($file && $use_env && !empty($this->env)) {
- $envfile = preg_replace('/\.(inc.php)$/', '-' . $this->env . '.\\1', $file);
- if (is_file($envfile))
- return $envfile;
+ foreach ($this->paths as $basepath) {
+ $realpath = $abs_path ? $file : realpath($basepath . '/' . $file);
+
+ // check if <file>-env.ini exists
+ if ($realpath && $use_env && !empty($this->env)) {
+ $envfile = preg_replace('/\.(inc.php)$/', '-' . $this->env . '.\\1', $realpath);
+ if (is_file($envfile))
+ $realpath = $envfile;
+ }
+
+ if ($realpath) {
+ $files[] = $realpath;
+
+ // no need to continue the loop if an absolute file path is given
+ if ($abs_path) {
+ break;
+ }
+ }
}
- return $file;
+ return $files;
}
+ /**
+ * Determine whether the given file path is absolute or relative
+ */
+ private function _is_absolute($path)
+ {
+ return $path[0] == DIRECTORY_SEPARATOR || preg_match('!^[a-z]:[\\\\/]!i', $path);
+ }
/**
* Getter for a specific config parameter
@@ -246,8 +286,10 @@ class rcube_config
$rcube = rcube::get_instance();
- if ($name == 'timezone' && isset($this->prop['_timezone_value'])) {
- $result = $this->prop['_timezone_value'];
+ if ($name == 'timezone') {
+ if (empty($result) || $result == 'auto') {
+ $result = $this->client_timezone();
+ }
}
else if ($name == 'client_mimetypes') {
if ($result == null && $def == null)
@@ -305,11 +347,6 @@ class rcube_config
}
}
- // convert user's timezone into the new format
- if (is_numeric($prefs['timezone']) && ($tz = timezone_name_from_abbr('', $prefs['timezone'] * 3600, 0))) {
- $prefs['timezone'] = $tz;
- }
-
// larry is the new default skin :-)
if ($prefs['skin'] == 'default') {
$prefs['skin'] = self::DEFAULT_SKIN;
@@ -317,13 +354,6 @@ class rcube_config
$this->userprefs = $prefs;
$this->prop = array_merge($this->prop, $prefs);
-
- // override timezone settings with client values
- if ($this->prop['timezone'] == 'auto') {
- $this->prop['_timezone_value'] = isset($_SESSION['timezone']) ? $this->client_timezone() : $this->prop['_timezone_value'];
- }
- else if (isset($this->prop['_timezone_value']))
- unset($this->prop['_timezone_value']);
}
@@ -464,13 +494,12 @@ class rcube_config
*/
private function client_timezone()
{
- if (isset($_SESSION['timezone']) && is_numeric($_SESSION['timezone'])
- && ($ctz = timezone_name_from_abbr("", $_SESSION['timezone'] * 3600, 0))) {
- return $ctz;
- }
- else if (!empty($_SESSION['timezone'])) {
+ // @TODO: remove this legacy timezone handling in the future
+ $props = $this->fix_legacy_props(array('timezone' => $_SESSION['timezone']));
+
+ if (!empty($props['timezone'])) {
try {
- $tz = timezone_open($_SESSION['timezone']);
+ $tz = new DateTimeZone($props['timezone']);
return $tz->getName();
}
catch (Exception $e) { /* gracefully ignore */ }
@@ -498,6 +527,77 @@ class rcube_config
}
}
+ // convert deprecated numeric timezone value
+ if (isset($props['timezone']) && is_numeric($props['timezone'])) {
+ if ($tz = self::timezone_name_from_abbr($props['timezone'])) {
+ $props['timezone'] = $tz;
+ }
+ else {
+ unset($props['timezone']);
+ }
+ }
+
return $props;
}
+
+ /**
+ * timezone_name_from_abbr() replacement. Converts timezone offset
+ * into timezone name abbreviation.
+ *
+ * @param float $offset Timezone offset (in hours)
+ *
+ * @return string Timezone abbreviation
+ */
+ static public function timezone_name_from_abbr($offset)
+ {
+ // List of timezones here is not complete - https://bugs.php.net/bug.php?id=44780
+ if ($tz = timezone_name_from_abbr('', $offset * 3600, 0)) {
+ return $tz;
+ }
+
+ // try with more complete list (#1489261)
+ $timezones = array(
+ '-660' => "Pacific/Apia",
+ '-600' => "Pacific/Honolulu",
+ '-570' => "Pacific/Marquesas",
+ '-540' => "America/Anchorage",
+ '-480' => "America/Los_Angeles",
+ '-420' => "America/Denver",
+ '-360' => "America/Chicago",
+ '-300' => "America/New_York",
+ '-270' => "America/Caracas",
+ '-240' => "America/Halifax",
+ '-210' => "Canada/Newfoundland",
+ '-180' => "America/Sao_Paulo",
+ '-60' => "Atlantic/Azores",
+ '0' => "Europe/London",
+ '60' => "Europe/Paris",
+ '120' => "Europe/Helsinki",
+ '180' => "Europe/Moscow",
+ '210' => "Asia/Tehran",
+ '240' => "Asia/Dubai",
+ '300' => "Asia/Karachi",
+ '270' => "Asia/Kabul",
+ '300' => "Asia/Karachi",
+ '330' => "Asia/Kolkata",
+ '345' => "Asia/Katmandu",
+ '360' => "Asia/Yekaterinburg",
+ '390' => "Asia/Rangoon",
+ '420' => "Asia/Krasnoyarsk",
+ '480' => "Asia/Shanghai",
+ '525' => "Australia/Eucla",
+ '540' => "Asia/Tokyo",
+ '570' => "Australia/Adelaide",
+ '600' => "Australia/Melbourne",
+ '630' => "Australia/Lord_Howe",
+ '660' => "Asia/Vladivostok",
+ '690' => "Pacific/Norfolk",
+ '720' => "Pacific/Auckland",
+ '765' => "Pacific/Chatham",
+ '780' => "Pacific/Enderbury",
+ '840' => "Pacific/Kiritimati",
+ );
+
+ return $timezones[(string) intval($offset * 60)];
+ }
}
diff --git a/program/lib/Roundcube/rcube_contacts.php b/program/lib/Roundcube/rcube_contacts.php
index 3919cdc6e..6d01368a1 100644
--- a/program/lib/Roundcube/rcube_contacts.php
+++ b/program/lib/Roundcube/rcube_contacts.php
@@ -718,6 +718,10 @@ class rcube_contacts extends rcube_addressbook
foreach ($save_data as $key => $values) {
list($field, $section) = explode(':', $key);
$fulltext = in_array($field, $this->fulltext_cols);
+ // avoid casting DateTime objects to array
+ if (is_object($values) && is_a($values, 'DateTime')) {
+ $values = array(0 => $values);
+ }
foreach ((array)$values as $value) {
if (isset($value))
$vcard->set($field, $value, $section);
diff --git a/program/lib/Roundcube/rcube_csv2vcard.php b/program/lib/Roundcube/rcube_csv2vcard.php
index fb8d8f103..00e6d4e20 100644
--- a/program/lib/Roundcube/rcube_csv2vcard.php
+++ b/program/lib/Roundcube/rcube_csv2vcard.php
@@ -145,6 +145,7 @@ class rcube_csv2vcard
'work_mobile' => 'phone:work,cell',
'work_title' => 'jobtitle',
'work_zip' => 'zipcode:work',
+ 'group' => 'groups',
);
/**
@@ -268,6 +269,7 @@ class rcube_csv2vcard
'work_mobile' => "Work Mobile",
'work_title' => "Work Title",
'work_zip' => "Work Zip",
+ 'groups' => "Group",
);
protected $local_label_map = array();
diff --git a/program/lib/Roundcube/rcube_db.php b/program/lib/Roundcube/rcube_db.php
index 852070073..e66226ff5 100644
--- a/program/lib/Roundcube/rcube_db.php
+++ b/program/lib/Roundcube/rcube_db.php
@@ -386,17 +386,7 @@ class rcube_db
$result = $this->dbh->query($query);
if ($result === false) {
- $error = $this->dbh->errorInfo();
-
- if (empty($this->options['ignore_key_errors']) || $error[0] != '23000') {
- $this->db_error = true;
- $this->db_error_msg = sprintf('[%s] %s', $error[1], $error[2]);
-
- rcube::raise_error(array('code' => 500, 'type' => 'db',
- 'line' => __LINE__, 'file' => __FILE__,
- 'message' => $this->db_error_msg . " (SQL Query: $query)"
- ), true, false);
- }
+ $result = $this->handle_error($query);
}
$this->last_result = $result;
@@ -405,6 +395,30 @@ class rcube_db
}
/**
+ * Helper method to handle DB errors.
+ * This by default logs the error but could be overriden by a driver implementation
+ *
+ * @param string Query that triggered the error
+ * @return mixed Result to be stored and returned
+ */
+ protected function handle_error($query)
+ {
+ $error = $this->dbh->errorInfo();
+
+ if (empty($this->options['ignore_key_errors']) || $error[0] != '23000') {
+ $this->db_error = true;
+ $this->db_error_msg = sprintf('[%s] %s', $error[1], $error[2]);
+
+ rcube::raise_error(array('code' => 500, 'type' => 'db',
+ 'line' => __LINE__, 'file' => __FILE__,
+ 'message' => $this->db_error_msg . " (SQL Query: $query)"
+ ), true, false);
+ }
+
+ return false;
+ }
+
+ /**
* Get number of affected rows for the last query
*
* @param mixed $result Optional query handle
diff --git a/program/lib/Roundcube/rcube_db_mysql.php b/program/lib/Roundcube/rcube_db_mysql.php
index 6fa5ad768..24f9ce1bd 100644
--- a/program/lib/Roundcube/rcube_db_mysql.php
+++ b/program/lib/Roundcube/rcube_db_mysql.php
@@ -179,4 +179,29 @@ class rcube_db_mysql extends rcube_db
return isset($this->variables[$varname]) ? $this->variables[$varname] : $default;
}
+ /**
+ * Handle DB errors, re-issue the query on deadlock errors from InnoDB row-level locking
+ *
+ * @param string Query that triggered the error
+ * @return mixed Result to be stored and returned
+ */
+ protected function handle_error($query)
+ {
+ $error = $this->dbh->errorInfo();
+
+ // retry after "Deadlock found when trying to get lock" errors
+ $retries = 2;
+ while ($error[1] == 1213 && $retries >= 0) {
+ usleep(50000); // wait 50 ms
+ $result = $this->dbh->query($query);
+ if ($result !== false) {
+ return $result;
+ }
+ $error = $this->dbh->errorInfo();
+ $retries--;
+ }
+
+ return parent::handle_error($query);
+ }
+
}
diff --git a/program/lib/Roundcube/rcube_imap.php b/program/lib/Roundcube/rcube_imap.php
index 689a6266d..aa074233f 100644
--- a/program/lib/Roundcube/rcube_imap.php
+++ b/program/lib/Roundcube/rcube_imap.php
@@ -3785,9 +3785,10 @@ class rcube_imap extends rcube_storage
if ($this->messages_caching && !$this->mcache) {
$rcube = rcube::get_instance();
if (($dbh = $rcube->get_dbh()) && ($userid = $rcube->get_user_id())) {
- $ttl = $rcube->config->get('messages_cache_ttl', '10d');
+ $ttl = $rcube->config->get('messages_cache_ttl', '10d');
+ $threshold = $rcube->config->get('messages_cache_threshold', 50);
$this->mcache = new rcube_imap_cache(
- $dbh, $this, $userid, $this->options['skip_deleted'], $ttl);
+ $dbh, $this, $userid, $this->options['skip_deleted'], $ttl, $threshold);
}
}
diff --git a/program/lib/Roundcube/rcube_imap_cache.php b/program/lib/Roundcube/rcube_imap_cache.php
index 061ac546d..d72bfe0ab 100644
--- a/program/lib/Roundcube/rcube_imap_cache.php
+++ b/program/lib/Roundcube/rcube_imap_cache.php
@@ -56,6 +56,13 @@ class rcube_imap_cache
private $ttl;
/**
+ * Maximum cached message size
+ *
+ * @var int
+ */
+ private $threshold;
+
+ /**
* Internal (in-memory) cache
*
* @var array
@@ -96,9 +103,9 @@ class rcube_imap_cache
* @param int $userid User identifier
* @param bool $skip_deleted skip_deleted flag
* @param string $ttl Expiration time of memcache/apc items
- *
+ * @param int $threshold Maximum cached message size
*/
- function __construct($db, $imap, $userid, $skip_deleted, $ttl=0)
+ function __construct($db, $imap, $userid, $skip_deleted, $ttl=0, $threshold=0)
{
// convert ttl string to seconds
$ttl = get_offset_sec($ttl);
@@ -109,6 +116,7 @@ class rcube_imap_cache
$this->userid = $userid;
$this->skip_deleted = $skip_deleted;
$this->ttl = $ttl;
+ $this->threshold = $threshold;
}
@@ -1155,13 +1163,13 @@ class rcube_imap_cache
// Save current message from internal cache
if ($message = $this->icache['__message']) {
// clean up some object's data
- $object = $this->message_object_prepare($message['object']);
+ $this->message_object_prepare($message['object']);
// calculate current md5 sum
- $md5sum = md5(serialize($object));
+ $md5sum = md5(serialize($message['object']));
if ($message['md5sum'] != $md5sum) {
- $this->add_message($message['mailbox'], $object, !$message['exists']);
+ $this->add_message($message['mailbox'], $message['object'], !$message['exists']);
}
$this->icache['__message']['md5sum'] = $md5sum;
@@ -1171,12 +1179,19 @@ class rcube_imap_cache
/**
* Prepares message object to be stored in database.
+ *
+ * @param rcube_message_header|rcube_message_part
*/
- private function message_object_prepare($msg)
+ private function message_object_prepare(&$msg, &$size = 0)
{
- // Remove body too big (>25kB)
- if ($msg->body && strlen($msg->body) > 25 * 1024) {
- unset($msg->body);
+ // Remove body too big
+ if ($msg->body && ($length = strlen($msg->body))) {
+ $size += $length;
+
+ if ($size > $this->threshold * 1024) {
+ $size -= $length;
+ unset($msg->body);
+ }
}
// Fix mimetype which might be broken by some code when message is displayed
@@ -1186,13 +1201,19 @@ class rcube_imap_cache
list($msg->ctype_primary, $msg->ctype_secondary) = explode('/', $msg->mimetype);
}
+ unset($msg->replaces);
+
if (is_array($msg->structure->parts)) {
- foreach ($msg->structure->parts as $idx => $part) {
- $msg->structure->parts[$idx] = $this->message_object_prepare($part);
+ foreach ($msg->structure->parts as $part) {
+ $this->message_object_prepare($part, $size);
}
}
- return $msg;
+ if (is_array($msg->parts)) {
+ foreach ($msg->parts as $part) {
+ $this->message_object_prepare($part, $size);
+ }
+ }
}
diff --git a/program/lib/Roundcube/rcube_ldap.php b/program/lib/Roundcube/rcube_ldap.php
index cb7fa8466..78573789b 100644
--- a/program/lib/Roundcube/rcube_ldap.php
+++ b/program/lib/Roundcube/rcube_ldap.php
@@ -34,6 +34,7 @@ class rcube_ldap extends rcube_addressbook
public $ready = false;
public $group_id = 0;
public $coltypes = array();
+ public $export_groups = false;
// private properties
protected $ldap;
diff --git a/program/lib/Roundcube/rcube_mime.php b/program/lib/Roundcube/rcube_mime.php
index 572540f47..96a8eac61 100644
--- a/program/lib/Roundcube/rcube_mime.php
+++ b/program/lib/Roundcube/rcube_mime.php
@@ -708,12 +708,20 @@ class rcube_mime
*/
public static function file_content_type($path, $name, $failover = 'application/octet-stream', $is_stream = false, $skip_suffix = false)
{
+ static $mime_ext = array();
+
$mime_type = null;
- $mime_magic = rcube::get_instance()->config->get('mime_magic');
- $mime_ext = $skip_suffix ? null : @include(RCUBE_CONFIG_DIR . '/mimetypes.php');
+ $config = rcube::get_instance()->config;
+ $mime_magic = $config->get('mime_magic');
+
+ if (!$skip_suffix && empty($mime_ext)) {
+ foreach ($config->resolve_paths('mimetypes.php') as $fpath) {
+ $mime_ext = array_merge($mime_ext, (array) @include($fpath));
+ }
+ }
// use file name suffix with hard-coded mime-type map
- if (is_array($mime_ext) && $name) {
+ if (!$skip_suffix && is_array($mime_ext) && $name) {
if ($suffix = substr($name, strrpos($name, '.')+1)) {
$mime_type = $mime_ext[strtolower($suffix)];
}
@@ -818,7 +826,9 @@ class rcube_mime
// fallback to some well-known types most important for daily emails
if (empty($mime_types)) {
- $mime_extensions = (array) @include(RCUBE_CONFIG_DIR . '/mimetypes.php');
+ foreach (rcube::get_instance()->config->resolve_paths('mimetypes.php') as $fpath) {
+ $mime_extensions = array_merge($mime_extensions, (array) @include($fpath));
+ }
foreach ($mime_extensions as $ext => $mime) {
$mime_types[$mime][] = $ext;
diff --git a/program/lib/Roundcube/rcube_utils.php b/program/lib/Roundcube/rcube_utils.php
index 2540f779d..1d76ae508 100644
--- a/program/lib/Roundcube/rcube_utils.php
+++ b/program/lib/Roundcube/rcube_utils.php
@@ -787,6 +787,44 @@ class rcube_utils
return (int) $ts;
}
+ /**
+ * Date parsing function that turns the given value into a DateTime object
+ *
+ * @param string $date Date string
+ *
+ * @return object DateTime instance or false on failure
+ */
+ public static function anytodatetime($date)
+ {
+ if (is_object($date) && is_a($date, 'DateTime')) {
+ return $date;
+ }
+
+ $dt = false;
+ $date = trim($date);
+
+ // try to parse string with DateTime first
+ if (!empty($date)) {
+ try {
+ $dt = new DateTime($date);
+ }
+ catch (Exception $e) {
+ // ignore
+ }
+ }
+
+ // try our advanced strtotime() method
+ if (!$dt && ($timestamp = self::strtotime($date))) {
+ try {
+ $dt = new DateTime("@".$timestamp);
+ }
+ catch (Exception $e) {
+ // ignore
+ }
+ }
+
+ return $dt;
+ }
/*
* Idn_to_ascii wrapper.
diff --git a/program/lib/Roundcube/rcube_vcard.php b/program/lib/Roundcube/rcube_vcard.php
index a71305c4b..d54dc56ad 100644
--- a/program/lib/Roundcube/rcube_vcard.php
+++ b/program/lib/Roundcube/rcube_vcard.php
@@ -47,6 +47,7 @@ class rcube_vcard
'manager' => 'X-MANAGER',
'spouse' => 'X-SPOUSE',
'edit' => 'X-AB-EDIT',
+ 'groups' => 'CATEGORIES',
);
private $typemap = array(
'IPHONE' => 'mobile',
@@ -357,8 +358,8 @@ class rcube_vcard
case 'birthday':
case 'anniversary':
- if (($val = rcube_utils::strtotime($value)) && ($fn = self::$fieldmap[$field])) {
- $this->raw[$fn][] = array(0 => date('Y-m-d', $val), 'value' => array('date'));
+ if (($val = rcube_utils::anytodatetime($value)) && ($fn = self::$fieldmap[$field])) {
+ $this->raw[$fn][] = array(0 => $val->format('Y-m-d'), 'value' => array('date'));
}
break;
@@ -756,7 +757,7 @@ class rcube_vcard
*
* @return string Joined and quoted string
*/
- private static function vcard_quote($s, $sep = ';')
+ public static function vcard_quote($s, $sep = ';')
{
if (is_array($s)) {
foreach($s as $part) {
@@ -765,7 +766,7 @@ class rcube_vcard
return(implode($sep, (array)$r));
}
- return strtr($s, array('\\' => '\\\\', "\r" => '', "\n" => '\n', ',' => '\,', ';' => '\;'));
+ return strtr($s, array('\\' => '\\\\', "\r" => '', "\n" => '\n', $sep => '\\'.$sep));
}
/**
diff --git a/program/localization/en_US/labels.inc b/program/localization/en_US/labels.inc
index 1865bcb3d..840c9358c 100644
--- a/program/localization/en_US/labels.inc
+++ b/program/localization/en_US/labels.inc
@@ -367,8 +367,11 @@ $labels['searchdelete'] = 'Delete search';
$labels['import'] = 'Import';
$labels['importcontacts'] = 'Import contacts';
$labels['importfromfile'] = 'Import from file:';
-$labels['importtarget'] = 'Add new contacts to address book:';
+$labels['importtarget'] = 'Add contacts to';
$labels['importreplace'] = 'Replace the entire address book';
+$labels['importgroups'] = 'Import group assignments';
+$labels['importgroupsall'] = 'All (create groups if necessary)';
+$labels['importgroupsexisting'] = 'Only for existing groups';
$labels['importdesc'] = 'You can upload contacts from an existing address book.<br/>We currently support importing addresses from the <a href="http://en.wikipedia.org/wiki/VCard">vCard</a> or CSV (comma-separated) data format.';
$labels['done'] = 'Done';
diff --git a/program/steps/addressbook/export.inc b/program/steps/addressbook/export.inc
index 761f26b75..1e988feab 100644
--- a/program/steps/addressbook/export.inc
+++ b/program/steps/addressbook/export.inc
@@ -5,7 +5,7 @@
| program/steps/addressbook/export.inc |
| |
| This file is part of the Roundcube Webmail client |
- | Copyright (C) 2008-2011, The Roundcube Dev Team |
+ | Copyright (C) 2008-2013, The Roundcube Dev Team |
| Copyright (C) 2011, Kolab Systems AG |
| |
| Licensed under the GNU General Public License version 3 or |
@@ -21,6 +21,46 @@
+-----------------------------------------------------------------------+
*/
+
+/**
+ * Copy contact record properties into a vcard object
+ */
+function prepare_for_export(&$record, $source = null)
+{
+ $groups = $source && $source->groups && $source->export_groups ? $source->get_record_groups($record['ID']) : null;
+
+ if (empty($record['vcard'])) {
+ $vcard = new rcube_vcard();
+ if ($source) {
+ $vcard->extend_fieldmap($source->vcard_map);
+ }
+ $vcard->load($record['vcard']);
+ $vcard->reset();
+
+ foreach ($record as $key => $values) {
+ list($field, $section) = explode(':', $key);
+ foreach ((array)$values as $value) {
+ if (is_array($value) || @strlen($value)) {
+ $vcard->set($field, $value, strtoupper($section));
+ }
+ }
+ }
+
+ // append group names
+ if ($groups) {
+ $vcard->set('groups', join(',', $groups), null);
+ }
+
+ $record['vcard'] = $vcard->export(true);
+ }
+ // patch categories to alread existing vcard block
+ else if ($record['vcard'] && !empty($groups) && !strpos($record['vcard'], 'CATEGORIES:')) {
+ $vgroups = 'CATEGORIES:' . rcube_vcard::vcard_quote(join(',', $groups));
+ $record['vcard'] = str_replace('END:VCARD', $vgroups . rcube_vcard::$eol . 'END:VCARD', $record['vcard']);
+ }
+}
+
+
// Use search result
if (!empty($_REQUEST['_search']) && isset($_SESSION['search'][$_REQUEST['_search']]))
{
@@ -42,23 +82,7 @@ if (!empty($_REQUEST['_search']) && isset($_SESSION['search'][$_REQUEST['_search
while ($record = $result->next()) {
// because vcard_map is per-source we need to create vcard here
- if (empty($record['vcard']) || empty($record['name'])) {
- $vcard = new rcube_vcard();
- $vcard->extend_fieldmap($source->vcard_map);
- $vcard->load($record['vcard']);
- $vcard->reset();
-
- foreach ($record as $key => $values) {
- list($field, $section) = explode(':', $key);
- foreach ((array)$values as $value) {
- if (is_array($value) || @strlen($value)) {
- $vcard->set($field, $value, strtoupper($section));
- }
- }
- }
-
- $record['vcard'] = $vcard->export(true);
- }
+ prepare_for_export($record, $source);
$record['sourceid'] = $s;
$key = rcube_addressbook::compose_contact_key($record, $sort_col);
@@ -90,23 +114,7 @@ else if (!empty($_REQUEST['_cid'])) {
while ($record = $result->next()) {
// because vcard_map is per-source we need to create vcard here
- if (empty($record['vcard']) || empty($record['name'])) {
- $vcard = new rcube_vcard();
- $vcard->extend_fieldmap($source->vcard_map);
- $vcard->load($record['vcard']);
- $vcard->reset();
-
- foreach ($record as $key => $values) {
- list($field, $section) = explode(':', $key);
- foreach ((array)$values as $value) {
- if (is_array($value) || @strlen($value)) {
- $vcard->set($field, $value, strtoupper($section));
- }
- }
- }
-
- $record['vcard'] = $vcard->export(true);
- }
+ prepare_for_export($record, $source);
$record['sourceid'] = $s;
$key = rcube_addressbook::compose_contact_key($record, $sort_col);
@@ -136,30 +144,12 @@ header('Content-Type: text/x-vcard; charset='.RCMAIL_CHARSET);
header('Content-Disposition: attachment; filename="contacts.vcf"');
while ($result && ($row = $result->next())) {
- // we already have a vcard record
- if ($row['vcard'] && $row['name']) {
- // fix folding and end-of-line chars
- $row['vcard'] = preg_replace('/\r|\n\s+/', '', $row['vcard']);
- $row['vcard'] = preg_replace('/\n/', rcube_vcard::$eol, $row['vcard']);
- echo rcube_vcard::rfc2425_fold($row['vcard']) . rcube_vcard::$eol;
- }
- // copy values into vcard object
- else {
- $vcard = new rcube_vcard();
- $vcard->extend_fieldmap($CONTACTS->vcard_map);
- $vcard->load($row['vcard']);
- $vcard->reset();
+ prepare_for_export($row, $CONTACTS);
- foreach ($row as $key => $values) {
- list($field, $section) = explode(':', $key);
- foreach ((array)$values as $value) {
- if (is_array($value) || @strlen($value))
- $vcard->set($field, $value, strtoupper($section));
- }
- }
-
- echo $vcard->export(true) . rcube_vcard::$eol;
- }
+ // fix folding and end-of-line chars
+ $row['vcard'] = preg_replace('/\r|\n\s+/', '', $row['vcard']);
+ $row['vcard'] = preg_replace('/\n/', rcube_vcard::$eol, $row['vcard']);
+ echo rcube_vcard::rfc2425_fold($row['vcard']) . rcube_vcard::$eol;
}
exit;
diff --git a/program/steps/addressbook/import.inc b/program/steps/addressbook/import.inc
index 915aac884..60f5d7b61 100644
--- a/program/steps/addressbook/import.inc
+++ b/program/steps/addressbook/import.inc
@@ -40,6 +40,7 @@ function rcmail_import_form($attrib)
'multiple' => 'multiple',
));
$form = html::p(null, html::label('rcmimportfile', rcube_label('importfromfile')) . $upload->show());
+ $table = new html_table(array('cols' => 2));
// addressbook selector
if (count($writable_books) > 1) {
@@ -48,17 +49,31 @@ function rcmail_import_form($attrib)
foreach ($writable_books as $book)
$select->add($book['name'], $book['id']);
- $form .= html::p(null, html::label('rcmimporttarget', rcube_label('importtarget'))
- . $select->show($target));
+ $table->add('title', html::label('rcmimporttarget', rcube_label('importtarget')));
+ $table->add(null, $select->show($target));
}
else {
$abook = new html_hiddenfield(array('name' => '_target', 'value' => key($writable_books)));
$form .= $abook->show();
}
+ // selector for group import options
+ if (count($writable_books) >= 1 || $writable_books[0]->groups) {
+ $select = new html_select(array('name' => '_groups', 'id' => 'rcmimportgroups', 'is_escaped' => true));
+ $select->add(rcube_label('none'), '0');
+ $select->add(rcube_label('importgroupsall'), '1');
+ $select->add(rcube_label('importgroupsexisting'), '2');
+
+ $table->add('title', html::label('rcmimportgroups', rcube_label('importgroups')));
+ $table->add(null, $select->show(get_input_value('_groups', RCUBE_INPUT_GPC)));
+ }
+
+ // checkbox to replace the entire address book
$check_replace = new html_checkbox(array('name' => '_replace', 'value' => 1, 'id' => 'rcmimportreplace'));
- $form .= html::p(null, $check_replace->show(get_input_value('_replace', RCUBE_INPUT_GPC)) .
- html::label('rcmimportreplace', rcube_label('importreplace')));
+ $table->add('title', html::label('rcmimportreplace', rcube_label('importreplace')));
+ $table->add(null, $check_replace->show(get_input_value('_replace', RCUBE_INPUT_GPC)));
+
+ $form .= $table->show(array('id' => null) + $attrib);
$OUTPUT->set_env('writable_source', !empty($writable_books));
$OUTPUT->add_label('selectimportfile','importwait');
@@ -134,19 +149,50 @@ function rcmail_import_buttons($attrib)
}
+/**
+ * Returns the matching group id. If group doesn't exist, it'll be created if allowed.
+ */
+function rcmail_import_group_id($group_name, $CONTACTS, $create, &$import_groups)
+{
+ $group_id = 0;
+ foreach ($import_groups as $key => $group) {
+ if (strtolower($group['name']) == strtolower($group_name)) {
+ $group_id = $group['ID'];
+ break;
+ }
+ }
+
+ // create a new group
+ if (!$group_id && $create) {
+ $new_group = $CONTACTS->create_group($group_name);
+ if (!$new_group['ID'])
+ $new_group['ID'] = $new_group['id'];
+ $import_groups[] = $new_group;
+ $group_id = $new_group['ID'];
+ }
+
+ return $group_id;
+}
+
+
/** The import process **/
$importstep = 'rcmail_import_form';
if (is_array($_FILES['_file'])) {
- $replace = (bool)get_input_value('_replace', RCUBE_INPUT_GPC);
- $target = get_input_value('_target', RCUBE_INPUT_GPC);
+ $replace = (bool)get_input_value('_replace', RCUBE_INPUT_GPC);
+ $target = get_input_value('_target', RCUBE_INPUT_GPC);
+ $with_groups = intval(get_input_value('_groups', RCUBE_INPUT_GPC));
$vcards = array();
$upload_error = null;
$CONTACTS = $RCMAIL->get_address_book($target, true);
+ if (!$CONTACTS->groups) {
+ $with_groups = false;
+ }
+
if ($CONTACTS->readonly) {
$OUTPUT->show_message('addresswriterror', 'error');
}
@@ -206,6 +252,10 @@ if (is_array($_FILES['_file'])) {
$CONTACTS->delete_all();
}
+ if ($with_groups) {
+ $import_groups = $CONTACTS->list_groups();
+ }
+
foreach ($vcards as $vcard) {
$a_record = $vcard->get_assoc();
@@ -258,6 +308,15 @@ if (is_array($_FILES['_file'])) {
$success = $plugin['result'];
if ($success) {
+ // assign groups for this contact (if enabled)
+ if ($with_groups && !empty($a_record['groups'])) {
+ foreach (explode(',', $a_record['groups'][0]) as $group_name) {
+ if ($group_id = rcmail_import_group_id($group_name, $CONTACTS, $with_groups == 1, $import_groups)) {
+ $CONTACTS->add_to_group($group_id, $success);
+ }
+ }
+ }
+
$IMPORT_STATS->inserted++;
$IMPORT_STATS->names[] = $a_record['name'] ? $a_record['name'] : $email;
}
diff --git a/program/steps/addressbook/save.inc b/program/steps/addressbook/save.inc
index e7e5efc63..2adc53bcf 100644
--- a/program/steps/addressbook/save.inc
+++ b/program/steps/addressbook/save.inc
@@ -59,15 +59,34 @@ foreach ($GLOBALS['CONTACT_COLTYPES'] as $col => $colprop) {
}
// assign values and subtypes
else if (is_array($_POST[$fname])) {
- $values = get_input_value($fname, RCUBE_INPUT_POST, true);
+ $values = get_input_value($fname, RCUBE_INPUT_POST, true);
$subtypes = get_input_value('_subtype_' . $col, RCUBE_INPUT_POST);
+
foreach ($values as $i => $val) {
+ if ($col == 'email') {
+ // extract email from full address specification, e.g. "Name" <addr@domain.tld>
+ $addr = rcube_mime::decode_address_list($val, 1, false);
+ if (!empty($addr) && ($addr = array_pop($addr)) && $addr['mailto']) {
+ $val = $addr['mailto'];
+ }
+ }
+
$subtype = $subtypes[$i] ? ':'.$subtypes[$i] : '';
$a_record[$col.$subtype][] = $val;
}
}
else if (isset($_POST[$fname])) {
$a_record[$col] = get_input_value($fname, RCUBE_INPUT_POST, true);
+
+ // normalize the submitted date strings
+ if ($colprop['type'] == 'date') {
+ if ($timestamp = rcube_utils::strtotime($a_record[$col])) {
+ $a_record[$col] = date('Y-m-d', $timestamp);
+ }
+ else {
+ unset($a_record[$col]);
+ }
+ }
}
}
@@ -75,8 +94,10 @@ foreach ($GLOBALS['CONTACT_COLTYPES'] as $col => $colprop) {
if (empty($a_record['name'])) {
$a_record['name'] = rcube_addressbook::compose_display_name($a_record, true);
// Reset it if equals to email address (from compose_display_name())
- if ($a_record['name'] == $a_record['email'][0])
+ $email = rcube_addressbook::get_col_values('email', $a_record, true);
+ if ($a_record['name'] == $email[0]) {
$a_record['name'] = '';
+ }
}
// do input checks (delegated to $CONTACTS instance)
diff --git a/program/steps/mail/attachments.inc b/program/steps/mail/attachments.inc
index f83f6892e..85aa9542b 100644
--- a/program/steps/mail/attachments.inc
+++ b/program/steps/mail/attachments.inc
@@ -118,9 +118,12 @@ if (is_array($_FILES['_attachments']['tmp_name'])) {
'alt' => rcube_label('delete')
));
}
- else {
+ else if ($COMPOSE['textbuttons']) {
$button = Q(rcube_label('delete'));
}
+ else {
+ $button = '';
+ }
$content = html::a(array(
'href' => "#delete",
diff --git a/program/steps/mail/compose.inc b/program/steps/mail/compose.inc
index b56f5b5b4..30c9f79fb 100644
--- a/program/steps/mail/compose.inc
+++ b/program/steps/mail/compose.inc
@@ -168,6 +168,8 @@ else if ($msg_uid = $COMPOSE['param']['forward_uid']) {
else if ($msg_uid = $COMPOSE['param']['uid']) {
$compose_mode = RCUBE_COMPOSE_EDIT;
}
+
+$COMPOSE['mode'] = $compose_mode;
$OUTPUT->set_env('compose_mode', $compose_mode);
$config_show_sig = $RCMAIL->config->get('show_sig', 1);
@@ -380,7 +382,12 @@ foreach ($parts as $header) {
$mailto = format_email(rcube_idn_to_utf8($addr_part['mailto']));
if (!in_array($mailto, $a_recipients)
- && ($header == 'to' || empty($MESSAGE->compose['from_email']) || $mailto != $MESSAGE->compose['from_email'])
+ && (
+ $header == 'to'
+ || $compose_mode != RCUBE_COMPOSE_REPLY
+ || empty($MESSAGE->compose['from_email'])
+ || $mailto != $MESSAGE->compose['from_email']
+ )
) {
if ($addr_part['name'] && $addr_part['mailto'] != $addr_part['name'])
$string = format_email_recipient($mailto, $addr_part['name']);
@@ -1366,8 +1373,9 @@ function rcmail_compose_attachment_list($attrib)
if (!$attrib['id'])
$attrib['id'] = 'rcmAttachmentList';
- $out = "\n";
+ $out = "\n";
$jslist = array();
+ $button = '';
if (is_array($COMPOSE['attachments'])) {
if ($attrib['deleteicon']) {
@@ -1376,27 +1384,38 @@ function rcmail_compose_attachment_list($attrib)
'alt' => rcube_label('delete')
));
}
- else
+ else if (rcube_utils::get_boolean($attrib['textbuttons'])) {
$button = Q(rcube_label('delete'));
+ }
foreach ($COMPOSE['attachments'] as $id => $a_prop) {
if (empty($a_prop))
continue;
- $out .= html::tag('li', array('id' => 'rcmfile'.$id, 'class' => rcmail_filetype2classname($a_prop['mimetype'], $a_prop['name'])),
+ $out .= html::tag('li',
+ array(
+ 'id' => 'rcmfile'.$id,
+ 'class' => rcmail_filetype2classname($a_prop['mimetype'], $a_prop['name']),
+ 'onmouseover' => "rcube_webmail.long_subject_title_ex(this, 0)",
+ ),
html::a(array(
'href' => "#delete",
'title' => rcube_label('delete'),
'onclick' => sprintf("return %s.command('remove-attachment','rcmfile%s', this)", JS_OBJECT_NAME, $id),
- 'class' => 'delete'),
- $button) . Q($a_prop['name']));
+ 'class' => 'delete'
+ ),
+ $button
+ ) . Q($a_prop['name'])
+ );
- $jslist['rcmfile'.$id] = array('name' => $a_prop['name'], 'complete' => true, 'mimetype' => $a_prop['mimetype']);
+ $jslist['rcmfile'.$id] = array('name' => $a_prop['name'], 'complete' => true, 'mimetype' => $a_prop['mimetype']);
}
}
if ($attrib['deleteicon'])
$COMPOSE['deleteicon'] = $CONFIG['skin_path'] . $attrib['deleteicon'];
+ else if (rcube_utils::get_boolean($attrib['textbuttons']))
+ $COMPOSE['textbuttons'] = true;
if ($attrib['cancelicon'])
$OUTPUT->set_env('cancelicon', $CONFIG['skin_path'] . $attrib['cancelicon']);
if ($attrib['loadingicon'])
diff --git a/program/steps/mail/func.inc b/program/steps/mail/func.inc
index 018a31b84..a7d9ca240 100644
--- a/program/steps/mail/func.inc
+++ b/program/steps/mail/func.inc
@@ -1730,7 +1730,7 @@ function rcmail_identity_select($MESSAGE, $identities = null, $compose_mode = 'r
$a_to = rcube_mime::decode_address_list($MESSAGE->headers->to, null, true, $MESSAGE->headers->charset);
foreach ($a_to as $addr) {
if (!empty($addr['mailto'])) {
- $a_recipients[] = format_email($addr['mailto']);
+ $a_recipients[] = strtolower($addr['mailto']);
$a_names[] = $addr['name'];
}
}
@@ -1739,7 +1739,7 @@ function rcmail_identity_select($MESSAGE, $identities = null, $compose_mode = 'r
$a_cc = rcube_mime::decode_address_list($MESSAGE->headers->cc, null, true, $MESSAGE->headers->charset);
foreach ($a_cc as $addr) {
if (!empty($addr['mailto'])) {
- $a_recipients[] = format_email($addr['mailto']);
+ $a_recipients[] = strtolower($addr['mailto']);
$a_names[] = $addr['name'];
}
}
@@ -1765,7 +1765,7 @@ function rcmail_identity_select($MESSAGE, $identities = null, $compose_mode = 'r
break;
}
// use replied message recipients
- else if (($found = array_search($ident['email_ascii'], $a_recipients)) !== false) {
+ else if (($found = array_search(strtolower($ident['email_ascii']), $a_recipients)) !== false) {
if ($found_idx === null) {
$found_idx = $idx;
}
diff --git a/program/steps/mail/sendmail.inc b/program/steps/mail/sendmail.inc
index 436b4eea7..8fe149611 100644
--- a/program/steps/mail/sendmail.inc
+++ b/program/steps/mail/sendmail.inc
@@ -825,15 +825,24 @@ if ($savedraft) {
// start the auto-save timer again
$OUTPUT->command('auto_save_start');
-
- $OUTPUT->send('iframe');
}
else {
+ $folders = array();
+
+ if ($COMPOSE['mode'] == 'reply' || $COMPOSE['mode'] == 'forward')
+ $folders[] = $COMPOSE['mailbox'];
+
rcmail_compose_cleanup($COMPOSE_ID);
if ($store_folder && !$saved)
- $OUTPUT->command('sent_successfully', 'error', rcube_label('errorsavingsent'));
- else
- $OUTPUT->command('sent_successfully', 'confirmation', rcube_label('messagesent'), $store_target);
- $OUTPUT->send('iframe');
+ $OUTPUT->command('sent_successfully', 'error', rcube_label('errorsavingsent'), $folders);
+ else {
+ if ($store_folder) {
+ $folders[] = $store_target;
+ }
+
+ $OUTPUT->command('sent_successfully', 'confirmation', rcube_label('messagesent'), $folders);
+ }
}
+
+$OUTPUT->send('iframe');
diff --git a/program/steps/mail/show.inc b/program/steps/mail/show.inc
index 59f4d55e1..9d85f9c8f 100644
--- a/program/steps/mail/show.inc
+++ b/program/steps/mail/show.inc
@@ -175,9 +175,9 @@ function rcmail_message_attachments($attrib)
$ol .= html::tag('li', null, Q(sprintf("%s (%s)", $filename, $size)));
}
else {
- if (mb_strlen($filename) > 50) {
+ if ($attrib['maxlength'] && mb_strlen($filename) > $attrib['maxlength']) {
$title = $filename;
- $filename = abbreviate_string($filename, 50);
+ $filename = abbreviate_string($filename, $attrib['maxlength']);
}
else {
$title = '';
@@ -190,6 +190,7 @@ function rcmail_message_attachments($attrib)
'href' => $MESSAGE->get_part_url($attach_prop->mime_id, false),
'onclick' => sprintf('return %s.command(\'load-attachment\',\'%s\',this)',
JS_OBJECT_NAME, $attach_prop->mime_id),
+ 'onmouseover' => $title ? '' : 'rcube_webmail.long_subject_title_ex(this, 0)',
'title' => Q($title),
), Q($filename));
$ol .= html::tag('li', array('class' => $class, 'id' => $id), $link);
diff --git a/program/steps/settings/edit_prefs.inc b/program/steps/settings/edit_prefs.inc
index 468e4994d..adf6b1623 100644
--- a/program/steps/settings/edit_prefs.inc
+++ b/program/steps/settings/edit_prefs.inc
@@ -40,24 +40,21 @@ function rcmail_user_prefs_form($attrib)
$out = $form_start;
- foreach ($SECTIONS[$CURR_SECTION]['blocks'] as $block) {
+ foreach ($SECTIONS[$CURR_SECTION]['blocks'] as $class => $block) {
if (!empty($block['options'])) {
$table = new html_table(array('cols' => 2));
foreach ($block['options'] as $option) {
- if ($option['advanced'])
- $table->set_row_attribs('advanced');
-
if (isset($option['title'])) {
$table->add('title', $option['title']);
- $table->add(null, $option['content']);
+ $table->add(null, $option['content']);
}
else {
$table->add(array('colspan' => 2), $option['content']);
}
}
- $out .= html::tag('fieldset', null, html::tag('legend', null, $block['name']) . $table->show($attrib));
+ $out .= html::tag('fieldset', $class, html::tag('legend', null, $block['name']) . $table->show($attrib));
}
else if (!empty($block['content'])) {
$out .= html::tag('fieldset', null, html::tag('legend', null, $block['name']) . $block['content']);
diff --git a/program/steps/settings/func.inc b/program/steps/settings/func.inc
index 57c52a01e..b492c9644 100644
--- a/program/steps/settings/func.inc
+++ b/program/steps/settings/func.inc
@@ -158,6 +158,7 @@ function rcmail_user_prefs($current = null)
'main' => array('name' => Q(rcube_label('mainoptions'))),
'skin' => array('name' => Q(rcube_label('skin'))),
'browser' => array('name' => Q(rcube_label('browseroptions'))),
+ 'advanced'=> array('name' => Q(rcube_label('advancedoptions'))),
);
// language selection
@@ -367,6 +368,7 @@ function rcmail_user_prefs($current = null)
$blocks = array(
'main' => array('name' => Q(rcube_label('mainoptions'))),
'new_message' => array('name' => Q(rcube_label('newmessage'))),
+ 'advanced' => array('name' => Q(rcube_label('advancedoptions'))),
);
// show config parameter for preview pane
@@ -488,6 +490,7 @@ function rcmail_user_prefs($current = null)
case 'mailview':
$blocks = array(
'main' => array('name' => Q(rcube_label('mainoptions'))),
+ 'advanced' => array('name' => Q(rcube_label('advancedoptions'))),
);
// show checkbox to open message view in new window
@@ -543,7 +546,7 @@ function rcmail_user_prefs($current = null)
$field_id = 'rcmfd_default_charset';
- $blocks['main']['options']['default_charset'] = array(
+ $blocks['advanced']['options']['default_charset'] = array(
'title' => html::label($field_id, Q(rcube_label('defaultcharset'))),
'content' => $RCMAIL->output->charset_selector(array(
'id' => $field_id, 'name' => '_default_charset', 'selected' => $config['default_charset']
@@ -605,6 +608,7 @@ function rcmail_user_prefs($current = null)
'main' => array('name' => Q(rcube_label('mainoptions'))),
'sig' => array('name' => Q(rcube_label('signatureoptions'))),
'spellcheck' => array('name' => Q(rcube_label('spellcheckoptions'))),
+ 'advanced' => array('name' => Q(rcube_label('advancedoptions'))),
);
// show checkbox to compose messages in a new window
@@ -673,8 +677,7 @@ function rcmail_user_prefs($current = null)
$select->add(rcube_label('miscfolding'), 1);
$select->add(rcube_label('2047folding'), 2);
- $blocks['main']['options']['mime_param_folding'] = array(
- 'advanced' => true,
+ $blocks['advanced']['options']['mime_param_folding'] = array(
'title' => html::label($field_id, Q(rcube_label('mimeparamfolding'))),
'content' => $select->show($config['mime_param_folding']),
);
@@ -688,8 +691,7 @@ function rcmail_user_prefs($current = null)
$field_id = 'rcmfd_force_7bit';
$input = new html_checkbox(array('name' => '_force_7bit', 'id' => $field_id, 'value' => 1));
- $blocks['main']['options']['force_7bit'] = array(
- 'advanced' => true,
+ $blocks['advanced']['options']['force_7bit'] = array(
'title' => html::label($field_id, Q(rcube_label('force7bit'))),
'content' => $input->show($config['force_7bit']?1:0),
);
@@ -871,7 +873,8 @@ function rcmail_user_prefs($current = null)
// Addressbook config
case 'addressbook':
$blocks = array(
- 'main' => array('name' => Q(rcube_label('mainoptions'))),
+ 'main' => array('name' => Q(rcube_label('mainoptions'))),
+ 'advanced' => array('name' => Q(rcube_label('advancedoptions'))),
);
if (!isset($no_override['default_addressbook'])
@@ -967,7 +970,8 @@ function rcmail_user_prefs($current = null)
// Special IMAP folders
case 'folders':
$blocks = array(
- 'main' => array('name' => Q(rcube_label('mainoptions'))),
+ 'main' => array('name' => Q(rcube_label('mainoptions'))),
+ 'advanced' => array('name' => Q(rcube_label('advancedoptions'))),
);
if (!isset($no_override['show_real_foldernames'])) {
@@ -1048,6 +1052,7 @@ function rcmail_user_prefs($current = null)
$blocks = array(
'main' => array('name' => Q(rcube_label('mainoptions'))),
'maintenance' => array('name' => Q(rcube_label('maintenance'))),
+ 'advanced' => array('name' => Q(rcube_label('advancedoptions'))),
);
if (!isset($no_override['read_when_deleted'])) {