summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG38
-rwxr-xr-xbin/updatedb.sh7
-rw-r--r--config/defaults.inc.php11
-rw-r--r--plugins/managesieve/Changelog3
-rw-r--r--plugins/managesieve/lib/Roundcube/rcube_sieve_engine.php78
-rw-r--r--plugins/managesieve/package.xml14
-rw-r--r--plugins/newmail_notifier/config.inc.php.dist3
-rw-r--r--plugins/newmail_notifier/localization/en_US.inc1
-rw-r--r--plugins/newmail_notifier/newmail_notifier.js14
-rw-r--r--plugins/newmail_notifier/newmail_notifier.php25
-rw-r--r--plugins/newmail_notifier/package.xml4
-rw-r--r--plugins/password/config.inc.php.dist3
-rw-r--r--plugins/password/drivers/virtualmin.php4
-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
-rw-r--r--skins/classic/mail.css7
-rw-r--r--skins/classic/templates/message.html2
-rw-r--r--skins/classic/templates/messagepreview.html2
-rw-r--r--skins/larry/addressbook.css5
-rw-r--r--skins/larry/settings.css20
-rw-r--r--skins/larry/styles.css7
-rw-r--r--skins/larry/templates/importcontacts.html2
-rw-r--r--skins/larry/ui.js13
-rw-r--r--tests/Framework/Browser.php203
50 files changed, 1012 insertions, 299 deletions
diff --git a/CHANGELOG b/CHANGELOG
index 796bbc5b5..f18d10d94 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,6 +1,18 @@
CHANGELOG Roundcube Webmail
===========================
+- Display full attachment name using title attribute when name is too long to display (#1489320)
+- Fix XSS issue in addressbook group name field [CVE-2013-5646] (#1489333)
+- Fix attachment icon issue when rare font/language is used (#1489326)
+- After message is sent refresh messages list of replied message folder (#1489249)
+- Add option force specified domain in user login - username_domain_forced (#1489264)
+- Fix expanded thread root message styling after refreshing messages list (#1489327)
+- Fix issue where From address was removed from Cc and Bcc fields when editing a draft (#1489319)
+- Add option to import Vcards with group assignments
+- Save groups membership in Vcard export (#1488509)
+- Workaround broken PHP function timezone_name_from_abbr (#1489261)
+- Fix error_reporting directive check (#1489323)
+- Make cached message size limit configurable - messages_cache_threshold (#1489317)
- Log also failed logins to userlogins log
- Add temp_dir_ttl configuration option (#1489304)
- Allow setting INBOX as Sent folder (#1489219)
@@ -62,6 +74,8 @@ CHANGELOG Roundcube Webmail
RELEASE 0.9.4
-------------
+- Make identities matching case insensitive (#1485480)
+- Fix issue where too big message data was stored in cache causing sql errors (#1489316)
- Fix iframe scrollbars on webkit desktop browsers (#1489306)
- Fix issue where legacy config was overriden by default config (#1489288)
- Fix newmail_notifier issue where favicon wasn't changed back to default (#1489313)
@@ -84,8 +98,8 @@ RELEASE 0.9.3
- Fix base URL resolving on attribute values with no quotes (#1489275)
- Fix wrong handling of links with '|' character (#1489276)
- Fix colorspace issue on image conversion using ImageMagick (#1489270)
-- Fix XSS vulnerability when editing a message "as new" or draft (#1489251)
-- Fix XSS vulnerability when saving HTML signatures (#1489251)
+- Fix XSS vulnerability when editing a message "as new" or draft [CVE-2013-5645] (#1489251)
+- Fix XSS vulnerability when saving HTML signatures [CVE-2013-5645] (#1489251)
- Fix rewrite rule in .htaccess (#1489240)
- Fix detecting Turkish language in ISO-8859-9 encoding (#1489252)
- Fix identity-selection using Return-Path headers (#1489241)
@@ -305,7 +319,7 @@ RELEASE 0.8.5
- Fix #countcontrols issue in IE<=8 when text is very long (#1488890)
- Fix unwanted horizontal scrollbar in message preview header (#1488866)
- Add workaround for IE<=8 bug where Content-Disposition:inline was ignored (#1488844)
-- Fix XSS vulnerability in vbscript: and data:text links handling (#1488850)
+- Fix XSS vulnerability in vbscript: and data:text links handling [CVE-2012-6121] (#1488850)
- Fix absolute positioning in HTML messages (#1488819)
- Fix cache (in)validation after setting \Deleted flag
- Fix keybord events on messages list in opera browser (#1488823)
@@ -360,8 +374,8 @@ RELEASE 0.8.1
- Fix bug where domain name was converted to lower-case even with login_lc=false (#1488593)
- Fix lower-casing email address on replies (#1488598)
- Fix line separator in exported messages (#1488603)
-- Fix XSS issue where plain signatures wasn't secured in HTML mode (#1488613)
-- Fix XSS issue where href="javascript:" wasn't secured (#1488613)
+- Fix XSS issue where plain signatures wasn't secured in HTML mode [CVE-2012-4668] (#1488613)
+- Fix XSS issue where href="javascript:" wasn't secured [CVE-2012-3508] (#1488613)
- Fix impossible to create message with empty plain text part (#1488610)
- Fix stripped apostrophes when replying in plain text to HTML message (#1488606)
- Fix inactive Save search option after advanced search (#1488607)
@@ -396,7 +410,7 @@ RELEASE 0.8.0
- Fix removing contact photo using LDAP addressbook (#1488420)
- Fix storing X-ANNIVERSARY date in vCard format (#1488527)
- Update to Mail_Mime-1.8.5 (#1488521)
-- Fix XSS vulnerability in message subject handling using Larry skin (#1488519)
+- Fix XSS vulnerability in message subject handling using Larry skin [CVE-2012-3507] (#1488519)
- Fix handling of links with various URI schemes e.g. "skype:" (#1488106)
- Fix handling of links inside PRE elements on html to text conversion
- Fix indexing of links on html to text conversion
@@ -523,7 +537,7 @@ RELEASE 0.7
- Improved handling of some malformed values encoded with quoted-printable (#1488232)
- Add possibility to do LDAP bind before searching for bind DN
- Fix handling of empty <U> tags in HTML messages (#1488225)
-- Add content filter for embedded attachments to protect from XSS on IE (#1487895)
+- Add content filter for embedded attachments to protect from XSS on IE [CVE-2012-1253] (#1487895)
- Use strpos() instead of strstr() when possible (#1488211)
- Fix handling HTML entities when converting HTML to text (#1488212)
- Fix fit_string_to_size() renders browser and ui unresponsive (#1488207)
@@ -691,7 +705,7 @@ RELEASE 0.6-beta
RELEASE 0.5.4
-------------
-- Fix XSS vulnerability in UI messages (#1488030)
+- Fix XSS vulnerability in UI messages [CVE-2011-2937] (#1488030)
RELEASE 0.5.3
-------------
@@ -741,8 +755,8 @@ RELEASE 0.5.1
- Security: add optional referer check to prevent CSRF in GET requests
- Fix email_dns_check setting not used for identities/contacts (#1487740)
- Fix ICANN example addresses doesn't validate (#1487742)
-- Security: protect login form submission from CSRF
-- Security: prevent from relaying malicious requests through modcss.inc
+- Security: protect login form submission from CSRF [CVE-2011-1491]
+- Security: prevent from relaying malicious requests through modcss.inc [CVE-2011-1492]
- Fix handling of non-image attachments in multipart/related messages (#1487750)
- Fix IDNA support when IDN/INTL modules are in use (#1487742)
- Fix handling of invalid HTML comments in messages (#1487759)
@@ -1185,7 +1199,7 @@ RELEASE 0.3-RC1
---------------
- Fix import of vCard entries with params (#1485453)
- Fix HTML messages output with empty block elements (#1485974)
-- Use request tokens to protect POST requests from CSRF
+- Use request tokens to protect POST requests from CSRF [CVE-2009-4076, CVE-2009-4077]
- Added hook when killing a session
- Added hook to write_log function (#1485971)
- Performance improvements by use UID commands (#1485690)
@@ -1312,7 +1326,7 @@ RELEASE 0.2.1
- Fix large search results on server without SORT capability (#1485668)
- Get rid of preg_replace() with eval modifier and create_function usage (#1485686)
- Bring back <base> and <link> tags in HTML messages
-- Fix XSS vulnerability through background attributes as reported by Julien Cayssol
+- Fix XSS vulnerability through background attributes [CVE-2009-0413]
- Fix problems with backslash as IMAP hierarchy delimiter (#1484467)
- Secure vcard export by getting rid of preg's 'e' modifier use (#1485689)
- Fix authentication when submitting form with existing session (#1485679)
diff --git a/bin/updatedb.sh b/bin/updatedb.sh
index b4ed8b7ba..1f5e18434 100755
--- a/bin/updatedb.sh
+++ b/bin/updatedb.sh
@@ -72,13 +72,20 @@ if (!$version && $opts['version']) {
'0.2-alpha' => 2008040500,
'0.2-beta' => 2008060900,
'0.2-stable' => 2008092100,
+ '0.2.1' => 2008092100,
+ '0.2.2' => 2008092100,
'0.3-stable' => 2008092100,
'0.3.1' => 2009090400,
'0.4-beta' => 2009103100,
+ '0.4' => 2010042300,
+ '0.4.1' => 2010042300,
'0.4.2' => 2010042300,
'0.5-beta' => 2010100600,
'0.5' => 2010100600,
'0.5.1' => 2010100600,
+ '0.5.2' => 2010100600,
+ '0.5.3' => 2010100600,
+ '0.5.4' => 2010100600,
'0.6-beta' => 2011011200,
'0.6' => 2011011200,
'0.7-beta' => 2011092800,
diff --git a/config/defaults.inc.php b/config/defaults.inc.php
index 268bf009a..97c8f3b25 100644
--- a/config/defaults.inc.php
+++ b/config/defaults.inc.php
@@ -164,6 +164,11 @@ $config['imap_cache_ttl'] = '10d';
// Lifetime of messages cache. Possible units: s, m, h, d, w
$config['messages_cache_ttl'] = '10d';
+// Maximum cached message size in kilobytes.
+// Note: On MySQL this should be less than (max_allowed_packet - 30%)
+$config['messages_cache_threshold'] = 50;
+
+
// ----------------------------------
// SMTP
// ----------------------------------
@@ -240,6 +245,8 @@ $config['support_url'] = '';
// replace Roundcube logo with this image
// specify an URL relative to the document root of this Roundcube installation
+// an array can be used to specify different logos for specific template files, '*' for default logo
+// for example array("*" => "/images/roundcube_logo.png", "messageprint" => "/images/roundcube_logo_print.png")
$config['skin_logo'] = null;
// automatically create a new Roundcube user when log-in the first time.
@@ -341,6 +348,10 @@ $config['des_key'] = 'rcmail-!24ByteDESkey*Str';
// For example %n = mail.domain.tld, %t = domain.tld
$config['username_domain'] = '';
+// Force domain configured in username_domain to be used for login.
+// Any domain in username will be replaced by username_domain.
+$config['username_domain_forced'] = false;
+
// This domain will be used to form e-mail addresses of new users
// Specify an array with 'host' => 'domain' values to support multiple hosts
// Supported replacement variables:
diff --git a/plugins/managesieve/Changelog b/plugins/managesieve/Changelog
index 60b2f1831..e660ee1ee 100644
--- a/plugins/managesieve/Changelog
+++ b/plugins/managesieve/Changelog
@@ -1,3 +1,5 @@
+* version 7.0 [2013-09-09]
+-----------------------------------------------------------
- Add vacation-seconds extension support (RFC 6131)
- Several script parser code improvements
- Support string list arguments in filter form (#1489018)
@@ -5,6 +7,7 @@
- Split plugin file into two files
- Fix handling of &, <, > characters in scripts/filter names (#1489208)
- Support 'keep' action (#1489226)
+- Add common headers to header selector (#1489271)
* version 6.2 [2013-02-17]
-----------------------------------------------------------
diff --git a/plugins/managesieve/lib/Roundcube/rcube_sieve_engine.php b/plugins/managesieve/lib/Roundcube/rcube_sieve_engine.php
index bbbfa9d91..e4efef5b3 100644
--- a/plugins/managesieve/lib/Roundcube/rcube_sieve_engine.php
+++ b/plugins/managesieve/lib/Roundcube/rcube_sieve_engine.php
@@ -195,7 +195,7 @@ class rcube_sieve_engine
}
else {
$this->exts = $this->sieve->get_extensions();
- $this->script = $this->sieve->script->as_array();
+ $this->init_script();
$this->rc->output->set_env('currentset', $this->sieve->current);
$_SESSION['managesieve_current'] = $this->sieve->current;
}
@@ -742,13 +742,22 @@ class rcube_sieve_engine
$cust_header = (is_array($headers) && count($headers) == 1) ? $headers[0] : $headers;
}
+ $header = $header == '...' ? $cust_header : $header;
+
+ if (is_array($header)) {
+ foreach ($header as $h_index => $val) {
+ if (isset($this->headers[$val])) {
+ $header[$h_index] = $this->headers[$val];
+ }
+ }
+ }
+
if ($type == 'exists') {
$this->form['tests'][$i]['test'] = 'exists';
- $this->form['tests'][$i]['arg'] = $header == '...' ? $cust_header : $header;
+ $this->form['tests'][$i]['arg'] = $header;
}
else {
- $test = 'header';
- $header = $header == '...' ? $cust_header : $header;
+ $test = 'header';
if ($mod == 'address' || $mod == 'envelope') {
$found = false;
@@ -1258,27 +1267,33 @@ class rcube_sieve_engine
$select_header = new html_select(array('name' => "_header[]", 'id' => 'header'.$id,
'onchange' => 'rule_header_select(' .$id .')'));
- foreach ($this->headers as $name => $val)
- $select_header->add(rcube::Q($this->plugin->gettext($name)), Q($val));
- $select_header->add(rcube::Q($this->plugin->gettext('...')), '...');
+ foreach ($this->headers as $index => $header) {
+ $header = $this->rc->text_exists($index) ? $this->plugin->gettext($index) : $header;
+ $select_header->add($header, $index);
+ }
+ $select_header->add($this->plugin->gettext('...'), '...');
if (in_array('body', $this->exts))
- $select_header->add(rcube::Q($this->plugin->gettext('body')), 'body');
- $select_header->add(rcube::Q($this->plugin->gettext('size')), 'size');
+ $select_header->add($this->plugin->gettext('body'), 'body');
+ $select_header->add($this->plugin->gettext('size'), 'size');
if (in_array('date', $this->exts)) {
- $select_header->add(rcube::Q($this->plugin->gettext('datetest')), 'date');
- $select_header->add(rcube::Q($this->plugin->gettext('currdate')), 'currentdate');
+ $select_header->add($this->plugin->gettext('datetest'), 'date');
+ $select_header->add($this->plugin->gettext('currdate'), 'currentdate');
}
if (isset($rule['test'])) {
if (in_array($rule['test'], array('header', 'address', 'envelope'))
- && !is_array($rule['arg1']) && in_array($rule['arg1'], $this->headers)
+ && !is_array($rule['arg1'])
+ && ($header = strtolower($rule['arg1']))
+ && isset($this->headers[$header])
) {
- $test = $rule['arg1'];
+ $test = $header;
}
else if ($rule['test'] == 'exists'
- && !is_array($rule['arg']) && in_array($rule['arg'], $this->headers)
+ && !is_array($rule['arg'])
+ && ($header = strtolower($rule['arg']))
+ && isset($this->headers[$header])
) {
- $test = $rule['arg'];
+ $test = $header;
}
else if (in_array($rule['test'], array('size', 'body', 'date', 'currentdate'))) {
$test = $rule['test'];
@@ -2120,4 +2135,37 @@ class rcube_sieve_engine
return $result;
}
+
+ /**
+ * Initializes internal script data
+ */
+ private function init_script()
+ {
+ $this->script = $this->sieve->script->as_array();
+
+ if (!$this->script) {
+ return;
+ }
+
+ $headers = array();
+
+ // find common headers used in script, will be added to the list
+ // of available (predefined) headers (#1489271)
+ foreach ($this->script as $rule) {
+ foreach ((array) $rule['tests'] as $test) {
+ if ($test['test'] == 'header') {
+ foreach ((array) $test['arg1'] as $header) {
+ $lc_header = strtolower($header);
+ if (!isset($this->headers[$lc_header]) && !isset($headers[$lc_header])) {
+ $headers[$lc_header] = $header;
+ }
+ }
+ }
+ }
+ }
+
+ ksort($headers);
+
+ $this->headers += $headers;
+ }
}
diff --git a/plugins/managesieve/package.xml b/plugins/managesieve/package.xml
index 9c02957a5..6ae53c250 100644
--- a/plugins/managesieve/package.xml
+++ b/plugins/managesieve/package.xml
@@ -17,10 +17,10 @@
<email>alec@alec.pl</email>
<active>yes</active>
</lead>
- <date>2013-02-17</date>
+ <date>2013-09-09</date>
<version>
- <release>6.2</release>
- <api>6.0</api>
+ <release>7.0</release>
+ <api>7.0</api>
</version>
<stability>
<release>stable</release>
@@ -38,6 +38,10 @@
<tasks:replace from="@name@" to="name" type="package-info"/>
<tasks:replace from="@package_version@" to="version" type="package-info"/>
</file>
+ <file name="lib/Roundcube/rcube_sieve.php" role="php"></file>
+ <file name="lib/Roundcube/rcube_sieve_engine.php" role="php"></file>
+ <file name="lib/Roundcube/rcube_sieve_script.php" role="php"></file>
+ <file name="lib/Net/Sieve.php" role="php"></file>
<file name="localization/be_BE.inc" role="data"></file>
<file name="localization/bg_BG.inc" role="data"></file>
<file name="localization/bs_BA.inc" role="data"></file>
@@ -106,10 +110,6 @@
<file name="skins/larry/images/down_small.gif" role="data"></file>
<file name="skins/larry/images/erase.png" role="data"></file>
<file name="skins/larry/images/up_small.gif" role="data"></file>
- <file name="lib/Roundcube/rcube_sieve.php" role="php"></file>
- <file name="lib/Roundcube/rcube_sieve_engine.php" role="php"></file>
- <file name="lib/Roundcube/rcube_sieve_script.php" role="php"></file>
- <file name="lib/Net/Sieve.php" role="php"></file>
<file name="config.inc.php.dist" role="data"></file>
</dir>
<!-- / -->
diff --git a/plugins/newmail_notifier/config.inc.php.dist b/plugins/newmail_notifier/config.inc.php.dist
index cdb563c40..1a7c0d74f 100644
--- a/plugins/newmail_notifier/config.inc.php.dist
+++ b/plugins/newmail_notifier/config.inc.php.dist
@@ -9,4 +9,7 @@ $config['newmail_notifier_sound'] = false;
// Enables desktop notification
$config['newmail_notifier_desktop'] = false;
+// Desktop notification close timeout in seconds
+$config['newmail_notifier_desktop_timeout'] = 10;
+
?>
diff --git a/plugins/newmail_notifier/localization/en_US.inc b/plugins/newmail_notifier/localization/en_US.inc
index 7c1c5cf3f..1c4054615 100644
--- a/plugins/newmail_notifier/localization/en_US.inc
+++ b/plugins/newmail_notifier/localization/en_US.inc
@@ -25,5 +25,6 @@ $labels['body'] = 'You\'ve received a new message.';
$labels['testbody'] = 'This is a test notification.';
$labels['desktopdisabled'] = 'Desktop notifications are disabled in your browser.';
$labels['desktopunsupported'] = 'Your browser does not support desktop notifications.';
+$labels['desktoptimeout'] = 'Close desktop notification';
?>
diff --git a/plugins/newmail_notifier/newmail_notifier.js b/plugins/newmail_notifier/newmail_notifier.js
index b00f33d10..846bc94c3 100644
--- a/plugins/newmail_notifier/newmail_notifier.js
+++ b/plugins/newmail_notifier/newmail_notifier.js
@@ -90,13 +90,11 @@ function newmail_notifier_sound()
// - Require Chrome or Firefox latest version (22+) / 21.0 or older with a plugin
function newmail_notifier_desktop(body)
{
+ var timeout = rcmail.env.newmail_notifier_timeout || 10;
-/**
- * Fix: As of 17 June 2013, Chrome/Chromium does not implement Notification.permission correctly that
- * it gives 'undefined' until an object has been created:
- * https://code.google.com/p/chromium/issues/detail?id=163226
- *
- */
+ // As of 17 June 2013, Chrome/Chromium does not implement Notification.permission correctly that
+ // it gives 'undefined' until an object has been created:
+ // https://code.google.com/p/chromium/issues/detail?id=163226
try {
if (Notification.permission == 'granted' || Notification.permission == undefined) {
var popup = new Notification(rcmail.gettext('title', 'newmail_notifier'), {
@@ -109,7 +107,7 @@ function newmail_notifier_desktop(body)
popup.onclick = function() {
this.close();
}
- setTimeout(function() { popup.close(); }, 10000); // close after 10 seconds
+ setTimeout(function() { popup.close(); }, timeout * 1000);
if (popup.permission == 'granted') return true;
}
}
@@ -125,7 +123,7 @@ function newmail_notifier_desktop(body)
this.cancel();
}
popup.show();
- setTimeout(function() { popup.cancel(); }, 10000); // close after 10 seconds
+ setTimeout(function() { popup.cancel(); }, timeout * 1000);
rcmail.newmail_popup = popup;
return true;
}
diff --git a/plugins/newmail_notifier/newmail_notifier.php b/plugins/newmail_notifier/newmail_notifier.php
index ca1c2ff67..20c542f58 100644
--- a/plugins/newmail_notifier/newmail_notifier.php
+++ b/plugins/newmail_notifier/newmail_notifier.php
@@ -123,6 +123,23 @@ class newmail_notifier extends rcube_plugin
}
}
+ $type = 'desktop_timeout';
+ $key = 'newmail_notifier_' . $type;
+ if (!in_array($key, $dont_override)) {
+ $field_id = '_' . $key;
+ $select = new html_select(array('name' => $field_id, 'id' => $field_id));
+
+ foreach (array(5, 10, 15, 30, 45, 60) as $sec) {
+ $label = $this->rc->gettext(array('name' => 'afternseconds', 'vars' => array('n' => $sec)));
+ $select->add($label, $sec);
+ }
+
+ $args['blocks']['new_message']['options'][$key] = array(
+ 'title' => html::label($field_id, rcube::Q($this->gettext('desktoptimeout'))),
+ 'content' => $select->show((int) $this->rc->config->get($key))
+ );
+ }
+
return $args;
}
@@ -148,6 +165,13 @@ class newmail_notifier extends rcube_plugin
}
}
+ $option = 'newmail_notifier_desktop_timeout';
+ if (!in_array($option, $dont_override)) {
+ if ($value = (int) rcube_utils::get_input_value('_' . $option, rcube_utils::INPUT_POST)) {
+ $args['prefs'][$option] = $value;
+ }
+ }
+
return $args;
}
@@ -180,6 +204,7 @@ class newmail_notifier extends rcube_plugin
if ($unseen->count()) {
$this->notified = true;
+ $this->rc->output->set_env('newmail_notifier_timeout', $this->rc->config->get('newmail_notifier_desktop_timeout'));
$this->rc->output->command('plugin.newmail_notifier',
array(
'basic' => $this->opt['basic'],
diff --git a/plugins/newmail_notifier/package.xml b/plugins/newmail_notifier/package.xml
index b8ef34933..e46c9bc92 100644
--- a/plugins/newmail_notifier/package.xml
+++ b/plugins/newmail_notifier/package.xml
@@ -19,9 +19,9 @@
<email>alec@alec.pl</email>
<active>yes</active>
</lead>
- <date>2013-03-16</date>
+ <date>2013-09-12</date>
<version>
- <release>0.5</release>
+ <release>0.6</release>
<api>0.5</api>
</version>
<stability>
diff --git a/plugins/password/config.inc.php.dist b/plugins/password/config.inc.php.dist
index 82f6617e5..bfea52918 100644
--- a/plugins/password/config.inc.php.dist
+++ b/plugins/password/config.inc.php.dist
@@ -320,8 +320,7 @@ $config['hmailserver_server'] = array(
// 5: domain-username
// 6: username_domain
// 7: domain_username
-// 8: username@domain; mbox.username
-$config['password_virtualmin_format'] = 8;
+$config['password_virtualmin_format'] = 0;
// pw_usermod Driver options
diff --git a/plugins/password/drivers/virtualmin.php b/plugins/password/drivers/virtualmin.php
index 2c7aee617..2d2f73f97 100644
--- a/plugins/password/drivers/virtualmin.php
+++ b/plugins/password/drivers/virtualmin.php
@@ -48,10 +48,6 @@ class rcube_virtualmin_password
$pieces = explode("_", $username);
$domain = $pieces[0];
break;
- case 8: // domain taken from alias, username left as it was
- $email = $rcmail->user->data['alias'];
- $domain = substr(strrchr($email, "@"), 1);
- break;
default: // username@domain
$domain = substr(strrchr($username, "@"), 1);
}
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'])) {
diff --git a/skins/classic/mail.css b/skins/classic/mail.css
index b8cc9f351..43367749c 100644
--- a/skins/classic/mail.css
+++ b/skins/classic/mail.css
@@ -1592,10 +1592,9 @@ input.from_address
#compose-attachments ul li
{
height: 18px;
+ line-height: 16px;
font-size: 11px;
- padding-left: 2px;
- padding-top: 2px;
- padding-right: 4px;
+ padding: 2px 2px 1px 2px;
border-bottom: 1px solid #EBEBEB;
white-space: nowrap;
overflow: hidden;
@@ -1608,8 +1607,10 @@ input.from_address
text-indent: -5000px;
width: 17px;
height: 16px;
+ padding-bottom: 2px;
display: inline-block;
text-decoration: none;
+ vertical-align: middle;
}
#compose-attachments li img
diff --git a/skins/classic/templates/message.html b/skins/classic/templates/message.html
index 757c0a635..bd4fbf277 100644
--- a/skins/classic/templates/message.html
+++ b/skins/classic/templates/message.html
@@ -49,7 +49,7 @@
</div>
<roundcube:object name="messageHeaders" class="headers-table" cellspacing="0" cellpadding="2" addicon="/images/icons/silhouette.png" summary="Message headers" />
<roundcube:object name="messageFullHeaders" id="full-headers" />
-<roundcube:object name="messageAttachments" id="attachment-list" />
+<roundcube:object name="messageAttachments" id="attachment-list" maxlength="50" />
<roundcube:object name="messageObjects" id="message-objects" />
<roundcube:object name="messageBody" id="messagebody" />
</div>
diff --git a/skins/classic/templates/messagepreview.html b/skins/classic/templates/messagepreview.html
index b42a06342..82414c420 100644
--- a/skins/classic/templates/messagepreview.html
+++ b/skins/classic/templates/messagepreview.html
@@ -20,7 +20,7 @@
</div>
<roundcube:object name="messageHeaders" class="headers-table" cellspacing="0" cellpadding="2" addicon="/images/icons/silhouette.png" summary="Message headers" />
<roundcube:object name="messageFullHeaders" id="full-headers" />
-<roundcube:object name="messageAttachments" id="attachment-list" />
+<roundcube:object name="messageAttachments" id="attachment-list" maxlength="50" />
</div>
<roundcube:object name="messageObjects" id="message-objects" />
diff --git a/skins/larry/addressbook.css b/skins/larry/addressbook.css
index 6bf9426c4..39d0cce21 100644
--- a/skins/larry/addressbook.css
+++ b/skins/larry/addressbook.css
@@ -387,3 +387,8 @@ a.deletebutton {
overflow: auto;
padding: 10px;
}
+
+#import-box p,
+#import-box .propform {
+ max-width: 50em;
+}
diff --git a/skins/larry/settings.css b/skins/larry/settings.css
index 59037ac76..6afa48c40 100644
--- a/skins/larry/settings.css
+++ b/skins/larry/settings.css
@@ -48,6 +48,26 @@
border-radius: 4px 4px 0 0;
}
+#preferences-details fieldset.advanced legend {
+ position: relative;
+ display: block;
+ width: 100%;
+ cursor: pointer;
+}
+
+#preferences-details fieldset.advanced .propform {
+ display: none;
+}
+
+#preferences-details fieldset.advanced .advanced-toggle {
+ position: absolute;
+ top: 2px;
+ right: 6px;
+ text-decoration: none;
+ color: #666;
+ font-size: 11px;
+}
+
#sections-table tbody td.section,
#settings-sections span.listitem a,
#settings-sections span.tablink a {
diff --git a/skins/larry/styles.css b/skins/larry/styles.css
index d542768b7..4b238c163 100644
--- a/skins/larry/styles.css
+++ b/skins/larry/styles.css
@@ -2292,12 +2292,13 @@ ul.toolbarmenu li span.conversation {
display: block;
color: #333;
font-weight: bold;
- padding: 8px 15px 3px 30px;
+ padding: 3px 15px 3px 30px;
text-shadow: 0px 1px 1px #fff;
text-decoration: none;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
+ line-height: 20px;
}
.attachmentslist li a.drop {
@@ -2326,9 +2327,9 @@ ul.toolbarmenu li span.conversation {
.attachmentslist li a.delete,
.attachmentslist li a.cancelupload {
position: absolute;
- top: 6px;
+ top: 4px;
right: 0;
- width: 24px;
+ width: 20px;
height: 18px;
padding: 0;
text-decoration: none;
diff --git a/skins/larry/templates/importcontacts.html b/skins/larry/templates/importcontacts.html
index d3d0f2b93..69b138b9a 100644
--- a/skins/larry/templates/importcontacts.html
+++ b/skins/larry/templates/importcontacts.html
@@ -18,7 +18,7 @@
<h2 class="boxtitle"><roundcube:label name="importcontacts" /></h2>
<div id="import-box" class="boxcontent">
-<roundcube:object name="importstep" />
+<roundcube:object name="importstep" class="propform" />
<br/>
<p class="formbuttons">
<roundcube:object name="importnav" class="button" />
diff --git a/skins/larry/ui.js b/skins/larry/ui.js
index ae14d81b2..d558f16a2 100644
--- a/skins/larry/ui.js
+++ b/skins/larry/ui.js
@@ -195,6 +195,19 @@ function rcube_mail_ui()
new rcube_splitter({ id:'prefviewsplitter', p1:'#sectionslist', p2:'#preferences-box',
orientation:'v', relative:true, start:266, min:180, size:12 }).init();
}
+ else if (rcmail.env.action == 'edit-prefs') {
+ $('<a href="#toggle">&#9660;</a>')
+ .addClass('advanced-toggle')
+ .appendTo('#preferences-details fieldset.advanced legend');
+
+ $('#preferences-details fieldset.advanced legend').click(function(e){
+ var collapsed = $(this).hasClass('collapsed'),
+ toggle = $('.advanced-toggle', this).html(collapsed ? '&#9650;' : '&#9660;');
+ $(this)
+ .toggleClass('collapsed')
+ .closest('fieldset').children('.propform').toggle()
+ }).addClass('collapsed')
+ }
}
/*** addressbook task ***/
else if (rcmail.env.task == 'addressbook') {
diff --git a/tests/Framework/Browser.php b/tests/Framework/Browser.php
index c3860d8a3..832d4bf14 100644
--- a/tests/Framework/Browser.php
+++ b/tests/Framework/Browser.php
@@ -17,4 +17,207 @@ class Framework_Browser extends PHPUnit_Framework_TestCase
$this->assertInstanceOf('rcube_browser', $object, "Class constructor");
}
+
+ /**
+ * @dataProvider browsers
+ */
+ function test_browser($useragent, $opera, $chrome, $ie, $ns, $ns4, $khtml, $safari, $mz)
+ {
+
+ $object = $this->getBrowser($useragent);
+
+ $this->assertEquals($opera, $object->opera, 'Check for Opera failed');
+ $this->assertEquals($chrome, $object->chrome, 'Check for Chrome failed');
+ $this->assertEquals($ie, $object->ie, 'Check for IE failed');
+ $this->assertEquals($ns, $object->ns, 'Check for NS failed');
+ $this->assertEquals($ns4, $object->ns4, 'Check for NS4 failed');
+ $this->assertEquals($khtml, $object->khtml, 'Check for khtml failed');
+ $this->assertEquals($safari, $object->safari, 'Check for Safari failed');
+ $this->assertEquals($mz, $object->mz, 'Check for MZ failed');
+ }
+
+ /**
+ * @dataProvider os
+ */
+ function test_os($useragent, $windows, $linux, $unix, $mac)
+ {
+ $object = $this->getBrowser($useragent);
+
+ $this->assertEquals($windows, $object->win, 'Check Result of Windows');
+ $this->assertEquals($linux, $object->linux, 'Check Result of Linux');
+ $this->assertEquals($mac, $object->mac, 'Check Result of Mac');
+ $this->assertEquals($unix, $object->unix, 'Check Result of Unix');
+
+ }
+
+ /**
+ * @dataProvider versions
+ */
+ function test_version($useragent, $version)
+ {
+ $object = $this->getBrowser($useragent);
+ $this->assertEquals($version, $object->ver);
+ }
+
+ /**
+ * @dataProvider dom
+ */
+ function test_dom($useragent, $dom)
+ {
+ $object = $this->getBrowser($useragent);
+ $this->assertEquals($dom, $object->dom);
+
+ }
+
+ /**
+ * @dataProvider pngalpha
+ */
+ function test_pngalpha($useragent, $pngalpha)
+ {
+ $object = $this->getBrowser($useragent);
+ $this->assertEquals($pngalpha, $object->pngalpha);
+ }
+
+ /**
+ * @dataProvider imgdata
+ */
+ function test_imgdata($useragent, $imgdata)
+ {
+ $object = $this->getBrowser($useragent);
+ $this->assertEquals($imgdata, $object->imgdata);
+ }
+
+ function versions()
+ {
+ return $this->extractDataSet(array('version'));
+ }
+
+ function pngalpha()
+ {
+ return $this->extractDataSet(array('canPNGALPHA'));
+ }
+
+ function imgdata()
+ {
+ return $this->extractDataSet(array('canIMGDATA'));
+ }
+
+ private function extractDataSet($keys)
+ {
+ $keys = array_merge(array('useragent'), $keys);
+
+ $browser = $this->useragents();
+
+ $extracted = array();
+
+ foreach ($browser as $label => $data) {
+ foreach($keys as $key) {
+ $extracted[$data['useragent']][] = $data[$key];
+ }
+
+ }
+
+ return $extracted;
+ }
+
+ function lang()
+ {
+ return $this->extractDataSet(array('lang'));
+ }
+
+ function dom()
+ {
+ return $this->extractDataSet(array('hasDOM'));
+ }
+
+ function browsers()
+ {
+ return $this->extractDataSet(array('isOpera','isChrome','isIE','isNS','isNS4','isKHTML','isSafari','isMZ'));
+ }
+
+ function useragents()
+ {
+ return array(
+ 'WIN: Mozilla Firefox ' => array(
+ 'useragent' => 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.0.1) Gecko/20060111 Firefox/1.5.0.1',
+ 'version' => '1.8', //Version
+ 'isWin' => true, //isWindows
+ 'isLinux' => false,
+ 'isMac' => false, //isMac
+ 'isUnix' => false, //isUnix
+ 'isOpera' => false, //isOpera
+ 'isChrome' => false, //isChrome
+ 'isIE' => false, //isIE
+ 'isNS' => false, //isNS
+ 'isNS4' => false, //isNS4
+ 'isKHTML' => false, //isKHTML
+ 'isSafari' => false, //isSafari
+ 'isMZ' => true, //isMZ
+ 'lang' => 'en-US', //lang
+ 'hasDOM' => true, //hasDOM
+ 'canPNGALPHA' => true, //canPNGALPHA
+ 'canIMGDATA' => true, //canIMGDATA
+ ),
+ 'LINUX: Bon Echo ' => array(
+ 'useragent' => 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.1.1) Gecko/20070222 BonEcho/2.0.0.1',
+ 'version' => '1.8', //Version
+ 'isWin' => false, //isWindows
+ 'isLinux' => true,
+ 'isMac' => false, //isMac
+ 'isUnix' => false, //isUnix
+ 'isOpera' => false, //isOpera
+ 'isChrome' => false, //isChrome
+ 'isIE' => false, //isIE
+ 'isNS' => false, //isNS
+ 'isNS4' => false, //isNS4
+ 'isKHTML' => false, //isKHTML
+ 'isSafari' => false, //isSafari
+ 'isMZ' => true, //isMZ
+ 'lang' => 'en-US', //lang
+ 'hasDOM' => true, //hasDOM
+ 'canPNGALPHA' => true, //canPNGALPHA
+ 'canIMGDATA' => true, //canIMGDATA
+ ),
+
+ 'Chrome Mac' => array(
+ 'useragent' => 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_4; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.461.0 Safari/534.3',
+ 'version' => '5', //Version
+ 'isWin' => false, //isWindows
+ 'isLinux' => false,
+ 'isMac' => true, //isMac
+ 'isUnix' => false, //isUnix
+ 'isOpera' => false, //isOpera
+ 'isChrome' => true, //isChrome
+ 'isIE' => false, //isIE
+ 'isNS' => false, //isNS
+ 'isNS4' => false, //isNS4
+ 'isKHTML' => true, //isKHTML
+ 'isSafari' => false, //isSafari
+ 'isMZ' => false, //isMZ
+ 'lang' => 'en-US', //lang
+ 'hasDOM' => false, //hasDOM
+ 'canPNGALPHA' => false, //canPNGALPHA
+ 'canIMGDATA' => true, //canIMGDATA
+ ),
+ );
+ }
+
+ function os()
+ {
+ return $this->extractDataSet(array('isWin','isLinux','isUnix','isMac'));
+ }
+
+ /**
+ * @param string $useragent
+ * @return rcube_browser
+ */
+ private function getBrowser($useragent)
+ {
+ /** @var $object rcube_browser */
+ $_SERVER['HTTP_USER_AGENT'] = $useragent;
+
+ $object = new rcube_browser();
+
+ return $object;
+ }
}