summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG51
-rw-r--r--config/main.inc.php.dist25
-rw-r--r--installer/rcube_install.php11
-rw-r--r--plugins/acl/acl.php3
-rw-r--r--plugins/acl/skins/larry/acl.css4
-rw-r--r--plugins/acl/skins/larry/templates/table.html8
-rw-r--r--plugins/hide_blockquote/hide_blockquote.php6
-rw-r--r--plugins/hide_blockquote/skins/larry/style.css (renamed from plugins/hide_blockquote/skins/default/style.css)0
-rw-r--r--plugins/managesieve/tests/Makefile7
-rw-r--r--plugins/managesieve/tests/Parser.php54
-rw-r--r--plugins/managesieve/tests/Tokenizer.php33
-rw-r--r--plugins/managesieve/tests/parser.phpt120
-rw-r--r--plugins/managesieve/tests/parser_body.phpt49
-rw-r--r--plugins/managesieve/tests/parser_imapflags.phpt28
-rw-r--r--plugins/managesieve/tests/parser_include.phpt30
-rw-r--r--plugins/managesieve/tests/parser_kep14.phpt19
-rw-r--r--plugins/managesieve/tests/parser_prefix.phpt25
-rw-r--r--plugins/managesieve/tests/parser_relational.phpt25
-rw-r--r--plugins/managesieve/tests/parser_vacation.phpt39
-rw-r--r--plugins/managesieve/tests/parser_variables.phpt39
-rw-r--r--plugins/managesieve/tests/parset_subaddress.phpt38
-rw-r--r--plugins/managesieve/tests/src/parser52
-rw-r--r--plugins/managesieve/tests/src/parser.out52
-rw-r--r--plugins/managesieve/tests/src/parser_body17
-rw-r--r--plugins/managesieve/tests/src/parser_imapflags7
-rw-r--r--plugins/managesieve/tests/src/parser_include7
-rw-r--r--plugins/managesieve/tests/src/parser_kep142
-rw-r--r--plugins/managesieve/tests/src/parser_kep14.out3
-rw-r--r--plugins/managesieve/tests/src/parser_prefix5
-rw-r--r--plugins/managesieve/tests/src/parser_relational6
-rw-r--r--plugins/managesieve/tests/src/parser_subaddress11
-rw-r--r--plugins/managesieve/tests/src/parser_vacation12
-rw-r--r--plugins/managesieve/tests/src/parser_variables12
-rw-r--r--plugins/managesieve/tests/tokenize.phpt66
-rw-r--r--plugins/password/drivers/virtualmin.php4
-rw-r--r--plugins/virtuser_query/virtuser_query.php5
-rw-r--r--program/include/html.php2
-rw-r--r--program/include/rcmail.php35
-rw-r--r--program/include/rcube.php1439
-rw-r--r--program/include/rcube_bc.inc5
-rw-r--r--program/include/rcube_browser.php2
-rw-r--r--program/include/rcube_charset.php6
-rw-r--r--program/include/rcube_config.php1
-rw-r--r--program/include/rcube_imap.php4
-rw-r--r--program/include/rcube_imap_cache.php52
-rw-r--r--program/include/rcube_imap_generic.php5
-rw-r--r--program/include/rcube_ldap.php5
-rw-r--r--program/include/rcube_message.php28
-rw-r--r--program/include/rcube_mime.php4
-rw-r--r--program/include/rcube_output_html.php36
-rw-r--r--program/include/rcube_plugin.php2
-rw-r--r--program/include/rcube_shared.inc33
-rw-r--r--program/include/rcube_storage.php2
-rw-r--r--program/include/rcube_utils.php61
-rw-r--r--program/include/rcube_vcard.php2
-rw-r--r--program/js/app.js152
-rw-r--r--program/js/common.js7
-rw-r--r--program/js/googiespell.js4
-rw-r--r--program/js/list.js4
-rw-r--r--program/localization/en_US/labels.inc13
-rw-r--r--program/localization/pl_PL/labels.inc1
-rw-r--r--program/steps/addressbook/export.inc2
-rw-r--r--program/steps/mail/compose.inc53
-rw-r--r--program/steps/mail/func.inc17
-rw-r--r--program/steps/mail/headers.inc3
-rw-r--r--program/steps/settings/folders.inc12
-rw-r--r--program/steps/settings/func.inc16
-rw-r--r--program/steps/settings/save_folder.inc5
-rw-r--r--program/steps/settings/save_prefs.inc4
-rw-r--r--skins/classic/functions.js15
-rw-r--r--skins/classic/includes/links.html1
-rw-r--r--skins/classic/splitter.js4
-rw-r--r--skins/classic/templates/compose.html2
-rw-r--r--skins/classic/templates/message.html15
-rw-r--r--skins/classic/templates/messageerror.html15
-rw-r--r--skins/larry/addressbook.css1
-rw-r--r--skins/larry/ie7hacks.css16
-rw-r--r--skins/larry/iehacks.css2
-rw-r--r--skins/larry/images/contactpic_32px.pngbin4883 -> 3422 bytes
-rw-r--r--skins/larry/images/contactpic_48px.pngbin0 -> 3812 bytes
-rw-r--r--skins/larry/includes/links.html1
-rw-r--r--skins/larry/mail.css90
-rw-r--r--skins/larry/styles.css6
-rw-r--r--skins/larry/svggradient.php2
-rw-r--r--skins/larry/svggradients.css2
-rw-r--r--skins/larry/templates/message.html61
-rw-r--r--skins/larry/templates/messageerror.html10
-rw-r--r--skins/larry/templates/messagepreview.html5
-rw-r--r--skins/larry/ui.js62
-rw-r--r--tests/Framework/BaseReplacer.php20
-rw-r--r--tests/Framework/Browser.php20
-rw-r--r--tests/Framework/Cache.php20
-rw-r--r--tests/Framework/Charset.php28
-rw-r--r--tests/Framework/ContentFilter.php20
-rw-r--r--tests/Framework/Html.php46
-rw-r--r--tests/Framework/Image.php20
-rw-r--r--tests/Framework/Imap.php20
-rw-r--r--tests/Framework/ImapGeneric.php20
-rw-r--r--tests/Framework/MessageHeader.php20
-rw-r--r--tests/Framework/MessagePart.php20
-rw-r--r--tests/Framework/Mime.php123
-rw-r--r--tests/Framework/Rcube.php20
-rw-r--r--tests/Framework/ResultIndex.php20
-rw-r--r--tests/Framework/ResultSet.php20
-rw-r--r--tests/Framework/ResultThread.php20
-rw-r--r--tests/Framework/Shared.php204
-rw-r--r--tests/Framework/Smtp.php20
-rw-r--r--tests/Framework/Spellchecker.php20
-rw-r--r--tests/Framework/StringReplacer.php20
-rw-r--r--tests/Framework/User.php20
-rw-r--r--tests/Framework/Utils.php196
-rw-r--r--tests/Framework/VCard.php59
-rw-r--r--tests/HtmlToText.php (renamed from tests/html_to_text.php)28
-rw-r--r--tests/MailFunc.php172
-rw-r--r--[-rwxr-xr-x]tests/bootstrap.php (renamed from tests/runtests.sh)35
-rw-r--r--tests/maildecode.php130
-rw-r--r--tests/mailfunc.php173
-rw-r--r--tests/modcss.php45
-rw-r--r--tests/phpunit.xml37
-rw-r--r--tests/vcards.php65
120 files changed, 2868 insertions, 2015 deletions
diff --git a/CHANGELOG b/CHANGELOG
index 4cbaa6ece..0706009c4 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,19 +1,26 @@
CHANGELOG Roundcube Webmail
===========================
-- 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 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)
-- Fix Remove from group option is active for contact search result (#1488608)
-- Disable autocapitalization in login form on iPad/iPhone (#1488609)
-- Fix focus on the list when list row is clicked (#1488600)
-- Added separate From and To columns apart from smart From/To column (#1486891)
-- Fix fallback to Larry skin when configured skin isn't available (#1488591)
-- Fix (workaround) delete operations with some versions of memcache (#1488592)
-- Fix (disable) request validation for spell and spell_html actions
+- Fix focus issue in IE when selecting message row (#1488620)
+- Remove (too big) min-width on mail screen
+- Add full headers view in message preview window (#1488538)
+- Fix message display page issues - unified with message preview (#1488590, #1488642)
+- Fix displaying all headers when they contain malformed characters (#1488666)
+- Fix decoding of HTML messages with UTF-16 charset specified (#1488654)
+- Fix quota capability detection so it can be overwritten by a plugin (#1488655)
+- Added template object 'frame'
+- Fix identity selection on reply (#1488101)
+- Add option to enable HTML editor on forwarding (#1488517)
+- Add option to not include original message on reply, rename option top_posting to reply_mode (#1485149)
+- Fix Larry's messages list filter in IE (#1488632)
+- Fix more IE issues by disabling Compat. mode with X-UA-Compatible meta tag (#1488626)
+- Fix setting locales under Solaris - use additional .UTF-8 suffix (#1488628)
+- Fix email address validation for addresses with IP address in domain part
+- Fix Larry skin issues in IE7 compat. mode (#1488618)
+- Fix so subscribed non-existing/non-accessible shared folder can be unsubscribed
+- Added session_path config option and unified cookies settings in javascript
+- Added "Undeleted" option to messages list filter
+- Rewritten test scripts for PHPUnit
- Add new DB abstraction layer based on PHP PDO, supporting SQLite3 (#1488332)
- Removed PEAR::MDB2 package
- Removed users.alias column, added option ('user_aliases')
@@ -39,6 +46,24 @@ CHANGELOG Roundcube Webmail
Move global functions from main.inc and rcube_shared.inc into classes
Better classes separation
+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 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)
+- Fix Remove from group option is active for contact search result (#1488608)
+- Disable autocapitalization in login form on iPad/iPhone (#1488609)
+- Fix focus on the list when list row is clicked (#1488600)
+- Added separate From and To columns apart from smart From/To column (#1486891)
+- Fix fallback to Larry skin when configured skin isn't available (#1488591)
+- Fix (workaround) delete operations with some versions of memcache (#1488592)
+- Fix (disable) request validation for spell and spell_html actions
+
RELEASE 0.8.0
-------------
- Don't show product version on login screen (can be enabled by config)
diff --git a/config/main.inc.php.dist b/config/main.inc.php.dist
index 58f9ca865..7e07341a9 100644
--- a/config/main.inc.php.dist
+++ b/config/main.inc.php.dist
@@ -21,7 +21,7 @@ $rcmail_config = array();
// LOGGING/DEBUGGING
// ----------------------------------
-// system error reporting: 1 = log; 2 = report (not implemented yet), 4 = show, 8 = trace
+// system error reporting, sum of: 1 = log; 4 = show, 8 = trace
$rcmail_config['debug_level'] = 1;
// log driver: 'syslog' or 'file'.
@@ -224,11 +224,12 @@ $rcmail_config['use_https'] = false;
// 0 - disabled, 1 - username and host only, 2 - username, host, password
$rcmail_config['login_autocomplete'] = 0;
-// If users authentication is not case sensitive this must be enabled.
-// You can also use it to force conversion of logins to lower case.
+// Forces conversion of logins to lower case.
+// 0 - disabled, 1 - only domain part, 2 - domain and local part.
+// If users authentication is not case-sensitive this must be enabled.
// After enabling it all user records need to be updated, e.g. with query:
// UPDATE users SET username = LOWER(username);
-$rcmail_config['login_lc'] = false;
+$rcmail_config['login_lc'] = 0;
// Includes should be interpreted as PHP files
$rcmail_config['skin_include_php'] = false;
@@ -240,12 +241,15 @@ $rcmail_config['display_version'] = false;
// must be greater than 'keep_alive'/60
$rcmail_config['session_lifetime'] = 10;
-// session domain: .example.org
+// Session domain: .example.org
$rcmail_config['session_domain'] = '';
-// session name. Default: 'roundcube_sessid'
+// Session name. Default: 'roundcube_sessid'
$rcmail_config['session_name'] = null;
+// Session path. Defaults to PHP session.cookie_path setting.
+$rcmail_config['session_path'] = null;
+
// Backend to use for session storage. Can either be 'db' (default) or 'memcache'
// If set to memcache, a list of servers need to be specified in 'memcache_hosts'
// Make sure the Memcache extension (http://pecl.php.net/package/memcache) version >= 2.0.0 is installed
@@ -721,7 +725,7 @@ $rcmail_config['prefer_html'] = true;
$rcmail_config['show_images'] = 0;
// compose html formatted messages by default
-// 0 - never, 1 - always, 2 - on reply to HTML message only
+// 0 - never, 1 - always, 2 - on reply to HTML message, 3 - on forward or reply to HTML message
$rcmail_config['htmleditor'] = 0;
// show pretty dates as standard
@@ -779,8 +783,11 @@ $rcmail_config['display_next'] = true;
// 2 - Expand only threads with unread messages
$rcmail_config['autoexpand_threads'] = 0;
-// When replying place cursor above original message (top posting)
-$rcmail_config['top_posting'] = false;
+// When replying:
+// -1 - don't cite the original message
+// 0 - place cursor below the original message
+// 1 - place cursor above original message (top posting)
+$rcmail_config['reply_mode'] = 0;
// When replying strip original signature from message
$rcmail_config['strip_existing_sig'] = true;
diff --git a/installer/rcube_install.php b/installer/rcube_install.php
index bfb111f1d..5af871346 100644
--- a/installer/rcube_install.php
+++ b/installer/rcube_install.php
@@ -35,13 +35,14 @@ class rcube_install
var $obsolete_config = array('db_backend', 'double_auth');
var $replaced_config = array(
- 'skin_path' => 'skin',
- 'locale_string' => 'language',
- 'multiple_identities' => 'identities_level',
+ 'skin_path' => 'skin',
+ 'locale_string' => 'language',
+ 'multiple_identities' => 'identities_level',
'addrbook_show_images' => 'show_images',
- 'imap_root' => 'imap_ns_personal',
- 'pagesize' => 'mail_pagesize',
+ 'imap_root' => 'imap_ns_personal',
+ 'pagesize' => 'mail_pagesize',
'default_imap_folders' => 'default_folders',
+ 'top_posting' => 'reply_mode',
);
// these config options are required for a working system
diff --git a/plugins/acl/acl.php b/plugins/acl/acl.php
index 144250495..1952dad49 100644
--- a/plugins/acl/acl.php
+++ b/plugins/acl/acl.php
@@ -233,8 +233,7 @@ class acl extends rcube_plugin
// Advanced rights
$attrib['id'] = 'advancedrights';
- foreach ($supported as $val) {
- $id = "acl$val";
+ foreach ($supported as $idx => $val) {
$ul .= html::tag('li', null,
$input->show('', array(
'name' => "acl[$val]", 'value' => $val, 'id' => $id))
diff --git a/plugins/acl/skins/larry/acl.css b/plugins/acl/skins/larry/acl.css
index 5e2448efd..e392a269e 100644
--- a/plugins/acl/skins/larry/acl.css
+++ b/plugins/acl/skins/larry/acl.css
@@ -123,3 +123,7 @@
{
margin-left: 0.5em;
}
+
+ul.toolbarmenu li span.delete {
+ background-position: 0 -1509px;
+}
diff --git a/plugins/acl/skins/larry/templates/table.html b/plugins/acl/skins/larry/templates/table.html
index 7f99f6ffe..3cf8292a4 100644
--- a/plugins/acl/skins/larry/templates/table.html
+++ b/plugins/acl/skins/larry/templates/table.html
@@ -3,14 +3,14 @@
<roundcube:object name="acltable" id="acltable" class="records-table" />
</div>
<div id="acllist-footer" class="boxfooter">
- <roundcube:button command="acl-create" id="aclcreatelink" type="link" title="acl.newuser" class="listbutton add disabled" classAct="listbutton add" innerClass="inner" content="+" /><roundcube:button name="aclmenulink" id="aclmenulink" type="link" title="acl.actions" class="listbutton groupactions"onclick="UI.show_popup('aclmenu');return false" innerClass="inner" content="&#9881;" />
+ <roundcube:button command="acl-create" id="aclcreatelink" type="link" title="acl.newuser" class="listbutton add disabled" classAct="listbutton add" innerClass="inner" content="+" /><roundcube:button name="aclmenulink" id="aclmenulink" type="link" title="acl.actions" class="listbutton groupactions"onclick="UI.show_popup('aclmenu', undefined, {above:1});return false" innerClass="inner" content="&#9881;" />
</div>
</div>
<div id="aclmenu" class="popupmenu">
- <ul class="toolbarmenu selectable">
- <li><roundcube:button command="acl-edit" label="edit" classAct="active" /></li>
- <li><roundcube:button command="acl-delete" label="delete" classAct="active" /></li>
+ <ul class="toolbarmenu selectable iconized">
+ <li><roundcube:button command="acl-edit" label="edit" class="icon" classAct="icon active" innerclass="icon edit" /></li>
+ <li><roundcube:button command="acl-delete" label="delete" class="icon" classAct="icon active" innerclass="icon delete" /></li>
<roundcube:if condition="!in_array('acl_advanced_mode', (array)config:dont_override)" />
<li><roundcube:button name="acl-switch" id="acl-switch" label="acl.advanced" onclick="rcmail.command('acl-mode-switch')" class="active" /></li>
<roundcube:endif />
diff --git a/plugins/hide_blockquote/hide_blockquote.php b/plugins/hide_blockquote/hide_blockquote.php
index ca0273a5d..7af163dcd 100644
--- a/plugins/hide_blockquote/hide_blockquote.php
+++ b/plugins/hide_blockquote/hide_blockquote.php
@@ -27,11 +27,7 @@ class hide_blockquote extends rcube_plugin
&& ($limit = $rcmail->config->get('hide_blockquote_limit'))
) {
// include styles
- $skin = $rcmail->config->get('skin');
- if (!file_exists($this->home."/skins/$skin/style.css")) {
- $skin = 'default';
- }
- $this->include_stylesheet("skins/$skin/style.css");
+ $this->include_stylesheet($this->local_skin_path() . "/style.css");
// Script and localization
$this->include_script('hide_blockquote.js');
diff --git a/plugins/hide_blockquote/skins/default/style.css b/plugins/hide_blockquote/skins/larry/style.css
index 198172f92..198172f92 100644
--- a/plugins/hide_blockquote/skins/default/style.css
+++ b/plugins/hide_blockquote/skins/larry/style.css
diff --git a/plugins/managesieve/tests/Makefile b/plugins/managesieve/tests/Makefile
deleted file mode 100644
index 072be2f2c..000000000
--- a/plugins/managesieve/tests/Makefile
+++ /dev/null
@@ -1,7 +0,0 @@
-
-clean:
- rm -f *.log *.php *.diff *.exp *.out
-
-
-test:
- pear run-tests *.phpt
diff --git a/plugins/managesieve/tests/Parser.php b/plugins/managesieve/tests/Parser.php
new file mode 100644
index 000000000..00915cc20
--- /dev/null
+++ b/plugins/managesieve/tests/Parser.php
@@ -0,0 +1,54 @@
+<?php
+
+class Parser extends PHPUnit_Framework_TestCase
+{
+
+ function setUp()
+ {
+ include_once dirname(__FILE__) . '/../lib/rcube_sieve_script.php';
+ }
+
+ /**
+ * Sieve script parsing
+ *
+ * @dataProvider data_parser
+ */
+ function test_parser($input, $output, $message)
+ {
+ $script = new rcube_sieve_script($input);
+ $result = $script->as_text();
+
+ $this->assertEquals(trim($result), trim($output), $message);
+ }
+
+ /**
+ * Data provider for test_parser()
+ */
+ function data_parser()
+ {
+ $dir_path = realpath(dirname(__FILE__) . '/src');
+ $dir = opendir($dir_path);
+ $result = array();
+
+ while ($file = readdir($dir)) {
+ if (preg_match('/^[a-z0-9_]+$/', $file)) {
+ $input = file_get_contents($dir_path . '/' . $file);
+
+ if (file_exists($dir_path . '/' . $file . '.out')) {
+ $output = file_get_contents($dir_path . '/' . $file . '.out');
+ }
+ else {
+ $output = $input;
+ }
+
+ $result[] = array(
+ 'input' => $input,
+ 'output' => $output,
+ 'message' => "Error in parsing '$file' file",
+ );
+ }
+ }
+
+ return $result;
+ }
+}
diff --git a/plugins/managesieve/tests/Tokenizer.php b/plugins/managesieve/tests/Tokenizer.php
new file mode 100644
index 000000000..8c0bced23
--- /dev/null
+++ b/plugins/managesieve/tests/Tokenizer.php
@@ -0,0 +1,33 @@
+<?php
+
+class Tokenizer extends PHPUnit_Framework_TestCase
+{
+
+ function setUp()
+ {
+ include_once dirname(__FILE__) . '/../lib/rcube_sieve_script.php';
+ }
+
+ function data_tokenizer()
+ {
+ return array(
+ array(1, "text: #test\nThis is test ; message;\nMulti line\n.\n;\n", '"This is test ; message;\nMulti line"'),
+ array(0, '["test1","test2"]', '[["test1","test2"]]'),
+ array(1, '["test"]', '["test"]'),
+ array(1, '"te\\"st"', '"te\\"st"'),
+ array(0, 'test #comment', '["test"]'),
+ array(0, "text:\ntest\n.\ntext:\ntest\n.\n", '["test","test"]'),
+ array(1, '"\\a\\\\\\"a"', '"a\\\\\\"a"'),
+ );
+ }
+
+ /**
+ * @dataProvider data_tokenizer
+ */
+ function test_tokenizer($num, $input, $output)
+ {
+ $res = json_encode(rcube_sieve_script::tokenize($input, $num));
+
+ $this->assertEquals(trim($res), trim($output));
+ }
+}
diff --git a/plugins/managesieve/tests/parser.phpt b/plugins/managesieve/tests/parser.phpt
deleted file mode 100644
index aec042187..000000000
--- a/plugins/managesieve/tests/parser.phpt
+++ /dev/null
@@ -1,120 +0,0 @@
---TEST--
-Main test of script parser
---SKIPIF--
---FILE--
-<?php
-include '../lib/rcube_sieve_script.php';
-
-$txt = '
-require ["fileinto","reject","envelope"];
-# rule:[spam]
-if anyof (header :contains "X-DSPAM-Result" "Spam")
-{
- fileinto "Spam";
- stop;
-}
-# rule:[test1]
-if anyof (header :comparator "i;ascii-casemap" :contains ["From","To"] "test@domain.tld")
-{
- discard;
- stop;
-}
-# rule:[test2]
-if anyof (not header :comparator "i;octet" :contains ["Subject"] "[test]", header :contains "Subject" "[test2]")
-{
- fileinto "test";
- stop;
-}
-# rule:[comments]
-if anyof (true) /* comment
- * "comment" #comment */ {
- /* comment */ stop;
-# comment
-}
-# rule:[reject]
-if size :over 5000K {
- reject "Message over 5MB size limit. Please contact me before sending this.";
-}
-# rule:[false]
-if false # size :over 5000K
-{
- stop; /* rule disabled */
-}
-# rule:[true]
-if true
-{
- stop;
-}
-fileinto "Test";
-# rule:[address test]
-if address :all :is "From" "nagios@domain.tld"
-{
- fileinto "domain.tld";
- stop;
-}
-# rule:[envelope test]
-if envelope :domain :is "From" "domain.tld"
-{
- fileinto "domain.tld";
- stop;
-}
-';
-
-$s = new rcube_sieve_script($txt);
-echo $s->as_text();
-
-// -------------------------------------------------------------------------------
-?>
---EXPECT--
-require ["fileinto","reject","envelope"];
-# rule:[spam]
-if header :contains "X-DSPAM-Result" "Spam"
-{
- fileinto "Spam";
- stop;
-}
-# rule:[test1]
-if header :contains ["From","To"] "test@domain.tld"
-{
- discard;
- stop;
-}
-# rule:[test2]
-if anyof (not header :comparator "i;octet" :contains "Subject" "[test]", header :contains "Subject" "[test2]")
-{
- fileinto "test";
- stop;
-}
-# rule:[comments]
-if true
-{
- stop;
-}
-# rule:[reject]
-if size :over 5000K
-{
- reject "Message over 5MB size limit. Please contact me before sending this.";
-}
-# rule:[false]
-if false # size :over 5000K
-{
- stop;
-}
-# rule:[true]
-if true
-{
- stop;
-}
-fileinto "Test";
-# rule:[address test]
-if address :all :is "From" "nagios@domain.tld"
-{
- fileinto "domain.tld";
- stop;
-}
-# rule:[envelope test]
-if envelope :domain :is "From" "domain.tld"
-{
- fileinto "domain.tld";
- stop;
-}
diff --git a/plugins/managesieve/tests/parser_body.phpt b/plugins/managesieve/tests/parser_body.phpt
deleted file mode 100644
index 08ad54959..000000000
--- a/plugins/managesieve/tests/parser_body.phpt
+++ /dev/null
@@ -1,49 +0,0 @@
---TEST--
-Test of Sieve body extension (RFC5173)
---SKIPIF--
---FILE--
-<?php
-include '../lib/rcube_sieve_script.php';
-
-$txt = '
-require ["body","fileinto"];
-if body :raw :contains "MAKE MONEY FAST"
-{
- stop;
-}
-if body :content "text" :contains ["missile","coordinates"]
-{
- fileinto "secrets";
-}
-if body :content "audio/mp3" :contains ""
-{
- fileinto "jukebox";
-}
-if body :text :contains "project schedule"
-{
- fileinto "project/schedule";
-}
-';
-
-$s = new rcube_sieve_script($txt);
-echo $s->as_text();
-
-?>
---EXPECT--
-require ["body","fileinto"];
-if body :raw :contains "MAKE MONEY FAST"
-{
- stop;
-}
-if body :content "text" :contains ["missile","coordinates"]
-{
- fileinto "secrets";
-}
-if body :content "audio/mp3" :contains ""
-{
- fileinto "jukebox";
-}
-if body :text :contains "project schedule"
-{
- fileinto "project/schedule";
-}
diff --git a/plugins/managesieve/tests/parser_imapflags.phpt b/plugins/managesieve/tests/parser_imapflags.phpt
deleted file mode 100644
index a4bc465a3..000000000
--- a/plugins/managesieve/tests/parser_imapflags.phpt
+++ /dev/null
@@ -1,28 +0,0 @@
---TEST--
-Test of Sieve vacation extension (RFC5232)
---SKIPIF--
---FILE--
-<?php
-include '../lib/rcube_sieve_script.php';
-
-$txt = '
-require ["imapflags"];
-# rule:[imapflags]
-if header :matches "Subject" "^Test$" {
- setflag "\\\\Seen";
- addflag ["\\\\Answered","\\\\Deleted"];
-}
-';
-
-$s = new rcube_sieve_script($txt, array('imapflags'));
-echo $s->as_text();
-
-?>
---EXPECT--
-require ["imapflags"];
-# rule:[imapflags]
-if header :matches "Subject" "^Test$"
-{
- setflag "\\Seen";
- addflag ["\\Answered","\\Deleted"];
-}
diff --git a/plugins/managesieve/tests/parser_include.phpt b/plugins/managesieve/tests/parser_include.phpt
deleted file mode 100644
index addc0d449..000000000
--- a/plugins/managesieve/tests/parser_include.phpt
+++ /dev/null
@@ -1,30 +0,0 @@
---TEST--
-Test of Sieve include extension
---SKIPIF--
---FILE--
-<?php
-include '../lib/rcube_sieve_script.php';
-
-$txt = '
-require ["include"];
-
-include "script.sieve";
-# rule:[two]
-if true
-{
- include :optional "second.sieve";
-}
-';
-
-$s = new rcube_sieve_script($txt, array(), array('variables'));
-echo $s->as_text();
-
-?>
---EXPECT--
-require ["include"];
-include "script.sieve";
-# rule:[two]
-if true
-{
- include :optional "second.sieve";
-}
diff --git a/plugins/managesieve/tests/parser_kep14.phpt b/plugins/managesieve/tests/parser_kep14.phpt
deleted file mode 100644
index dcdbd48a0..000000000
--- a/plugins/managesieve/tests/parser_kep14.phpt
+++ /dev/null
@@ -1,19 +0,0 @@
---TEST--
-Test of Kolab's KEP:14 implementation
---SKIPIF--
---FILE--
-<?php
-include '../lib/rcube_sieve_script.php';
-
-$txt = '
-# EDITOR Roundcube
-# EDITOR_VERSION 123
-';
-
-$s = new rcube_sieve_script($txt, array('body'));
-echo $s->as_text();
-
-?>
---EXPECT--
-# EDITOR Roundcube
-# EDITOR_VERSION 123
diff --git a/plugins/managesieve/tests/parser_prefix.phpt b/plugins/managesieve/tests/parser_prefix.phpt
deleted file mode 100644
index c87e9658f..000000000
--- a/plugins/managesieve/tests/parser_prefix.phpt
+++ /dev/null
@@ -1,25 +0,0 @@
---TEST--
-Test of prefix comments handling
---SKIPIF--
---FILE--
-<?php
-include '../lib/rcube_sieve_script.php';
-
-$txt = '
-# this is a comment
-# and the second line
-
-require ["variables"];
-set "b" "c";
-';
-
-$s = new rcube_sieve_script($txt, array(), array('variables'));
-echo $s->as_text();
-
-?>
---EXPECT--
-# this is a comment
-# and the second line
-
-require ["variables"];
-set "b" "c";
diff --git a/plugins/managesieve/tests/parser_relational.phpt b/plugins/managesieve/tests/parser_relational.phpt
deleted file mode 100644
index 6b6f29f4c..000000000
--- a/plugins/managesieve/tests/parser_relational.phpt
+++ /dev/null
@@ -1,25 +0,0 @@
---TEST--
-Test of Sieve relational extension (RFC5231)
---SKIPIF--
---FILE--
-<?php
-include '../lib/rcube_sieve_script.php';
-
-$txt = '
-require ["relational","comparator-i;ascii-numeric"];
-# rule:[redirect]
-if header :value "ge" :comparator "i;ascii-numeric"
- ["X-Spam-score"] ["14"] {redirect "test@test.tld";}
-';
-
-$s = new rcube_sieve_script($txt);
-echo $s->as_text();
-
-?>
---EXPECT--
-require ["relational","comparator-i;ascii-numeric"];
-# rule:[redirect]
-if header :value "ge" :comparator "i;ascii-numeric" "X-Spam-score" "14"
-{
- redirect "test@test.tld";
-}
diff --git a/plugins/managesieve/tests/parser_vacation.phpt b/plugins/managesieve/tests/parser_vacation.phpt
deleted file mode 100644
index a603ff6c1..000000000
--- a/plugins/managesieve/tests/parser_vacation.phpt
+++ /dev/null
@@ -1,39 +0,0 @@
---TEST--
-Test of Sieve vacation extension (RFC5230)
---SKIPIF--
---FILE--
-<?php
-include '../lib/rcube_sieve_script.php';
-
-$txt = '
-require ["vacation"];
-# rule:[test-vacation]
-if anyof (header :contains "Subject" "vacation")
-{
- vacation :days 1 text:
-# test
-test test /* test */
-test
-.
-;
- stop;
-}
-';
-
-$s = new rcube_sieve_script($txt);
-echo $s->as_text();
-
-?>
---EXPECT--
-require ["vacation"];
-# rule:[test-vacation]
-if header :contains "Subject" "vacation"
-{
- vacation :days 1 text:
-# test
-test test /* test */
-test
-.
-;
- stop;
-}
diff --git a/plugins/managesieve/tests/parser_variables.phpt b/plugins/managesieve/tests/parser_variables.phpt
deleted file mode 100644
index cf1f8fcad..000000000
--- a/plugins/managesieve/tests/parser_variables.phpt
+++ /dev/null
@@ -1,39 +0,0 @@
---TEST--
-Test of Sieve variables extension
---SKIPIF--
---FILE--
-<?php
-include '../lib/rcube_sieve_script.php';
-
-$txt = '
-require ["variables"];
-set "honorific" "Mr";
-set "vacation" text:
-Dear ${HONORIFIC} ${last_name},
-I am out, please leave a message after the meep.
-.
-;
-set :length "b" "${a}";
-set :lower "b" "${a}";
-set :upperfirst "b" "${a}";
-set :upperfirst :lower "b" "${a}";
-set :quotewildcard "b" "Rock*";
-';
-
-$s = new rcube_sieve_script($txt, array(), array('variables'));
-echo $s->as_text();
-
-?>
---EXPECT--
-require ["variables"];
-set "honorific" "Mr";
-set "vacation" text:
-Dear ${HONORIFIC} ${last_name},
-I am out, please leave a message after the meep.
-.
-;
-set :length "b" "${a}";
-set :lower "b" "${a}";
-set :upperfirst "b" "${a}";
-set :upperfirst :lower "b" "${a}";
-set :quotewildcard "b" "Rock*";
diff --git a/plugins/managesieve/tests/parset_subaddress.phpt b/plugins/managesieve/tests/parset_subaddress.phpt
deleted file mode 100644
index 6d4d03c6e..000000000
--- a/plugins/managesieve/tests/parset_subaddress.phpt
+++ /dev/null
@@ -1,38 +0,0 @@
---TEST--
-Test of Sieve subaddress extension (RFC5233)
---SKIPIF--
---FILE--
-<?php
-include '../lib/rcube_sieve_script.php';
-
-$txt = '
-require ["envelope","subaddress","fileinto"];
-if envelope :user "To" "postmaster"
-{
- fileinto "postmaster";
- stop;
-}
-if envelope :detail :is "To" "mta-filters"
-{
- fileinto "mta-filters";
- stop;
-}
-';
-
-$s = new rcube_sieve_script($txt);
-echo $s->as_text();
-
-// -------------------------------------------------------------------------------
-?>
---EXPECT--
-require ["envelope","subaddress","fileinto"];
-if envelope :user "To" "postmaster"
-{
- fileinto "postmaster";
- stop;
-}
-if envelope :detail :is "To" "mta-filters"
-{
- fileinto "mta-filters";
- stop;
-}
diff --git a/plugins/managesieve/tests/src/parser b/plugins/managesieve/tests/src/parser
new file mode 100644
index 000000000..9c4717be4
--- /dev/null
+++ b/plugins/managesieve/tests/src/parser
@@ -0,0 +1,52 @@
+require ["fileinto","reject","envelope"];
+# rule:[spam]
+if anyof (header :contains "X-DSPAM-Result" "Spam")
+{
+ fileinto "Spam";
+ stop;
+}
+# rule:[test1]
+if anyof (header :comparator "i;ascii-casemap" :contains ["From","To"] "test@domain.tld")
+{
+ discard;
+ stop;
+}
+# rule:[test2]
+if anyof (not header :comparator "i;octet" :contains ["Subject"] "[test]", header :contains "Subject" "[test2]")
+{
+ fileinto "test";
+ stop;
+}
+# rule:[comments]
+if anyof (true) /* comment
+ * "comment" #comment */ {
+ /* comment */ stop;
+# comment
+}
+# rule:[reject]
+if size :over 5000K {
+ reject "Message over 5MB size limit. Please contact me before sending this.";
+}
+# rule:[false]
+if false # size :over 5000K
+{
+ stop; /* rule disabled */
+}
+# rule:[true]
+if true
+{
+ stop;
+}
+fileinto "Test";
+# rule:[address test]
+if address :all :is "From" "nagios@domain.tld"
+{
+ fileinto "domain.tld";
+ stop;
+}
+# rule:[envelope test]
+if envelope :domain :is "From" "domain.tld"
+{
+ fileinto "domain.tld";
+ stop;
+}
diff --git a/plugins/managesieve/tests/src/parser.out b/plugins/managesieve/tests/src/parser.out
new file mode 100644
index 000000000..385c8890d
--- /dev/null
+++ b/plugins/managesieve/tests/src/parser.out
@@ -0,0 +1,52 @@
+require ["fileinto","reject","envelope"];
+# rule:[spam]
+if header :contains "X-DSPAM-Result" "Spam"
+{
+ fileinto "Spam";
+ stop;
+}
+# rule:[test1]
+if header :contains ["From","To"] "test@domain.tld"
+{
+ discard;
+ stop;
+}
+# rule:[test2]
+if anyof (not header :comparator "i;octet" :contains "Subject" "[test]", header :contains "Subject" "[test2]")
+{
+ fileinto "test";
+ stop;
+}
+# rule:[comments]
+if true
+{
+ stop;
+}
+# rule:[reject]
+if size :over 5000K
+{
+ reject "Message over 5MB size limit. Please contact me before sending this.";
+}
+# rule:[false]
+if false # size :over 5000K
+{
+ stop;
+}
+# rule:[true]
+if true
+{
+ stop;
+}
+fileinto "Test";
+# rule:[address test]
+if address :all :is "From" "nagios@domain.tld"
+{
+ fileinto "domain.tld";
+ stop;
+}
+# rule:[envelope test]
+if envelope :domain :is "From" "domain.tld"
+{
+ fileinto "domain.tld";
+ stop;
+}
diff --git a/plugins/managesieve/tests/src/parser_body b/plugins/managesieve/tests/src/parser_body
new file mode 100644
index 000000000..bd142ed8c
--- /dev/null
+++ b/plugins/managesieve/tests/src/parser_body
@@ -0,0 +1,17 @@
+require ["body","fileinto"];
+if body :raw :contains "MAKE MONEY FAST"
+{
+ stop;
+}
+if body :content "text" :contains ["missile","coordinates"]
+{
+ fileinto "secrets";
+}
+if body :content "audio/mp3" :contains ""
+{
+ fileinto "jukebox";
+}
+if body :text :contains "project schedule"
+{
+ fileinto "project/schedule";
+}
diff --git a/plugins/managesieve/tests/src/parser_imapflags b/plugins/managesieve/tests/src/parser_imapflags
new file mode 100644
index 000000000..e67bf7cfc
--- /dev/null
+++ b/plugins/managesieve/tests/src/parser_imapflags
@@ -0,0 +1,7 @@
+require ["imap4flags"];
+# rule:[imapflags]
+if header :matches "Subject" "^Test$"
+{
+ setflag "\\Seen";
+ addflag ["\\Answered","\\Deleted"];
+}
diff --git a/plugins/managesieve/tests/src/parser_include b/plugins/managesieve/tests/src/parser_include
new file mode 100644
index 000000000..b5585a4ba
--- /dev/null
+++ b/plugins/managesieve/tests/src/parser_include
@@ -0,0 +1,7 @@
+require ["include"];
+include "script.sieve";
+# rule:[two]
+if true
+{
+ include :optional "second.sieve";
+}
diff --git a/plugins/managesieve/tests/src/parser_kep14 b/plugins/managesieve/tests/src/parser_kep14
new file mode 100644
index 000000000..1ded8d8d4
--- /dev/null
+++ b/plugins/managesieve/tests/src/parser_kep14
@@ -0,0 +1,2 @@
+# EDITOR Roundcube
+# EDITOR_VERSION 123
diff --git a/plugins/managesieve/tests/src/parser_kep14.out b/plugins/managesieve/tests/src/parser_kep14.out
new file mode 100644
index 000000000..cb7faa7f8
--- /dev/null
+++ b/plugins/managesieve/tests/src/parser_kep14.out
@@ -0,0 +1,3 @@
+require ["variables"];
+set "EDITOR" "Roundcube";
+set "EDITOR_VERSION" "123";
diff --git a/plugins/managesieve/tests/src/parser_prefix b/plugins/managesieve/tests/src/parser_prefix
new file mode 100644
index 000000000..9f6a33a1c
--- /dev/null
+++ b/plugins/managesieve/tests/src/parser_prefix
@@ -0,0 +1,5 @@
+# this is a comment
+# and the second line
+
+require ["variables"];
+set "b" "c";
diff --git a/plugins/managesieve/tests/src/parser_relational b/plugins/managesieve/tests/src/parser_relational
new file mode 100644
index 000000000..0a92fde54
--- /dev/null
+++ b/plugins/managesieve/tests/src/parser_relational
@@ -0,0 +1,6 @@
+require ["relational","comparator-i;ascii-numeric"];
+# rule:[redirect]
+if header :value "ge" :comparator "i;ascii-numeric" "X-Spam-score" "14"
+{
+ redirect "test@test.tld";
+}
diff --git a/plugins/managesieve/tests/src/parser_subaddress b/plugins/managesieve/tests/src/parser_subaddress
new file mode 100644
index 000000000..f106b796e
--- /dev/null
+++ b/plugins/managesieve/tests/src/parser_subaddress
@@ -0,0 +1,11 @@
+require ["envelope","subaddress","fileinto"];
+if envelope :user "To" "postmaster"
+{
+ fileinto "postmaster";
+ stop;
+}
+if envelope :detail :is "To" "mta-filters"
+{
+ fileinto "mta-filters";
+ stop;
+}
diff --git a/plugins/managesieve/tests/src/parser_vacation b/plugins/managesieve/tests/src/parser_vacation
new file mode 100644
index 000000000..93026db45
--- /dev/null
+++ b/plugins/managesieve/tests/src/parser_vacation
@@ -0,0 +1,12 @@
+require ["vacation"];
+# rule:[test-vacation]
+if header :contains "Subject" "vacation"
+{
+ vacation :days 1 text:
+# test
+test test /* test */
+test
+.
+;
+ stop;
+}
diff --git a/plugins/managesieve/tests/src/parser_variables b/plugins/managesieve/tests/src/parser_variables
new file mode 100644
index 000000000..bd5941c02
--- /dev/null
+++ b/plugins/managesieve/tests/src/parser_variables
@@ -0,0 +1,12 @@
+require ["variables"];
+set "honorific" "Mr";
+set "vacation" text:
+Dear ${HONORIFIC} ${last_name},
+I am out, please leave a message after the meep.
+.
+;
+set :length "b" "${a}";
+set :lower "b" "${a}";
+set :upperfirst "b" "${a}";
+set :upperfirst :lower "b" "${a}";
+set :quotewildcard "b" "Rock*";
diff --git a/plugins/managesieve/tests/tokenize.phpt b/plugins/managesieve/tests/tokenize.phpt
deleted file mode 100644
index f988653ee..000000000
--- a/plugins/managesieve/tests/tokenize.phpt
+++ /dev/null
@@ -1,66 +0,0 @@
---TEST--
-Script parsing: tokenizer
---SKIPIF--
---FILE--
-<?php
-include '../lib/rcube_sieve_script.php';
-
-$txt[1] = array(1, 'text: #test
-This is test ; message;
-Multi line
-.
-;
-');
-$txt[2] = array(0, '["test1","test2"]');
-$txt[3] = array(1, '["test"]');
-$txt[4] = array(1, '"te\\"st"');
-$txt[5] = array(0, 'test #comment');
-$txt[6] = array(0, 'text:
-test
-.
-text:
-test
-.
-');
-$txt[7] = array(1, '"\\a\\\\\\"a"');
-
-foreach ($txt as $idx => $t) {
- echo "[$idx]---------------\n";
- var_dump(rcube_sieve_script::tokenize($t[1], $t[0]));
-}
-?>
---EXPECT--
-[1]---------------
-string(34) "This is test ; message;
-Multi line"
-[2]---------------
-array(1) {
- [0]=>
- array(2) {
- [0]=>
- string(5) "test1"
- [1]=>
- string(5) "test2"
- }
-}
-[3]---------------
-array(1) {
- [0]=>
- string(4) "test"
-}
-[4]---------------
-string(5) "te"st"
-[5]---------------
-array(1) {
- [0]=>
- string(4) "test"
-}
-[6]---------------
-array(2) {
- [0]=>
- string(4) "test"
- [1]=>
- string(4) "test"
-}
-[7]---------------
-string(4) "a\"a"
diff --git a/plugins/password/drivers/virtualmin.php b/plugins/password/drivers/virtualmin.php
index b2547e07f..f9eca9633 100644
--- a/plugins/password/drivers/virtualmin.php
+++ b/plugins/password/drivers/virtualmin.php
@@ -48,6 +48,10 @@ 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/plugins/virtuser_query/virtuser_query.php b/plugins/virtuser_query/virtuser_query.php
index 21a869c57..073b4e230 100644
--- a/plugins/virtuser_query/virtuser_query.php
+++ b/plugins/virtuser_query/virtuser_query.php
@@ -14,8 +14,11 @@
*
* $rcmail_config['virtuser_query'] = array('email' => '', 'user' => '', 'host' => '');
*
+ * The email query can return more than one record to create more identities.
+ * This requires identities_level option to be set to value less than 2.
+ *
* @version @package_version@
- * @author Aleksander Machniak
+ * @author Aleksander Machniak <alec@alec.pl>
* @author Steffen Vogel
*/
class virtuser_query extends rcube_plugin
diff --git a/program/include/html.php b/program/include/html.php
index d15d50875..c6507f813 100644
--- a/program/include/html.php
+++ b/program/include/html.php
@@ -154,7 +154,7 @@ class html
$attr = array('src' => $attr);
}
return self::tag('img', $attr + array('alt' => ''), null, array_merge(self::$common_attrib,
- array('src','alt','width','height','border','usemap','onclick')));
+ array('src','alt','width','height','border','usemap','onclick')));
}
/**
diff --git a/program/include/rcmail.php b/program/include/rcmail.php
index a6b0bcd57..02f38e647 100644
--- a/program/include/rcmail.php
+++ b/program/include/rcmail.php
@@ -157,14 +157,16 @@ class rcmail extends rcube
$this->config->set_user_prefs((array)$this->user->get_prefs());
}
- $_SESSION['language'] = $this->user->language = $this->language_prop($this->config->get('language', $_SESSION['language']));
+ $lang = $this->language_prop($this->config->get('language', $_SESSION['language']));
+ $_SESSION['language'] = $this->user->language = $lang;
// set localization
- setlocale(LC_ALL, $_SESSION['language'] . '.utf8', 'en_US.utf8');
+ setlocale(LC_ALL, $lang . '.utf8', $lang . '.UTF-8', 'en_US.utf8', 'en_US.UTF-8');
// workaround for http://bugs.php.net/bug.php?id=18556
- if (in_array($_SESSION['language'], array('tr_TR', 'ku', 'az_AZ')))
- setlocale(LC_CTYPE, 'en_US' . '.utf8');
+ if (in_array($lang, array('tr_TR', 'ku', 'az_AZ'))) {
+ setlocale(LC_CTYPE, 'en_US.utf8', 'en_US.UTF-8');
+ }
}
@@ -306,7 +308,7 @@ class rcmail extends rcube
/**
* Init output object for GUI and add common scripts.
- * This will instantiate a rcmail_template object and set
+ * This will instantiate a rcube_output_html object and set
* environment vars according to the current session and configuration
*
* @param boolean True if this request is loaded in a (i)frame
@@ -453,7 +455,14 @@ class rcmail extends rcube
// Convert username to lowercase. If storage backend
// is case-insensitive we need to store always the same username (#1487113)
if ($config['login_lc']) {
- $username = mb_strtolower($username);
+ if ($config['login_lc'] == 2 || $config['login_lc'] === true) {
+ $username = mb_strtolower($username);
+ }
+ else if (strpos($username, '@')) {
+ // lowercase domain name
+ list($local, $domain) = explode('@', $username);
+ $username = $local . '@' . mb_strtolower($domain);
+ }
}
// try to resolve email address from virtuser table
@@ -463,17 +472,13 @@ class rcmail extends rcube
// Here we need IDNA ASCII
// Only rcube_contacts class is using domain names in Unicode
- $host = rcube_utils::idn_to_ascii($host);
- if (strpos($username, '@')) {
- // lowercase domain name
- list($local, $domain) = explode('@', $username);
- $username = $local . '@' . mb_strtolower($domain);
- $username = rcube_utils::idn_to_ascii($username);
- }
+ $host = rcube_utils::idn_to_ascii($host);
+ $username = rcube_utils::idn_to_ascii($username);
// user already registered -> overwrite username
- if ($user = rcube_user::query($username, $host))
+ if ($user = rcube_user::query($username, $host)) {
$username = $user->data['username'];
+ }
$storage = $this->get_storage();
@@ -1204,7 +1209,7 @@ class rcmail extends rcube
}
else {
if (!empty($date)) {
- $timestamp = rcube_strtotime($date);
+ $timestamp = rcube_utils::strtotime($date);
}
if (empty($timestamp)) {
diff --git a/program/include/rcube.php b/program/include/rcube.php
index 494b5c3dd..0e40b3c6b 100644
--- a/program/include/rcube.php
+++ b/program/include/rcube.php
@@ -25,364 +25,372 @@
* Base class of the Roundcube Framework
* implemented as singleton
*
- * @package Core
+ * @package Framework
+ * @subpackage Core
*/
class rcube
{
- const INIT_WITH_DB = 1;
- const INIT_WITH_PLUGINS = 2;
-
- /**
- * Singleton instace of rcube
- *
- * @var rcmail
- */
- static protected $instance;
-
- /**
- * Stores instance of rcube_config.
- *
- * @var rcube_config
- */
- public $config;
-
- /**
- * Instace of database class.
- *
- * @var rcube_pdo
- */
- public $db;
-
- /**
- * Instace of Memcache class.
- *
- * @var Memcache
- */
- public $memcache;
-
- /**
- * Instace of rcube_session class.
- *
- * @var rcube_session
- */
- public $session;
-
- /**
- * Instance of rcube_smtp class.
- *
- * @var rcube_smtp
- */
- public $smtp;
-
- /**
- * Instance of rcube_storage class.
- *
- * @var rcube_storage
- */
- public $storage;
-
- /**
- * Instance of rcube_output class.
- *
- * @var rcube_output
- */
- public $output;
-
- /**
- * Instance of rcube_plugin_api.
- *
- * @var rcube_plugin_api
- */
- public $plugins;
-
-
- /* private/protected vars */
- protected $texts;
- protected $caches = array();
- protected $shutdown_functions = array();
- protected $expunge_cache = false;
-
-
- /**
- * This implements the 'singleton' design pattern
- *
- * @param integer Options to initialize with this instance. See rcube::INIT_WITH_* constants
- * @return rcube The one and only instance
- */
- static function get_instance($mode = 0)
- {
- if (!self::$instance) {
- self::$instance = new rcube();
- self::$instance->init($mode);
- }
+ const INIT_WITH_DB = 1;
+ const INIT_WITH_PLUGINS = 2;
- return self::$instance;
- }
-
-
- /**
- * Private constructor
- */
- protected function __construct()
- {
- // load configuration
- $this->config = new rcube_config();
- $this->plugins = new rcube_dummy_plugin_api;
-
- register_shutdown_function(array($this, 'shutdown'));
- }
-
-
- /**
- * Initial startup function
- */
- protected function init($mode = 0)
- {
- // initialize syslog
- if ($this->config->get('log_driver') == 'syslog') {
- $syslog_id = $this->config->get('syslog_id', 'roundcube');
- $syslog_facility = $this->config->get('syslog_facility', LOG_USER);
- openlog($syslog_id, LOG_ODELAY, $syslog_facility);
- }
+ /**
+ * Singleton instace of rcube
+ *
+ * @var rcmail
+ */
+ static protected $instance;
- // connect to database
- if ($mode & self::INIT_WITH_DB) {
- $this->get_dbh();
- }
+ /**
+ * Stores instance of rcube_config.
+ *
+ * @var rcube_config
+ */
+ public $config;
+
+ /**
+ * Instace of database class.
+ *
+ * @var rcube_db
+ */
+ public $db;
+
+ /**
+ * Instace of Memcache class.
+ *
+ * @var Memcache
+ */
+ public $memcache;
+
+ /**
+ * Instace of rcube_session class.
+ *
+ * @var rcube_session
+ */
+ public $session;
+
+ /**
+ * Instance of rcube_smtp class.
+ *
+ * @var rcube_smtp
+ */
+ public $smtp;
+
+ /**
+ * Instance of rcube_storage class.
+ *
+ * @var rcube_storage
+ */
+ public $storage;
+
+ /**
+ * Instance of rcube_output class.
+ *
+ * @var rcube_output
+ */
+ public $output;
+
+ /**
+ * Instance of rcube_plugin_api.
+ *
+ * @var rcube_plugin_api
+ */
+ public $plugins;
- // create plugin API and load plugins
- if ($mode & self::INIT_WITH_PLUGINS) {
- $this->plugins = rcube_plugin_api::get_instance();
+
+ /* private/protected vars */
+ protected $texts;
+ protected $caches = array();
+ protected $shutdown_functions = array();
+ protected $expunge_cache = false;
+
+
+ /**
+ * This implements the 'singleton' design pattern
+ *
+ * @param integer Options to initialize with this instance. See rcube::INIT_WITH_* constants
+ *
+ * @return rcube The one and only instance
+ */
+ static function get_instance($mode = 0)
+ {
+ if (!self::$instance) {
+ self::$instance = new rcube();
+ self::$instance->init($mode);
+ }
+
+ return self::$instance;
}
- }
-
-
- /**
- * Get the current database connection
- *
- * @return rcube_pdo Database connection object
- */
- public function get_dbh()
- {
- if (!$this->db) {
- $config_all = $this->config->all();
- $this->db = rcube_db::factory($config_all['db_dsnw'], $config_all['db_dsnr'], $config_all['db_persistent']);
- $this->db->set_debug((bool)$config_all['sql_debug']);
+
+
+ /**
+ * Private constructor
+ */
+ protected function __construct()
+ {
+ // load configuration
+ $this->config = new rcube_config;
+ $this->plugins = new rcube_dummy_plugin_api;
+
+ register_shutdown_function(array($this, 'shutdown'));
}
- return $this->db;
- }
-
-
- /**
- * Get global handle for memcache access
- *
- * @return object Memcache
- */
- public function get_memcache()
- {
- if (!isset($this->memcache)) {
- // no memcache support in PHP
- if (!class_exists('Memcache')) {
- $this->memcache = false;
- return false;
- }
- $this->memcache = new Memcache;
- $this->mc_available = 0;
+ /**
+ * Initial startup function
+ */
+ protected function init($mode = 0)
+ {
+ // initialize syslog
+ if ($this->config->get('log_driver') == 'syslog') {
+ $syslog_id = $this->config->get('syslog_id', 'roundcube');
+ $syslog_facility = $this->config->get('syslog_facility', LOG_USER);
+ openlog($syslog_id, LOG_ODELAY, $syslog_facility);
+ }
- // add all configured hosts to pool
- $pconnect = $this->config->get('memcache_pconnect', true);
- foreach ($this->config->get('memcache_hosts', array()) as $host) {
- if (substr($host, 0, 7) != 'unix://') {
- list($host, $port) = explode(':', $host);
- if (!$port) $port = 11211;
+ // connect to database
+ if ($mode & self::INIT_WITH_DB) {
+ $this->get_dbh();
}
- else {
- $port = 0;
+
+ // create plugin API and load plugins
+ if ($mode & self::INIT_WITH_PLUGINS) {
+ $this->plugins = rcube_plugin_api::get_instance();
}
+ }
- $this->mc_available += intval($this->memcache->addServer($host, $port, $pconnect, 1, 1, 15, false, array($this, 'memcache_failure')));
- }
- // test connection and failover (will result in $this->mc_available == 0 on complete failure)
- $this->memcache->increment('__CONNECTIONTEST__', 1); // NOP if key doesn't exist
+ /**
+ * Get the current database connection
+ *
+ * @return rcube_db Database object
+ */
+ public function get_dbh()
+ {
+ if (!$this->db) {
+ $config_all = $this->config->all();
+ $this->db = rcube_db::factory($config_all['db_dsnw'], $config_all['db_dsnr'], $config_all['db_persistent']);
+ $this->db->set_debug((bool)$config_all['sql_debug']);
+ }
- if (!$this->mc_available)
- $this->memcache = false;
+ return $this->db;
}
- return $this->memcache;
- }
+ /**
+ * Get global handle for memcache access
+ *
+ * @return object Memcache
+ */
+ public function get_memcache()
+ {
+ if (!isset($this->memcache)) {
+ // no memcache support in PHP
+ if (!class_exists('Memcache')) {
+ $this->memcache = false;
+ return false;
+ }
- /**
- * Callback for memcache failure
- */
- public function memcache_failure($host, $port)
- {
- static $seen = array();
+ $this->memcache = new Memcache;
+ $this->mc_available = 0;
- // only report once
- if (!$seen["$host:$port"]++) {
- $this->mc_available--;
- self::raise_error(array('code' => 604, 'type' => 'db',
- 'line' => __LINE__, 'file' => __FILE__,
- 'message' => "Memcache failure on host $host:$port"),
- true, false);
- }
- }
-
-
- /**
- * Initialize and get cache object
- *
- * @param string $name Cache identifier
- * @param string $type Cache type ('db', 'apc' or 'memcache')
- * @param string $ttl Expiration time for cache items
- * @param bool $packed Enables/disables data serialization
- *
- * @return rcube_cache Cache object
- */
- public function get_cache($name, $type='db', $ttl=0, $packed=true)
- {
- if (!isset($this->caches[$name]) && ($userid = $this->get_user_id())) {
- $this->caches[$name] = new rcube_cache($type, $userid, $name, $ttl, $packed);
- }
+ // add all configured hosts to pool
+ $pconnect = $this->config->get('memcache_pconnect', true);
+ foreach ($this->config->get('memcache_hosts', array()) as $host) {
+ if (substr($host, 0, 7) != 'unix://') {
+ list($host, $port) = explode(':', $host);
+ if (!$port) $port = 11211;
+ }
+ else {
+ $port = 0;
+ }
- return $this->caches[$name];
- }
-
-
- /**
- * Create SMTP object and connect to server
- *
- * @param boolean True if connection should be established
- */
- public function smtp_init($connect = false)
- {
- $this->smtp = new rcube_smtp();
-
- if ($connect)
- $this->smtp->connect();
- }
-
-
- /**
- * Initialize and get storage object
- *
- * @return rcube_storage Storage object
- */
- public function get_storage()
- {
- // already initialized
- if (!is_object($this->storage)) {
- $this->storage_init();
- }
+ $this->mc_available += intval($this->memcache->addServer(
+ $host, $port, $pconnect, 1, 1, 15, false, array($this, 'memcache_failure')));
+ }
- return $this->storage;
- }
+ // test connection and failover (will result in $this->mc_available == 0 on complete failure)
+ $this->memcache->increment('__CONNECTIONTEST__', 1); // NOP if key doesn't exist
+ if (!$this->mc_available) {
+ $this->memcache = false;
+ }
+ }
- /**
- * Initialize storage object
- */
- public function storage_init()
- {
- // already initialized
- if (is_object($this->storage)) {
- return;
+ return $this->memcache;
}
- $driver = $this->config->get('storage_driver', 'imap');
- $driver_class = "rcube_{$driver}";
- if (!class_exists($driver_class)) {
- self::raise_error(array(
- 'code' => 700, 'type' => 'php',
- 'file' => __FILE__, 'line' => __LINE__,
- 'message' => "Storage driver class ($driver) not found!"),
- true, true);
+ /**
+ * Callback for memcache failure
+ */
+ public function memcache_failure($host, $port)
+ {
+ static $seen = array();
+
+ // only report once
+ if (!$seen["$host:$port"]++) {
+ $this->mc_available--;
+ self::raise_error(array(
+ 'code' => 604, 'type' => 'db',
+ 'line' => __LINE__, 'file' => __FILE__,
+ 'message' => "Memcache failure on host $host:$port"),
+ true, false);
+ }
}
- // Initialize storage object
- $this->storage = new $driver_class;
- // for backward compat. (deprecated, will be removed)
- $this->imap = $this->storage;
+ /**
+ * Initialize and get cache object
+ *
+ * @param string $name Cache identifier
+ * @param string $type Cache type ('db', 'apc' or 'memcache')
+ * @param string $ttl Expiration time for cache items
+ * @param bool $packed Enables/disables data serialization
+ *
+ * @return rcube_cache Cache object
+ */
+ public function get_cache($name, $type='db', $ttl=0, $packed=true)
+ {
+ if (!isset($this->caches[$name]) && ($userid = $this->get_user_id())) {
+ $this->caches[$name] = new rcube_cache($type, $userid, $name, $ttl, $packed);
+ }
- // enable caching of mail data
- $storage_cache = $this->config->get("{$driver}_cache");
- $messages_cache = $this->config->get('messages_cache');
- // for backward compatybility
- if ($storage_cache === null && $messages_cache === null && $this->config->get('enable_caching')) {
- $storage_cache = 'db';
- $messages_cache = true;
+ return $this->caches[$name];
}
- if ($storage_cache)
- $this->storage->set_caching($storage_cache);
- if ($messages_cache)
- $this->storage->set_messages_caching(true);
- // set pagesize from config
- $pagesize = $this->config->get('mail_pagesize');
- if (!$pagesize) {
- $pagesize = $this->config->get('pagesize', 50);
+ /**
+ * Create SMTP object and connect to server
+ *
+ * @param boolean True if connection should be established
+ */
+ public function smtp_init($connect = false)
+ {
+ $this->smtp = new rcube_smtp();
+
+ if ($connect) {
+ $this->smtp->connect();
+ }
}
- $this->storage->set_pagesize($pagesize);
-
- // set class options
- $options = array(
- 'auth_type' => $this->config->get("{$driver}_auth_type", 'check'),
- 'auth_cid' => $this->config->get("{$driver}_auth_cid"),
- 'auth_pw' => $this->config->get("{$driver}_auth_pw"),
- 'debug' => (bool) $this->config->get("{$driver}_debug"),
- 'force_caps' => (bool) $this->config->get("{$driver}_force_caps"),
- 'timeout' => (int) $this->config->get("{$driver}_timeout"),
- 'skip_deleted' => (bool) $this->config->get('skip_deleted'),
- 'driver' => $driver,
- );
-
- if (!empty($_SESSION['storage_host'])) {
- $options['host'] = $_SESSION['storage_host'];
- $options['user'] = $_SESSION['username'];
- $options['port'] = $_SESSION['storage_port'];
- $options['ssl'] = $_SESSION['storage_ssl'];
- $options['password'] = $this->decrypt($_SESSION['password']);
- $_SESSION[$driver.'_host'] = $_SESSION['storage_host'];
+
+
+ /**
+ * Initialize and get storage object
+ *
+ * @return rcube_storage Storage object
+ */
+ public function get_storage()
+ {
+ // already initialized
+ if (!is_object($this->storage)) {
+ $this->storage_init();
+ }
+
+ return $this->storage;
}
- $options = $this->plugins->exec_hook("storage_init", $options);
- // for backward compat. (deprecated, to be removed)
- $options = $this->plugins->exec_hook("imap_init", $options);
+ /**
+ * Initialize storage object
+ */
+ public function storage_init()
+ {
+ // already initialized
+ if (is_object($this->storage)) {
+ return;
+ }
- $this->storage->set_options($options);
- $this->set_storage_prop();
- }
+ $driver = $this->config->get('storage_driver', 'imap');
+ $driver_class = "rcube_{$driver}";
+ if (!class_exists($driver_class)) {
+ self::raise_error(array(
+ 'code' => 700, 'type' => 'php',
+ 'file' => __FILE__, 'line' => __LINE__,
+ 'message' => "Storage driver class ($driver) not found!"),
+ true, true);
+ }
- /**
- * Set storage parameters.
- * This must be done AFTER connecting to the server!
- */
- protected function set_storage_prop()
- {
- $storage = $this->get_storage();
+ // Initialize storage object
+ $this->storage = new $driver_class;
- $storage->set_charset($this->config->get('default_charset', RCMAIL_CHARSET));
+ // for backward compat. (deprecated, will be removed)
+ $this->imap = $this->storage;
- if ($default_folders = $this->config->get('default_folders')) {
- $storage->set_default_folders($default_folders);
- }
- if (isset($_SESSION['mbox'])) {
- $storage->set_folder($_SESSION['mbox']);
+ // enable caching of mail data
+ $storage_cache = $this->config->get("{$driver}_cache");
+ $messages_cache = $this->config->get('messages_cache');
+ // for backward compatybility
+ if ($storage_cache === null && $messages_cache === null && $this->config->get('enable_caching')) {
+ $storage_cache = 'db';
+ $messages_cache = true;
+ }
+
+ if ($storage_cache) {
+ $this->storage->set_caching($storage_cache);
+ }
+ if ($messages_cache) {
+ $this->storage->set_messages_caching(true);
+ }
+
+ // set pagesize from config
+ $pagesize = $this->config->get('mail_pagesize');
+ if (!$pagesize) {
+ $pagesize = $this->config->get('pagesize', 50);
+ }
+ $this->storage->set_pagesize($pagesize);
+
+ // set class options
+ $options = array(
+ 'auth_type' => $this->config->get("{$driver}_auth_type", 'check'),
+ 'auth_cid' => $this->config->get("{$driver}_auth_cid"),
+ 'auth_pw' => $this->config->get("{$driver}_auth_pw"),
+ 'debug' => (bool) $this->config->get("{$driver}_debug"),
+ 'force_caps' => (bool) $this->config->get("{$driver}_force_caps"),
+ 'timeout' => (int) $this->config->get("{$driver}_timeout"),
+ 'skip_deleted' => (bool) $this->config->get('skip_deleted'),
+ 'driver' => $driver,
+ );
+
+ if (!empty($_SESSION['storage_host'])) {
+ $options['host'] = $_SESSION['storage_host'];
+ $options['user'] = $_SESSION['username'];
+ $options['port'] = $_SESSION['storage_port'];
+ $options['ssl'] = $_SESSION['storage_ssl'];
+ $options['password'] = $this->decrypt($_SESSION['password']);
+ $_SESSION[$driver.'_host'] = $_SESSION['storage_host'];
+ }
+
+ $options = $this->plugins->exec_hook("storage_init", $options);
+
+ // for backward compat. (deprecated, to be removed)
+ $options = $this->plugins->exec_hook("imap_init", $options);
+
+ $this->storage->set_options($options);
+ $this->set_storage_prop();
}
- if (isset($_SESSION['page'])) {
- $storage->set_page($_SESSION['page']);
+
+
+ /**
+ * Set storage parameters.
+ * This must be done AFTER connecting to the server!
+ */
+ protected function set_storage_prop()
+ {
+ $storage = $this->get_storage();
+
+ $storage->set_charset($this->config->get('default_charset', RCMAIL_CHARSET));
+
+ if ($default_folders = $this->config->get('default_folders')) {
+ $storage->set_default_folders($default_folders);
+ }
+ if (isset($_SESSION['mbox'])) {
+ $storage->set_folder($_SESSION['mbox']);
+ }
+ if (isset($_SESSION['page'])) {
+ $storage->set_page($_SESSION['page']);
+ }
}
- }
/**
@@ -397,12 +405,17 @@ class rcube
$sess_name = $this->config->get('session_name');
$sess_domain = $this->config->get('session_domain');
+ $sess_path = $this->config->get('session_path');
$lifetime = $this->config->get('session_lifetime', 0) * 60;
// set session domain
if ($sess_domain) {
ini_set('session.cookie_domain', $sess_domain);
}
+ // set session path
+ if ($sess_path) {
+ ini_set('session.cookie_path', $sess_path);
+ }
// set session garbage collecting time according to session_lifetime
if ($lifetime) {
ini_set('session.gc_maxlifetime', $lifetime * 2);
@@ -492,433 +505,469 @@ class rcube
}
- /**
- * Get localized text in the desired language
- *
- * @param mixed $attrib Named parameters array or label name
- * @param string $domain Label domain (plugin) name
- *
- * @return string Localized text
- */
- public function gettext($attrib, $domain=null)
- {
- // load localization files if not done yet
- if (empty($this->texts))
- $this->load_language();
-
- // extract attributes
- if (is_string($attrib))
- $attrib = array('name' => $attrib);
-
- $name = $attrib['name'] ? $attrib['name'] : '';
-
- // attrib contain text values: use them from now
- if (($setval = $attrib[strtolower($_SESSION['language'])]) || ($setval = $attrib['en_us']))
- $this->texts[$name] = $setval;
-
- // check for text with domain
- if ($domain && ($text = $this->texts[$domain.'.'.$name]))
- ;
- // text does not exist
- else if (!($text = $this->texts[$name])) {
- return "[$name]";
- }
+ /**
+ * Get localized text in the desired language
+ *
+ * @param mixed $attrib Named parameters array or label name
+ * @param string $domain Label domain (plugin) name
+ *
+ * @return string Localized text
+ */
+ public function gettext($attrib, $domain=null)
+ {
+ // load localization files if not done yet
+ if (empty($this->texts)) {
+ $this->load_language();
+ }
- // replace vars in text
- if (is_array($attrib['vars'])) {
- foreach ($attrib['vars'] as $var_key => $var_value)
- $text = str_replace($var_key[0]!='$' ? '$'.$var_key : $var_key, $var_value, $text);
- }
+ // extract attributes
+ if (is_string($attrib)) {
+ $attrib = array('name' => $attrib);
+ }
- // format output
- if (($attrib['uppercase'] && strtolower($attrib['uppercase']=='first')) || $attrib['ucfirst'])
- return ucfirst($text);
- else if ($attrib['uppercase'])
- return mb_strtoupper($text);
- else if ($attrib['lowercase'])
- return mb_strtolower($text);
-
- return strtr($text, array('\n' => "\n"));
- }
-
-
- /**
- * Check if the given text label exists
- *
- * @param string $name Label name
- * @param string $domain Label domain (plugin) name or '*' for all domains
- * @param string $ref_domain Sets domain name if label is found
- *
- * @return boolean True if text exists (either in the current language or in en_US)
- */
- public function text_exists($name, $domain = null, &$ref_domain = null)
- {
- // load localization files if not done yet
- if (empty($this->texts))
- $this->load_language();
-
- if (isset($this->texts[$name])) {
- $ref_domain = '';
- return true;
- }
+ $name = $attrib['name'] ? $attrib['name'] : '';
+
+ // attrib contain text values: use them from now
+ if (($setval = $attrib[strtolower($_SESSION['language'])]) || ($setval = $attrib['en_us'])) {
+ $this->texts[$name] = $setval;
+ }
- // any of loaded domains (plugins)
- if ($domain == '*') {
- foreach ($this->plugins->loaded_plugins() as $domain)
- if (isset($this->texts[$domain.'.'.$name])) {
- $ref_domain = $domain;
- return true;
+ // check for text with domain
+ if ($domain && ($text = $this->texts[$domain.'.'.$name])) {
+ }
+ // text does not exist
+ else if (!($text = $this->texts[$name])) {
+ return "[$name]";
+ }
+
+ // replace vars in text
+ if (is_array($attrib['vars'])) {
+ foreach ($attrib['vars'] as $var_key => $var_value) {
+ $text = str_replace($var_key[0]!='$' ? '$'.$var_key : $var_key, $var_value, $text);
+ }
}
+
+ // format output
+ if (($attrib['uppercase'] && strtolower($attrib['uppercase'] == 'first')) || $attrib['ucfirst']) {
+ return ucfirst($text);
+ }
+ else if ($attrib['uppercase']) {
+ return mb_strtoupper($text);
+ }
+ else if ($attrib['lowercase']) {
+ return mb_strtolower($text);
+ }
+
+ return strtr($text, array('\n' => "\n"));
}
- // specified domain
- else if ($domain) {
- $ref_domain = $domain;
- return isset($this->texts[$domain.'.'.$name]);
+
+
+ /**
+ * Check if the given text label exists
+ *
+ * @param string $name Label name
+ * @param string $domain Label domain (plugin) name or '*' for all domains
+ * @param string $ref_domain Sets domain name if label is found
+ *
+ * @return boolean True if text exists (either in the current language or in en_US)
+ */
+ public function text_exists($name, $domain = null, &$ref_domain = null)
+ {
+ // load localization files if not done yet
+ if (empty($this->texts)) {
+ $this->load_language();
+ }
+
+ if (isset($this->texts[$name])) {
+ $ref_domain = '';
+ return true;
+ }
+
+ // any of loaded domains (plugins)
+ if ($domain == '*') {
+ foreach ($this->plugins->loaded_plugins() as $domain) {
+ if (isset($this->texts[$domain.'.'.$name])) {
+ $ref_domain = $domain;
+ return true;
+ }
+ }
+ }
+ // specified domain
+ else if ($domain) {
+ $ref_domain = $domain;
+ return isset($this->texts[$domain.'.'.$name]);
+ }
+
+ return false;
}
- return false;
- }
- /**
- * Load a localization package
- *
- * @param string Language ID
- */
- public function load_language($lang = null, $add = array())
- {
- $lang = $this->language_prop(($lang ? $lang : $_SESSION['language']));
+ /**
+ * Load a localization package
+ *
+ * @param string Language ID
+ * @param array Additional text labels/messages
+ */
+ public function load_language($lang = null, $add = array())
+ {
+ $lang = $this->language_prop(($lang ? $lang : $_SESSION['language']));
+
+ // load localized texts
+ if (empty($this->texts) || $lang != $_SESSION['language']) {
+ $this->texts = array();
- // load localized texts
- if (empty($this->texts) || $lang != $_SESSION['language']) {
- $this->texts = array();
+ // handle empty lines after closing PHP tag in localization files
+ ob_start();
- // handle empty lines after closing PHP tag in localization files
- ob_start();
+ // get english labels (these should be complete)
+ @include(INSTALL_PATH . 'program/localization/en_US/labels.inc');
+ @include(INSTALL_PATH . 'program/localization/en_US/messages.inc');
- // get english labels (these should be complete)
- @include(INSTALL_PATH . 'program/localization/en_US/labels.inc');
- @include(INSTALL_PATH . 'program/localization/en_US/messages.inc');
+ if (is_array($labels))
+ $this->texts = $labels;
+ if (is_array($messages))
+ $this->texts = array_merge($this->texts, $messages);
- if (is_array($labels))
- $this->texts = $labels;
- if (is_array($messages))
- $this->texts = array_merge($this->texts, $messages);
+ // include user language files
+ if ($lang != 'en' && $lang != 'en_US' && is_dir(INSTALL_PATH . 'program/localization/' . $lang)) {
+ include_once(INSTALL_PATH . 'program/localization/' . $lang . '/labels.inc');
+ include_once(INSTALL_PATH . 'program/localization/' . $lang . '/messages.inc');
- // include user language files
- if ($lang != 'en' && $lang != 'en_US' && is_dir(INSTALL_PATH . 'program/localization/' . $lang)) {
- include_once(INSTALL_PATH . 'program/localization/' . $lang . '/labels.inc');
- include_once(INSTALL_PATH . 'program/localization/' . $lang . '/messages.inc');
+ if (is_array($labels))
+ $this->texts = array_merge($this->texts, $labels);
+ if (is_array($messages))
+ $this->texts = array_merge($this->texts, $messages);
+ }
- if (is_array($labels))
- $this->texts = array_merge($this->texts, $labels);
- if (is_array($messages))
- $this->texts = array_merge($this->texts, $messages);
- }
+ ob_end_clean();
- ob_end_clean();
+ $_SESSION['language'] = $lang;
+ }
- $_SESSION['language'] = $lang;
+ // append additional texts (from plugin)
+ if (is_array($add) && !empty($add)) {
+ $this->texts += $add;
+ }
}
- // append additional texts (from plugin)
- if (is_array($add) && !empty($add))
- $this->texts += $add;
- }
-
-
- /**
- * Check the given string and return a valid language code
- *
- * @param string Language code
- * @return string Valid language code
- */
- protected function language_prop($lang)
- {
- static $rcube_languages, $rcube_language_aliases;
-
- // user HTTP_ACCEPT_LANGUAGE if no language is specified
- if (empty($lang) || $lang == 'auto') {
- $accept_langs = explode(',', $_SERVER['HTTP_ACCEPT_LANGUAGE']);
- $lang = str_replace('-', '_', $accept_langs[0]);
- }
-
- if (empty($rcube_languages)) {
- @include(INSTALL_PATH . 'program/localization/index.inc');
- }
- // check if we have an alias for that language
- if (!isset($rcube_languages[$lang]) && isset($rcube_language_aliases[$lang])) {
- $lang = $rcube_language_aliases[$lang];
+ /**
+ * Check the given string and return a valid language code
+ *
+ * @param string Language code
+ *
+ * @return string Valid language code
+ */
+ protected function language_prop($lang)
+ {
+ static $rcube_languages, $rcube_language_aliases;
+
+ // user HTTP_ACCEPT_LANGUAGE if no language is specified
+ if (empty($lang) || $lang == 'auto') {
+ $accept_langs = explode(',', $_SERVER['HTTP_ACCEPT_LANGUAGE']);
+ $lang = str_replace('-', '_', $accept_langs[0]);
+ }
+
+ if (empty($rcube_languages)) {
+ @include(INSTALL_PATH . 'program/localization/index.inc');
+ }
+
+ // check if we have an alias for that language
+ if (!isset($rcube_languages[$lang]) && isset($rcube_language_aliases[$lang])) {
+ $lang = $rcube_language_aliases[$lang];
+ }
+ // try the first two chars
+ else if (!isset($rcube_languages[$lang])) {
+ $short = substr($lang, 0, 2);
+
+ // check if we have an alias for the short language code
+ if (!isset($rcube_languages[$short]) && isset($rcube_language_aliases[$short])) {
+ $lang = $rcube_language_aliases[$short];
+ }
+ // expand 'nn' to 'nn_NN'
+ else if (!isset($rcube_languages[$short])) {
+ $lang = $short.'_'.strtoupper($short);
+ }
+ }
+
+ if (!isset($rcube_languages[$lang]) || !is_dir(INSTALL_PATH . 'program/localization/' . $lang)) {
+ $lang = 'en_US';
+ }
+
+ return $lang;
}
- // try the first two chars
- else if (!isset($rcube_languages[$lang])) {
- $short = substr($lang, 0, 2);
-
- // check if we have an alias for the short language code
- if (!isset($rcube_languages[$short]) && isset($rcube_language_aliases[$short])) {
- $lang = $rcube_language_aliases[$short];
- }
- // expand 'nn' to 'nn_NN'
- else if (!isset($rcube_languages[$short])) {
- $lang = $short.'_'.strtoupper($short);
- }
+
+
+ /**
+ * Read directory program/localization and return a list of available languages
+ *
+ * @return array List of available localizations
+ */
+ public function list_languages()
+ {
+ static $sa_languages = array();
+
+ if (!sizeof($sa_languages)) {
+ @include(INSTALL_PATH . 'program/localization/index.inc');
+
+ if ($dh = @opendir(INSTALL_PATH . 'program/localization')) {
+ while (($name = readdir($dh)) !== false) {
+ if ($name[0] == '.' || !is_dir(INSTALL_PATH . 'program/localization/' . $name)) {
+ continue;
+ }
+
+ if ($label = $rcube_languages[$name]) {
+ $sa_languages[$name] = $label;
+ }
+ }
+ closedir($dh);
+ }
+ }
+
+ return $sa_languages;
}
- if (!isset($rcube_languages[$lang]) || !is_dir(INSTALL_PATH . 'program/localization/' . $lang)) {
- $lang = 'en_US';
+
+ /**
+ * Encrypt using 3DES
+ *
+ * @param string $clear clear text input
+ * @param string $key encryption key to retrieve from the configuration, defaults to 'des_key'
+ * @param boolean $base64 whether or not to base64_encode() the result before returning
+ *
+ * @return string encrypted text
+ */
+ public function encrypt($clear, $key = 'des_key', $base64 = true)
+ {
+ if (!$clear) {
+ return '';
+ }
+
+ /*-
+ * Add a single canary byte to the end of the clear text, which
+ * will help find out how much of padding will need to be removed
+ * upon decryption; see http://php.net/mcrypt_generic#68082
+ */
+ $clear = pack("a*H2", $clear, "80");
+
+ if (function_exists('mcrypt_module_open') &&
+ ($td = mcrypt_module_open(MCRYPT_TripleDES, "", MCRYPT_MODE_CBC, ""))
+ ) {
+ $iv = $this->create_iv(mcrypt_enc_get_iv_size($td));
+ mcrypt_generic_init($td, $this->config->get_crypto_key($key), $iv);
+ $cipher = $iv . mcrypt_generic($td, $clear);
+ mcrypt_generic_deinit($td);
+ mcrypt_module_close($td);
+ }
+ else {
+ @include_once 'des.inc';
+
+ if (function_exists('des')) {
+ $des_iv_size = 8;
+ $iv = $this->create_iv($des_iv_size);
+ $cipher = $iv . des($this->config->get_crypto_key($key), $clear, 1, 1, $iv);
+ }
+ else {
+ self::raise_error(array(
+ 'code' => 500, 'type' => 'php',
+ 'file' => __FILE__, 'line' => __LINE__,
+ 'message' => "Could not perform encryption; make sure Mcrypt is installed or lib/des.inc is available"
+ ), true, true);
+ }
+ }
+
+ return $base64 ? base64_encode($cipher) : $cipher;
}
- return $lang;
- }
+ /**
+ * Decrypt 3DES-encrypted string
+ *
+ * @param string $cipher encrypted text
+ * @param string $key encryption key to retrieve from the configuration, defaults to 'des_key'
+ * @param boolean $base64 whether or not input is base64-encoded
+ *
+ * @return string decrypted text
+ */
+ public function decrypt($cipher, $key = 'des_key', $base64 = true)
+ {
+ if (!$cipher) {
+ return '';
+ }
- /**
- * Read directory program/localization and return a list of available languages
- *
- * @return array List of available localizations
- */
- public function list_languages()
- {
- static $sa_languages = array();
+ $cipher = $base64 ? base64_decode($cipher) : $cipher;
- if (!sizeof($sa_languages)) {
- @include(INSTALL_PATH . 'program/localization/index.inc');
+ if (function_exists('mcrypt_module_open') &&
+ ($td = mcrypt_module_open(MCRYPT_TripleDES, "", MCRYPT_MODE_CBC, ""))
+ ) {
+ $iv_size = mcrypt_enc_get_iv_size($td);
+ $iv = substr($cipher, 0, $iv_size);
- if ($dh = @opendir(INSTALL_PATH . 'program/localization')) {
- while (($name = readdir($dh)) !== false) {
- if ($name[0] == '.' || !is_dir(INSTALL_PATH . 'program/localization/' . $name))
- continue;
+ // session corruption? (#1485970)
+ if (strlen($iv) < $iv_size) {
+ return '';
+ }
- if ($label = $rcube_languages[$name])
- $sa_languages[$name] = $label;
+ $cipher = substr($cipher, $iv_size);
+ mcrypt_generic_init($td, $this->config->get_crypto_key($key), $iv);
+ $clear = mdecrypt_generic($td, $cipher);
+ mcrypt_generic_deinit($td);
+ mcrypt_module_close($td);
+ }
+ else {
+ @include_once 'des.inc';
+
+ if (function_exists('des')) {
+ $des_iv_size = 8;
+ $iv = substr($cipher, 0, $des_iv_size);
+ $cipher = substr($cipher, $des_iv_size);
+ $clear = des($this->config->get_crypto_key($key), $cipher, 0, 1, $iv);
+ }
+ else {
+ self::raise_error(array(
+ 'code' => 500, 'type' => 'php',
+ 'file' => __FILE__, 'line' => __LINE__,
+ 'message' => "Could not perform decryption; make sure Mcrypt is installed or lib/des.inc is available"
+ ), true, true);
+ }
}
- closedir($dh);
- }
+
+ /*-
+ * Trim PHP's padding and the canary byte; see note in
+ * rcube::encrypt() and http://php.net/mcrypt_generic#68082
+ */
+ $clear = substr(rtrim($clear, "\0"), 0, -1);
+
+ return $clear;
}
- return $sa_languages;
- }
-
-
- /**
- * Encrypt using 3DES
- *
- * @param string $clear clear text input
- * @param string $key encryption key to retrieve from the configuration, defaults to 'des_key'
- * @param boolean $base64 whether or not to base64_encode() the result before returning
- *
- * @return string encrypted text
- */
- public function encrypt($clear, $key = 'des_key', $base64 = true)
- {
- if (!$clear)
- return '';
-
- /*-
- * Add a single canary byte to the end of the clear text, which
- * will help find out how much of padding will need to be removed
- * upon decryption; see http://php.net/mcrypt_generic#68082
+
+ /**
+ * Generates encryption initialization vector (IV)
+ *
+ * @param int Vector size
+ *
+ * @return string Vector string
*/
- $clear = pack("a*H2", $clear, "80");
-
- if (function_exists('mcrypt_module_open') &&
- ($td = mcrypt_module_open(MCRYPT_TripleDES, "", MCRYPT_MODE_CBC, ""))) {
- $iv = $this->create_iv(mcrypt_enc_get_iv_size($td));
- mcrypt_generic_init($td, $this->config->get_crypto_key($key), $iv);
- $cipher = $iv . mcrypt_generic($td, $clear);
- mcrypt_generic_deinit($td);
- mcrypt_module_close($td);
- }
- else {
- @include_once 'des.inc';
-
- if (function_exists('des')) {
- $des_iv_size = 8;
- $iv = $this->create_iv($des_iv_size);
- $cipher = $iv . des($this->config->get_crypto_key($key), $clear, 1, 1, $iv);
- }
- else {
- self::raise_error(array(
- 'code' => 500, 'type' => 'php',
- 'file' => __FILE__, 'line' => __LINE__,
- 'message' => "Could not perform encryption; make sure Mcrypt is installed or lib/des.inc is available"
- ), true, true);
- }
+ private function create_iv($size)
+ {
+ // mcrypt_create_iv() can be slow when system lacks entrophy
+ // we'll generate IV vector manually
+ $iv = '';
+ for ($i = 0; $i < $size; $i++) {
+ $iv .= chr(mt_rand(0, 255));
+ }
+
+ return $iv;
}
- return $base64 ? base64_encode($cipher) : $cipher;
- }
-
- /**
- * Decrypt 3DES-encrypted string
- *
- * @param string $cipher encrypted text
- * @param string $key encryption key to retrieve from the configuration, defaults to 'des_key'
- * @param boolean $base64 whether or not input is base64-encoded
- *
- * @return string decrypted text
- */
- public function decrypt($cipher, $key = 'des_key', $base64 = true)
- {
- if (!$cipher)
- return '';
-
- $cipher = $base64 ? base64_decode($cipher) : $cipher;
-
- if (function_exists('mcrypt_module_open') &&
- ($td = mcrypt_module_open(MCRYPT_TripleDES, "", MCRYPT_MODE_CBC, ""))) {
- $iv_size = mcrypt_enc_get_iv_size($td);
- $iv = substr($cipher, 0, $iv_size);
-
- // session corruption? (#1485970)
- if (strlen($iv) < $iv_size)
- return '';
- $cipher = substr($cipher, $iv_size);
- mcrypt_generic_init($td, $this->config->get_crypto_key($key), $iv);
- $clear = mdecrypt_generic($td, $cipher);
- mcrypt_generic_deinit($td);
- mcrypt_module_close($td);
- }
- else {
- @include_once 'des.inc';
-
- if (function_exists('des')) {
- $des_iv_size = 8;
- $iv = substr($cipher, 0, $des_iv_size);
- $cipher = substr($cipher, $des_iv_size);
- $clear = des($this->config->get_crypto_key($key), $cipher, 0, 1, $iv);
- }
- else {
- self::raise_error(array(
- 'code' => 500, 'type' => 'php',
- 'file' => __FILE__, 'line' => __LINE__,
- 'message' => "Could not perform decryption; make sure Mcrypt is installed or lib/des.inc is available"
- ), true, true);
- }
+ /**
+ * Build a valid URL to this instance of Roundcube
+ *
+ * @param mixed Either a string with the action or url parameters as key-value pairs
+ * @return string Valid application URL
+ */
+ public function url($p)
+ {
+ // STUB: should be overloaded by the application
+ return '';
}
- /*-
- * Trim PHP's padding and the canary byte; see note in
- * rcube::encrypt() and http://php.net/mcrypt_generic#68082
+
+ /**
+ * Function to be executed in script shutdown
+ * Registered with register_shutdown_function()
*/
- $clear = substr(rtrim($clear, "\0"), 0, -1);
-
- return $clear;
- }
-
- /**
- * Generates encryption initialization vector (IV)
- *
- * @param int Vector size
- * @return string Vector string
- */
- private function create_iv($size)
- {
- // mcrypt_create_iv() can be slow when system lacks entrophy
- // we'll generate IV vector manually
- $iv = '';
- for ($i = 0; $i < $size; $i++)
- $iv .= chr(mt_rand(0, 255));
- return $iv;
- }
-
-
- /**
- * Build a valid URL to this instance of Roundcube
- *
- * @param mixed Either a string with the action or url parameters as key-value pairs
- * @return string Valid application URL
- */
- public function url($p)
- {
- // STUB: should be overloaded by the application
- return '';
- }
-
-
- /**
- * Function to be executed in script shutdown
- * Registered with register_shutdown_function()
- */
- public function shutdown()
- {
- foreach ($this->shutdown_functions as $function)
- call_user_func($function);
-
- if (is_object($this->smtp))
- $this->smtp->disconnect();
-
- foreach ($this->caches as $cache) {
- if (is_object($cache))
- $cache->close();
- }
+ public function shutdown()
+ {
+ foreach ($this->shutdown_functions as $function) {
+ call_user_func($function);
+ }
+
+ if (is_object($this->smtp)) {
+ $this->smtp->disconnect();
+ }
+
+ foreach ($this->caches as $cache) {
+ if (is_object($cache)) {
+ $cache->close();
+ }
+ }
- if (is_object($this->storage)) {
- if ($this->expunge_cache)
- $this->storage->expunge_cache();
- $this->storage->close();
+ if (is_object($this->storage)) {
+ if ($this->expunge_cache) {
+ $this->storage->expunge_cache();
+ }
+ $this->storage->close();
+ }
}
- }
-
-
- /**
- * Registers shutdown function to be executed on shutdown.
- * The functions will be executed before destroying any
- * objects like smtp, imap, session, etc.
- *
- * @param callback Function callback
- */
- public function add_shutdown_function($function)
- {
- $this->shutdown_functions[] = $function;
- }
-
-
- /**
- * Construct shell command, execute it and return output as string.
- * Keywords {keyword} are replaced with arguments
- *
- * @param $cmd Format string with {keywords} to be replaced
- * @param $values (zero, one or more arrays can be passed)
- * @return output of command. shell errors not detectable
- */
- public static function exec(/* $cmd, $values1 = array(), ... */)
- {
- $args = func_get_args();
- $cmd = array_shift($args);
- $values = $replacements = array();
-
- // merge values into one array
- foreach ($args as $arg)
- $values += (array)$arg;
-
- preg_match_all('/({(-?)([a-z]\w*)})/', $cmd, $matches, PREG_SET_ORDER);
- foreach ($matches as $tags) {
- list(, $tag, $option, $key) = $tags;
- $parts = array();
-
- if ($option) {
- foreach ((array)$values["-$key"] as $key => $value) {
- if ($value === true || $value === false || $value === null)
- $parts[] = $value ? $key : "";
- else foreach ((array)$value as $val)
- $parts[] = "$key " . escapeshellarg($val);
- }
- }
- else {
- foreach ((array)$values[$key] as $value)
- $parts[] = escapeshellarg($value);
- }
-
- $replacements[$tag] = join(" ", $parts);
+
+
+ /**
+ * Registers shutdown function to be executed on shutdown.
+ * The functions will be executed before destroying any
+ * objects like smtp, imap, session, etc.
+ *
+ * @param callback Function callback
+ */
+ public function add_shutdown_function($function)
+ {
+ $this->shutdown_functions[] = $function;
}
- // use strtr behaviour of going through source string once
- $cmd = strtr($cmd, $replacements);
- return (string)shell_exec($cmd);
- }
+ /**
+ * Construct shell command, execute it and return output as string.
+ * Keywords {keyword} are replaced with arguments
+ *
+ * @param $cmd Format string with {keywords} to be replaced
+ * @param $values (zero, one or more arrays can be passed)
+ *
+ * @return output of command. shell errors not detectable
+ */
+ public static function exec(/* $cmd, $values1 = array(), ... */)
+ {
+ $args = func_get_args();
+ $cmd = array_shift($args);
+ $values = $replacements = array();
+
+ // merge values into one array
+ foreach ($args as $arg) {
+ $values += (array)$arg;
+ }
+
+ preg_match_all('/({(-?)([a-z]\w*)})/', $cmd, $matches, PREG_SET_ORDER);
+ foreach ($matches as $tags) {
+ list(, $tag, $option, $key) = $tags;
+ $parts = array();
+
+ if ($option) {
+ foreach ((array)$values["-$key"] as $key => $value) {
+ if ($value === true || $value === false || $value === null) {
+ $parts[] = $value ? $key : "";
+ }
+ else {
+ foreach ((array)$value as $val) {
+ $parts[] = "$key " . escapeshellarg($val);
+ }
+ }
+ }
+ }
+ else {
+ foreach ((array)$values[$key] as $value) {
+ $parts[] = escapeshellarg($value);
+ }
+ }
+
+ $replacements[$tag] = join(" ", $parts);
+ }
+
+ // use strtr behaviour of going through source string once
+ $cmd = strtr($cmd, $replacements);
+
+ return (string)shell_exec($cmd);
+ }
/**
diff --git a/program/include/rcube_bc.inc b/program/include/rcube_bc.inc
index 1932f86e2..1894873e6 100644
--- a/program/include/rcube_bc.inc
+++ b/program/include/rcube_bc.inc
@@ -38,11 +38,6 @@ function get_table_name($table)
return rcmail::get_instance()->db->table_name($table);
}
-function get_sequence_name($sequence)
-{
- return rcmail::get_instance()->db->sequence_name($sequence);
-}
-
function rcube_label($p, $domain=null)
{
return rcmail::get_instance()->gettext($p, $domain);
diff --git a/program/include/rcube_browser.php b/program/include/rcube_browser.php
index 06033e036..7cfae709d 100644
--- a/program/include/rcube_browser.php
+++ b/program/include/rcube_browser.php
@@ -20,8 +20,6 @@
*/
/**
- * rcube_browser
- *
* Provide details about the client's browser based on the User-Agent header
*
* @package Core
diff --git a/program/include/rcube_charset.php b/program/include/rcube_charset.php
index 380d14978..1740a6096 100644
--- a/program/include/rcube_charset.php
+++ b/program/include/rcube_charset.php
@@ -181,6 +181,12 @@ class rcube_charset
$to = empty($to) ? strtoupper(RCMAIL_CHARSET) : self::parse_charset($to);
$from = self::parse_charset($from);
+ // It is a common case when UTF-16 charset is used with US-ASCII content (#1488654)
+ // In that case we can just skip the conversion (use UTF-8)
+ if ($from == 'UTF-16' && !preg_match('/[^\x00-\x7F]/', $str)) {
+ $from = 'UTF-8';
+ }
+
if ($from == $to || empty($str) || empty($from)) {
return $str;
}
diff --git a/program/include/rcube_config.php b/program/include/rcube_config.php
index e2997906c..41acc80dd 100644
--- a/program/include/rcube_config.php
+++ b/program/include/rcube_config.php
@@ -42,6 +42,7 @@ class rcube_config
'default_folders' => 'default_imap_folders',
'mail_pagesize' => 'pagesize',
'addressbook_pagesize' => 'pagesize',
+ 'reply_mode' => 'top_posting',
);
diff --git a/program/include/rcube_imap.php b/program/include/rcube_imap.php
index 5dd9c1250..66b5c4bd6 100644
--- a/program/include/rcube_imap.php
+++ b/program/include/rcube_imap.php
@@ -2222,6 +2222,10 @@ class rcube_imap extends rcube_storage
$folder = $this->folder;
}
+ if (!$this->check_connection()) {
+ return false;
+ }
+
// make sure folder exists
if ($this->folder_exists($folder)) {
if ($is_file) {
diff --git a/program/include/rcube_imap_cache.php b/program/include/rcube_imap_cache.php
index a061a1f6e..f36ace0eb 100644
--- a/program/include/rcube_imap_cache.php
+++ b/program/include/rcube_imap_cache.php
@@ -5,7 +5,7 @@
| program/include/rcube_imap_cache.php |
| |
| This file is part of the Roundcube Webmail client |
- | Copyright (C) 2005-2011, The Roundcube Dev Team |
+ | Copyright (C) 2005-2012, The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
@@ -350,11 +350,11 @@ class rcube_imap_cache
function get_message($mailbox, $uid, $update = true, $cache = true)
{
// Check internal cache
- if ($this->icache['message']
- && $this->icache['message']['mailbox'] == $mailbox
- && $this->icache['message']['object']->uid == $uid
+ if ($this->icache['__message']
+ && $this->icache['__message']['mailbox'] == $mailbox
+ && $this->icache['__message']['object']->uid == $uid
) {
- return $this->icache['message']['object'];
+ return $this->icache['__message']['object'];
}
$sql_result = $this->db->query(
@@ -386,7 +386,7 @@ class rcube_imap_cache
// Save current message from internal cache
$this->save_icache();
- $this->icache['message'] = array(
+ $this->icache['__message'] = array(
'object' => $message,
'mailbox' => $mailbox,
'exists' => $found,
@@ -459,20 +459,28 @@ class rcube_imap_cache
*/
function change_flag($mailbox, $uids, $flag, $enabled = false)
{
+ if (empty($uids)) {
+ return;
+ }
+
$flag = strtoupper($flag);
$idx = (int) array_search($flag, $this->flags);
+ $uids = (array) $uids;
if (!$idx) {
return;
}
// Internal cache update
- if ($uids && count($uids) == 1 && ($uid = current($uids))
- && ($message = $this->icache['message'])
- && $message['mailbox'] == $mailbox && $message['object']->uid == $uid
+ if (($message = $this->icache['__message'])
+ && $message['mailbox'] === $mailbox
+ && in_array($message['object']->uid, $uids)
) {
$message['object']->flags[$flag] = $enabled;
- return;
+
+ if (count($uids) == 1) {
+ return;
+ }
}
$this->db->query(
@@ -481,7 +489,7 @@ class rcube_imap_cache
.", flags = flags ".($enabled ? "+ $idx" : "- $idx")
." WHERE user_id = ?"
." AND mailbox = ?"
- .($uids !== null ? " AND uid IN (".$this->db->array2list((array)$uids, 'integer').")" : "")
+ .($uids !== null ? " AND uid IN (".$this->db->array2list($uids, 'integer').")" : "")
." AND (flags & $idx) ".($enabled ? "= 0" : "= $idx"),
$this->userid, $mailbox);
}
@@ -503,10 +511,11 @@ class rcube_imap_cache
}
else {
// Remove the message from internal cache
- if (!empty($uids) && !is_array($uids) && ($message = $this->icache['message'])
- && $message['mailbox'] == $mailbox && $message['object']->uid == $uids
+ if (!empty($uids) && ($message = $this->icache['__message'])
+ && $message['mailbox'] === $mailbox
+ && in_array($message['object']->uid, (array)$uids)
) {
- $this->icache['message'] = null;
+ $this->icache['__message'] = null;
}
$this->db->query(
@@ -608,13 +617,13 @@ class rcube_imap_cache
// get expiration timestamp
$ts = get_offset_time($ttl, -1);
- $this->db->query("DELETE FROM ".get_table_name('cache_messages')
+ $this->db->query("DELETE FROM ".$this->db->table_name('cache_messages')
." WHERE changed < " . $this->db->fromunixtime($ts));
- $this->db->query("DELETE FROM ".get_table_name('cache_index')
+ $this->db->query("DELETE FROM ".$this->db->table_name('cache_index')
." WHERE changed < " . $this->db->fromunixtime($ts));
- $this->db->query("DELETE FROM ".get_table_name('cache_thread')
+ $this->db->query("DELETE FROM ".$this->db->table_name('cache_thread')
." WHERE changed < " . $this->db->fromunixtime($ts));
}
@@ -763,6 +772,11 @@ class rcube_imap_cache
$object = $index['object'];
$is_thread = is_a($object, 'rcube_result_thread');
+ // sanity check
+ if (empty($object)) {
+ return false;
+ }
+
// Get mailbox data (UIDVALIDITY, counters, etc.) for status check
$mbox_data = $this->imap->folder_data($mailbox);
@@ -1078,7 +1092,7 @@ class rcube_imap_cache
private function save_icache()
{
// Save current message from internal cache
- if ($message = $this->icache['message']) {
+ if ($message = $this->icache['__message']) {
// clean up some object's data
$object = $this->message_object_prepare($message['object']);
@@ -1089,7 +1103,7 @@ class rcube_imap_cache
$this->add_message($message['mailbox'], $object, !$message['exists']);
}
- $this->icache['message']['md5sum'] = $md5sum;
+ $this->icache['__message']['md5sum'] = $md5sum;
}
}
diff --git a/program/include/rcube_imap_generic.php b/program/include/rcube_imap_generic.php
index 915a11aad..c3cfabc3a 100644
--- a/program/include/rcube_imap_generic.php
+++ b/program/include/rcube_imap_generic.php
@@ -2538,7 +2538,7 @@ class rcube_imap_generic
{
unset($this->data['APPENDUID']);
- if (!$mailbox) {
+ if ($mailbox === null || $mailbox === '') {
return false;
}
@@ -2603,7 +2603,7 @@ class rcube_imap_generic
{
unset($this->data['APPENDUID']);
- if (!$mailbox) {
+ if ($mailbox === null || $mailbox === '') {
return false;
}
@@ -2612,6 +2612,7 @@ class rcube_imap_generic
if (file_exists(realpath($path))) {
$in_fp = fopen($path, 'r');
}
+
if (!$in_fp) {
$this->setError(self::ERROR_UNKNOWN, "Couldn't open $path for reading");
return false;
diff --git a/program/include/rcube_ldap.php b/program/include/rcube_ldap.php
index dbab0fd06..ad2ccddeb 100644
--- a/program/include/rcube_ldap.php
+++ b/program/include/rcube_ldap.php
@@ -139,6 +139,11 @@ class rcube_ldap extends rcube_addressbook
unset($this->coltypes[$childcol]); // remove address child col from global coltypes list
}
}
+
+ // at least one address type must be specified
+ if (empty($this->coltypes['address']['subtypes'])) {
+ $this->coltypes['address']['subtypes'] = array('home');
+ }
}
else if ($this->coltypes['address']) {
$this->coltypes['address'] += array('type' => 'textarea', 'childs' => null, 'size' => 40);
diff --git a/program/include/rcube_message.php b/program/include/rcube_message.php
index 9d36acf38..6af1d0133 100644
--- a/program/include/rcube_message.php
+++ b/program/include/rcube_message.php
@@ -50,13 +50,14 @@ class rcube_message
*/
private $mime;
private $opt = array();
- private $inline_parts = array();
private $parse_alternative = false;
- public $uid = null;
+ public $uid;
+ public $folder;
public $headers;
public $parts = array();
public $mime_parts = array();
+ public $inline_parts = array();
public $attachments = array();
public $subject = '';
public $sender = null;
@@ -68,17 +69,22 @@ class rcube_message
*
* Provide a uid, and parse message structure.
*
- * @param string $uid The message UID.
+ * @param string $uid The message UID.
+ * @param string $folder Folder name
*
* @see self::$app, self::$storage, self::$opt, self::$parts
*/
- function __construct($uid)
+ function __construct($uid, $folder = null)
{
$this->uid = $uid;
$this->app = rcube::get_instance();
$this->storage = $this->app->get_storage();
+ $this->folder = strlen($folder) ? $folder : $this->storage->get_folder();
$this->storage->set_options(array('all_headers' => true));
+ // Set current folder
+ $this->storage->set_folder($this->folder);
+
$this->headers = $this->storage->get_message($uid);
if (!$this->headers)
@@ -179,10 +185,12 @@ class rcube_message
}
return $fp ? true : $part->body;
}
+
// get from IMAP
+ $this->storage->set_folder($this->folder);
+
return $this->storage->get_message_part($this->uid, $mime_id, $part, NULL, $fp, $skip_charset_conv);
- } else
- return null;
+ }
}
@@ -637,8 +645,10 @@ class rcube_message
function tnef_decode(&$part)
{
// @TODO: attachment may be huge, hadle it via file
- if (!isset($part->body))
+ if (!isset($part->body)) {
+ $this->storage->set_folder($this->folder);
$part->body = $this->storage->get_message_part($this->uid, $part->mime_id, $part);
+ }
$parts = array();
$tnef = new tnef_decoder;
@@ -673,8 +683,10 @@ class rcube_message
function uu_decode(&$part)
{
// @TODO: messages may be huge, hadle body via file
- if (!isset($part->body))
+ if (!isset($part->body)) {
+ $this->storage->set_folder($this->folder);
$part->body = $this->storage->get_message_part($this->uid, $part->mime_id, $part);
+ }
$parts = array();
// FIXME: line length is max.65?
diff --git a/program/include/rcube_mime.php b/program/include/rcube_mime.php
index e1f736a78..d8e04a97c 100644
--- a/program/include/rcube_mime.php
+++ b/program/include/rcube_mime.php
@@ -541,10 +541,10 @@ class rcube_mime
$prefix = $regs[0];
$level = strlen($prefix);
$line = rtrim(substr($line, $level));
- $line = $prefix . rc_wordwrap($line, $length - $level - 2, " \r\n$prefix ");
+ $line = $prefix . self::wordwrap($line, $length - $level - 2, " \r\n$prefix ");
}
else if ($line) {
- $line = rc_wordwrap(rtrim($line), $length - 2, " \r\n");
+ $line = self::wordwrap(rtrim($line), $length - 2, " \r\n");
// space-stuffing
$line = preg_replace('/(^|\r\n)(From| |>)/', '\\1 \\2', $line);
}
diff --git a/program/include/rcube_output_html.php b/program/include/rcube_output_html.php
index 0a8f0e364..2743e7705 100644
--- a/program/include/rcube_output_html.php
+++ b/program/include/rcube_output_html.php
@@ -67,6 +67,11 @@ class rcube_output_html extends rcube_output
$this->set_env('task', $task);
$this->set_env('x_frame_options', $this->config->get('x_frame_options', 'sameorigin'));
+ // add cookie info
+ $this->set_env('cookie_domain', ini_get('session.cookie_domain'));
+ $this->set_env('cookie_path', ini_get('session.cookie_path'));
+ $this->set_env('cookie_secure', ini_get('session.cookie_secure'));
+
// load the correct skin (in case user-defined)
$skin = $this->config->get('skin');
$this->set_skin($skin);
@@ -395,7 +400,7 @@ class rcube_output_html extends rcube_output
'line' => __LINE__,
'file' => __FILE__,
'message' => 'Error loading template for '.$realname
- ), true, true);
+ ), true, $write);
return false;
}
@@ -693,6 +698,11 @@ class rcube_output_html extends rcube_output
}
break;
+ // frame
+ case 'frame':
+ return $this->frame($attrib);
+ break;
+
// show a label
case 'label':
if ($attrib['name'] || $attrib['command']) {
@@ -1270,6 +1280,30 @@ class rcube_output_html extends rcube_output
}
+ /**
+ * Returns iframe object, registers some related env variables
+ *
+ * @param array $attrib HTML attributes
+ *
+ * @return string IFRAME element
+ */
+ public function frame($attrib)
+ {
+ if (!$attrib['id']) {
+ $attrib['id'] = 'rcmframe';
+ }
+
+ if (!$attrib['name']) {
+ $attrib['name'] = $attrib['id'];
+ }
+
+ $this->set_env('contentframe', $attrib['id']);
+ $this->set_env('blankpage', $attrib['src'] ? $this->abs_url($attrib['src']) : 'program/resources/blank.gif');
+
+ return html::iframe($attrib);
+ }
+
+
/* ************* common functions delivering gui objects ************** */
diff --git a/program/include/rcube_plugin.php b/program/include/rcube_plugin.php
index b1ec32a8a..c1035733b 100644
--- a/program/include/rcube_plugin.php
+++ b/program/include/rcube_plugin.php
@@ -336,7 +336,7 @@ abstract class rcube_plugin
public function local_skin_path()
{
$rcmail = rcube::get_instance();
- foreach (array($rcmail->config->get('skin'),'default') as $skin) {
+ foreach (array($rcmail->config->get('skin'), 'larry') as $skin) {
$skin_path = 'skins/' . $skin;
if (is_dir(realpath(slashify($this->home) . $skin_path)))
break;
diff --git a/program/include/rcube_shared.inc b/program/include/rcube_shared.inc
index 85f278432..c15305c08 100644
--- a/program/include/rcube_shared.inc
+++ b/program/include/rcube_shared.inc
@@ -108,11 +108,11 @@ function slashify($str)
/**
- * Remove slash at the end of the string
+ * Remove slashes at the end of the string
*/
function unslashify($str)
{
- return preg_replace('/\/$/', '', $str);
+ return preg_replace('/\/+$/', '', $str);
}
@@ -231,7 +231,7 @@ function array_keys_recursive($array)
{
$keys = array();
- if (!empty($array)) {
+ if (!empty($array) && is_array($array)) {
foreach ($array as $key => $child) {
$keys[] = $key;
foreach (array_keys_recursive($child) as $val) {
@@ -255,7 +255,7 @@ function asciiwords($str, $css_id = false, $replace_with = '')
/**
- * Remove single and double quotes from given string
+ * Remove single and double quotes from a given string
*
* @param string Input value
*
@@ -306,6 +306,29 @@ function format_email_recipient($email, $name = '')
/**
+ * Format e-mail address
+ *
+ * @param string $email E-mail address
+ *
+ * @return string Formatted e-mail address
+ */
+function format_email($email)
+{
+ $email = trim($email);
+ $parts = explode('@', $email);
+ $count = count($parts);
+
+ if ($count > 1) {
+ $parts[$count-1] = mb_strtolower($parts[$count-1]);
+
+ $email = implode('@', $parts);
+ }
+
+ return $email;
+}
+
+
+/**
* mbstring replacement functions
*/
if (!extension_loaded('mbstring'))
@@ -399,7 +422,6 @@ function rcube_autoload($classname)
{
$filename = preg_replace(
array(
- '/MDB2_(.+)/',
'/Mail_(.+)/',
'/Net_(.+)/',
'/Auth_(.+)/',
@@ -408,7 +430,6 @@ function rcube_autoload($classname)
),
array(
'Mail/\\1',
- 'Mail/\\1',
'Net/\\1',
'Auth/\\1',
'html',
diff --git a/program/include/rcube_storage.php b/program/include/rcube_storage.php
index 768a26d73..f83e24041 100644
--- a/program/include/rcube_storage.php
+++ b/program/include/rcube_storage.php
@@ -195,7 +195,7 @@ abstract class rcube_storage
*/
public function set_folder($folder)
{
- if ($this->folder == $folder) {
+ if ($this->folder === $folder) {
return;
}
diff --git a/program/include/rcube_utils.php b/program/include/rcube_utils.php
index d1a8315ec..23bf556e4 100644
--- a/program/include/rcube_utils.php
+++ b/program/include/rcube_utils.php
@@ -92,9 +92,9 @@ class rcube_utils
return false;
}
- // Check domain part
- if (preg_match('/^\[*(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3}\]*$/', $domain_part)) {
- return true; // IP address
+ // Validate domain part
+ if (preg_match('/^\[((IPv6:[0-9a-f:.]+)|([0-9.]+))\]$/i', $domain_part, $matches)) {
+ return self::check_ip(preg_replace('/^IPv6:/i', '', $matches[1])); // valid IPv4 or IPv6 address
}
else {
// If not an IP address
@@ -110,6 +110,11 @@ class rcube_utils
}
}
+ // last domain part
+ if (preg_match('/[^a-zA-Z]/', array_pop($domain_array))) {
+ return false;
+ }
+
$rcube = rcube::get_instance();
if (!$dns_check || !$rcube->config->get('email_dns_check')) {
@@ -141,6 +146,52 @@ class rcube_utils
return false;
}
+
+ /**
+ * Validates IPv4 or IPv6 address
+ *
+ * @param string $ip IP address in v4 or v6 format
+ *
+ * @return bool True if the address is valid
+ */
+ public static function check_ip($ip)
+ {
+ // IPv6, but there's no build-in IPv6 support
+ if (strpos($ip, ':') !== false && !defined('AF_INET6')) {
+ $parts = explode(':', $domain_part);
+ $count = count($parts);
+
+ if ($count > 8 || $count < 2) {
+ return false;
+ }
+
+ foreach ($parts as $idx => $part) {
+ $length = strlen($part);
+ if (!$length) {
+ // there can be only one ::
+ if ($found_empty) {
+ return false;
+ }
+ $found_empty = true;
+ }
+ // last part can be an IPv4 address
+ else if ($idx == $count - 1) {
+ if (!preg_match('/^[0-9a-f]{1,4}$/i', $part)) {
+ return @inet_pton($part) !== false;
+ }
+ }
+ else if (!preg_match('/^[0-9a-f]{1,4}$/i', $part)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ return @inet_pton($ip) !== false;
+ }
+
+
/**
* Check whether the HTTP referer matches the current request
*
@@ -149,8 +200,8 @@ class rcube_utils
public static function check_referer()
{
$uri = parse_url($_SERVER['REQUEST_URI']);
- $referer = parse_url(rcube_request_header('Referer'));
- return $referer['host'] == rcube_request_header('Host') && $referer['path'] == $uri['path'];
+ $referer = parse_url(self::request_header('Referer'));
+ return $referer['host'] == self::request_header('Host') && $referer['path'] == $uri['path'];
}
diff --git a/program/include/rcube_vcard.php b/program/include/rcube_vcard.php
index 37cd3ab26..2bfd474c6 100644
--- a/program/include/rcube_vcard.php
+++ b/program/include/rcube_vcard.php
@@ -313,7 +313,7 @@ class rcube_vcard
case 'birthday':
case 'anniversary':
- if (($val = rcube_strtotime($value)) && ($fn = self::$fieldmap[$field]))
+ if (($val = rcube_utils::strtotime($value)) && ($fn = self::$fieldmap[$field]))
$this->raw[$fn][] = array(0 => date('Y-m-d', $val), 'value' => array('date'));
break;
diff --git a/program/js/app.js b/program/js/app.js
index e8bb6c1a7..48de21764 100644
--- a/program/js/app.js
+++ b/program/js/app.js
@@ -976,7 +976,7 @@ function rcube_webmail()
// do reply-list, when list is detected and popup menu wasn't used
url._all = (!props && this.commands['reply-list'] ? 'list' : 'all');
else if (command == 'reply-list')
- url._all = list;
+ url._all = 'list';
this.goto_url('compose', url, true);
}
@@ -1447,29 +1447,21 @@ function rcube_webmail()
this.doc_mouse_up = function(e)
{
- var model, list, li, id;
+ var model, list, id;
// ignore event if jquery UI dialog is open
if ($(rcube_event.get_target(e)).closest('.ui-dialog, .ui-widget-overlay').length)
return;
- if (list = this.message_list) {
- if (!rcube_mouse_is_over(e, list.list.parentNode))
- list.blur();
- else
- list.focus();
+ if (list = this.message_list)
model = this.env.mailboxes;
- }
- else if (list = this.contact_list) {
- if (!rcube_mouse_is_over(e, list.list.parentNode))
- list.blur();
- else
- list.focus();
+ else if (list = this.contact_list)
model = this.env.contactfolders;
- }
- else if (this.ksearch_value) {
+ else if (this.ksearch_value)
this.ksearch_blur();
- }
+
+ if (list && !rcube_mouse_is_over(e, list.list.parentNode))
+ list.blur();
// handle mouse release when dragging
if (this.drag_active && model && this.env.last_folder_target) {
@@ -1546,14 +1538,17 @@ function rcube_webmail()
if (list.multi_selecting || !this.env.contentframe)
return;
- if (list.get_single_selection() && window.frames && window.frames[this.env.contentframe]) {
- if (window.frames[this.env.contentframe].location.href.indexOf(this.env.blankpage)>=0) {
- if (this.preview_timer)
- clearTimeout(this.preview_timer);
- if (this.preview_read_timer)
- clearTimeout(this.preview_read_timer);
- this.preview_timer = setTimeout(function(){ ref.msglist_get_preview(); }, 200);
- }
+ if (list.get_single_selection())
+ return;
+
+ var win = this.get_frame_window(this.env.contentframe);
+
+ if (win && win.location.href.indexOf(this.env.blankpage)>=0) {
+ if (this.preview_timer)
+ clearTimeout(this.preview_timer);
+ if (this.preview_read_timer)
+ clearTimeout(this.preview_read_timer);
+ this.preview_timer = setTimeout(function(){ ref.msglist_get_preview(); }, 200);
}
};
@@ -1918,12 +1913,12 @@ function rcube_webmail()
if (!id)
return;
- var target = window,
+ var win, target = window,
action = preview ? 'preview': 'show',
url = '&_action='+action+'&_uid='+id+'&_mbox='+urlencode(this.env.mailbox);
- if (preview && this.env.contentframe && window.frames && window.frames[this.env.contentframe]) {
- target = window.frames[this.env.contentframe];
+ if (preview && (win = this.get_frame_window(this.env.contentframe))) {
+ target = win;
url += '&_framed=1';
}
@@ -1960,20 +1955,37 @@ function rcube_webmail()
this.show_contentframe = function(show)
{
- var frm, win;
- if (this.env.contentframe && (frm = $('#'+this.env.contentframe)) && frm.length) {
- if (!show && (win = window.frames[this.env.contentframe])) {
+ var frame, win, name = this.env.contentframe;
+
+ if (name && (frame = this.get_frame_element(name))) {
+ if (!show && (win = this.get_frame_window(name))) {
if (win.location && win.location.href.indexOf(this.env.blankpage)<0)
win.location.href = this.env.blankpage;
}
else if (!bw.safari && !bw.konq)
- frm[show ? 'show' : 'hide']();
- }
+ $(frame)[show ? 'show' : 'hide']();
+ }
if (!show && this.busy)
this.set_busy(false, null, this.env.frame_lock);
};
+ this.get_frame_element = function(id)
+ {
+ var frame;
+
+ if (id && (frame = document.getElementById(id)))
+ return frame;
+ };
+
+ this.get_frame_window = function(id)
+ {
+ var frame = this.get_frame_element(id);
+
+ if (frame && frame.name && window.frames)
+ return window.frames[frame.name];
+ };
+
this.lock_frame = function()
{
if (!this.env.frame_lock)
@@ -2017,7 +2029,7 @@ function rcube_webmail()
// list messages of a specific mailbox
this.list_mailbox = function(mbox, page, sort, url)
{
- var target = window;
+ var win, target = window;
if (typeof url != 'object')
url = {};
@@ -2056,8 +2068,8 @@ function rcube_webmail()
return;
}
- if (this.env.contentframe && window.frames && window.frames[this.env.contentframe]) {
- target = window.frames[this.env.contentframe];
+ if (win = this.get_frame_window(this.env.contentframe)) {
+ target = win;
url._framed = 1;
}
@@ -2652,34 +2664,37 @@ function rcube_webmail()
// set a specific flag to one or more messages
this.mark_message = function(flag, uid)
{
- var a_uids = [], r_uids = [], len, n, id,
- selection = this.message_list ? this.message_list.get_selection() : [];
+ var a_uids = [], r_uids = [], len, n, id, selection,
+ list = this.message_list;
if (uid)
a_uids[0] = uid;
else if (this.env.uid)
a_uids[0] = this.env.uid;
- else if (this.message_list) {
+ else if (list) {
+ selection = list.get_selection();
for (n=0, len=selection.length; n<len; n++) {
a_uids.push(selection[n]);
}
}
- if (!this.message_list)
+ if (!list)
r_uids = a_uids;
- else
+ else {
+ list.focus();
for (n=0, len=a_uids.length; n<len; n++) {
id = a_uids[n];
- if ((flag=='read' && this.message_list.rows[id].unread)
- || (flag=='unread' && !this.message_list.rows[id].unread)
- || (flag=='delete' && !this.message_list.rows[id].deleted)
- || (flag=='undelete' && this.message_list.rows[id].deleted)
- || (flag=='flagged' && !this.message_list.rows[id].flagged)
- || (flag=='unflagged' && this.message_list.rows[id].flagged))
+ if ((flag=='read' && list.rows[id].unread)
+ || (flag=='unread' && !list.rows[id].unread)
+ || (flag=='delete' && !list.rows[id].deleted)
+ || (flag=='undelete' && list.rows[id].deleted)
+ || (flag=='flagged' && !list.rows[id].flagged)
+ || (flag=='unflagged' && list.rows[id].flagged))
{
r_uids.push(id);
}
}
+ }
// nothing to do
if (!r_uids.length && !this.select_all_mode)
@@ -4020,7 +4035,7 @@ function rcube_webmail()
this.list_contacts = function(src, group, page)
{
- var folder, url = {},
+ var win, folder, url = {},
target = window;
if (!src)
@@ -4052,8 +4067,8 @@ function rcube_webmail()
return;
}
- if (this.env.contentframe && window.frames && window.frames[this.env.contentframe]) {
- target = window.frames[this.env.contentframe];
+ if (win = this.get_frame_window(this.env.contentframe)) {
+ target = win;
url._framed = 1;
}
@@ -4109,11 +4124,11 @@ function rcube_webmail()
// load contact record
this.load_contact = function(cid, action, framed)
{
- var url = {}, target = window;
+ var win, url = {}, target = window;
- if (this.env.contentframe && window.frames && window.frames[this.env.contentframe]) {
+ if (win = this.get_frame_window(this.env.contentframe)) {
url._framed = 1;
- target = window.frames[this.env.contentframe];
+ target = win;
this.show_contentframe(true);
// load dummy content
@@ -4731,11 +4746,11 @@ function rcube_webmail()
// load advanced search page
this.advanced_search = function()
{
- var url = {_form: 1, _action: 'search'}, target = window;
+ var win, url = {_form: 1, _action: 'search'}, target = window;
- if (this.env.contentframe && window.frames && window.frames[this.env.contentframe]) {
+ if (win = this.get_frame_window(this.env.contentframe)) {
url._framed = 1;
- target = window.frames[this.env.contentframe];
+ target = win;
this.contact_list.clear_selection();
}
@@ -4857,13 +4872,13 @@ function rcube_webmail()
// preferences section select and load options frame
this.section_select = function(list)
{
- var id = list.get_single_selection(), target = window,
+ var win, id = list.get_single_selection(), target = window,
url = {_action: 'edit-prefs', _section: id};
if (id) {
- if (this.env.contentframe && window.frames && window.frames[this.env.contentframe]) {
+ if (win = this.get_frame_window(this.env.contentframe)) {
url._framed = 1;
- target = window.frames[this.env.contentframe];
+ target = win;
}
this.location_href(url, target, true);
}
@@ -4886,13 +4901,12 @@ function rcube_webmail()
if (action == 'edit-identity' && (!id || id == this.env.iid))
return false;
- var target = window,
+ var win, target = window,
url = {_action: action, _iid: id};
- if (this.env.contentframe && window.frames && window.frames[this.env.contentframe]) {
+ if (win = this.get_frame_window(this.env.contentframe)) {
url._framed = 1;
- target = window.frames[this.env.contentframe];
- document.getElementById(this.env.contentframe).style.visibility = 'inherit';
+ target = win;
}
if (action && (id || action == 'add-identity')) {
@@ -5268,14 +5282,14 @@ function rcube_webmail()
// when user select a folder in manager
this.show_folder = function(folder, path, force)
{
- var target = window,
+ var win, target = window,
url = '&_action=edit-folder&_mbox='+urlencode(folder);
if (path)
url += '&_path='+urlencode(path);
- if (this.env.contentframe && window.frames && window.frames[this.env.contentframe]) {
- target = window.frames[this.env.contentframe];
+ if (win = this.get_frame_window(this.env.contentframe)) {
+ target = win;
url += '&_framed=1';
}
@@ -6585,6 +6599,12 @@ function rcube_webmail()
return 0;
};
+ // Cookie setter
+ this.set_cookie = function(name, value, expires)
+ {
+ setCookie(name, value, expires, this.env.cookie_path, this.env.cookie_domain, this.env.cookie_secure);
+ }
+
} // end object rcube_webmail
@@ -6615,6 +6635,8 @@ rcube_webmail.long_subject_title_ie = function(elem, indent)
}
};
+rcube_webmail.prototype.get_cookie = getCookie;
+
// copy event engine prototype
rcube_webmail.prototype.addEventListener = rcube_event_engine.prototype.addEventListener;
rcube_webmail.prototype.removeEventListener = rcube_event_engine.prototype.removeEventListener;
diff --git a/program/js/common.js b/program/js/common.js
index fdef3453e..f9e945c05 100644
--- a/program/js/common.js
+++ b/program/js/common.js
@@ -494,12 +494,15 @@ function rcube_check_email(input, inline)
atom = '[^\\x00-\\x20\\x22\\x28\\x29\\x2c\\x2e\\x3a-\\x3c\\x3e\\x40\\x5b-\\x5d\\x7f-\\xff]+',
quoted_pair = '\\x5c[\\x00-\\x7f]',
quoted_string = '\\x22('+qtext+'|'+quoted_pair+')*\\x22',
+ ipv4 = '\\[(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3}\\]',
+ ipv6 = '\\[IPv6:[0-9a-f:.]+\\]',
+ ip_addr = '(' + ipv4 + ')|(' + ipv6 + ')',
// Use simplified domain matching, because we need to allow Unicode characters here
// So, e-mail address should be validated also on server side after idn_to_ascii() use
//domain_literal = '\\x5b('+dtext+'|'+quoted_pair+')*\\x5d',
//sub_domain = '('+atom+'|'+domain_literal+')',
// allow punycode/unicode top-level domain
- domain = '([^@\\x2e]+\\x2e)+([^\\x00-\\x40\\x5b-\\x60\\x7b-\\x7f]{2,}|xn--[a-z0-9]{2,})',
+ domain = '(('+ip_addr+')|(([^@\\x2e]+\\x2e)+([^\\x00-\\x40\\x5b-\\x60\\x7b-\\x7f]{2,}|xn--[a-z0-9]{2,})))',
// ICANN e-mail test (http://idn.icann.org/E-mail_test)
icann_domains = [
'\\u0645\\u062b\\u0627\\u0644\\x2e\\u0625\\u062e\\u062a\\u0628\\u0627\\u0631',
@@ -527,7 +530,6 @@ function rcube_check_email(input, inline)
return false;
};
-
// recursively copy an object
function rcube_clone_object(obj)
{
@@ -635,6 +637,7 @@ function getCookie(name)
return unescape(dc.substring(begin + prefix.length, end));
};
+// deprecated aliases, to be removed, use rcmail.set_cookie/rcmail.get_cookie
roundcube_browser.prototype.set_cookie = setCookie;
roundcube_browser.prototype.get_cookie = getCookie;
diff --git a/program/js/googiespell.js b/program/js/googiespell.js
index 9f1b41bb2..478858bac 100644
--- a/program/js/googiespell.js
+++ b/program/js/googiespell.js
@@ -25,7 +25,7 @@ var GOOGIE_CUR_LANG,
function GoogieSpell(img_dir, server_url, has_dict)
{
var ref = this,
- cookie_value = getCookie('language');
+ cookie_value = rcmail.get_cookie('language');
GOOGIE_CUR_LANG = cookie_value != null ? cookie_value : GOOGIE_DEFAULT_LANG;
@@ -150,7 +150,7 @@ this.setCurrentLanguage = function(lan_code)
//Set cookie
var now = new Date();
now.setTime(now.getTime() + 365 * 24 * 60 * 60 * 1000);
- setCookie('language', lan_code, now);
+ rcmail.set_cookie('language', lan_code, now);
};
this.setForceWidthHeight = function(width, height)
diff --git a/program/js/list.js b/program/js/list.js
index e84124b7c..1457382a4 100644
--- a/program/js/list.js
+++ b/program/js/list.js
@@ -231,8 +231,8 @@ focus: function(e)
}
}
- // Un-focus already focused elements
- $(document.activeElement).blur();
+ // Un-focus already focused elements (#1487123, #1487316, #1488600, #1488620)
+ $(':focus:not(body)').blur();
$('iframe').each(function() { this.blur(); });
if (e || (e = window.event))
diff --git a/program/localization/en_US/labels.inc b/program/localization/en_US/labels.inc
index 94bae1974..9882c19b5 100644
--- a/program/localization/en_US/labels.inc
+++ b/program/localization/en_US/labels.inc
@@ -1,7 +1,6 @@
<?php
/*
-
+-----------------------------------------------------------------------+
| language/en_US/labels.inc |
| |
@@ -15,9 +14,6 @@
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
-
- @version $Id$
-
*/
$labels = array();
@@ -163,6 +159,7 @@ $labels['unread'] = 'Unread';
$labels['flagged'] = 'Flagged';
$labels['unanswered'] = 'Unanswered';
$labels['deleted'] = 'Deleted';
+$labels['undeleted'] = 'Not deleted';
$labels['invert'] = 'Invert';
$labels['filter'] = 'Filter';
$labels['list'] = 'List';
@@ -384,7 +381,8 @@ $labels['pagesize'] = 'Rows per page';
$labels['signature'] = 'Signature';
$labels['dstactive'] = 'Daylight saving time';
$labels['htmleditor'] = 'Compose HTML messages';
-$labels['htmlonreply'] = 'on reply to HTML message only';
+$labels['htmlonreply'] = 'on reply to HTML message';
+$labels['htmlonreplyandforward'] = 'on forward or reply to HTML message';
$labels['htmlsignature'] = 'HTML signature';
$labels['previewpane'] = 'Show preview pane';
$labels['skin'] = 'Interface skin';
@@ -432,8 +430,9 @@ $labels['maintenance'] = 'Maintenance';
$labels['newmessage'] = 'New Message';
$labels['signatureoptions'] = 'Signature Options';
$labels['whenreplying'] = 'When replying';
-$labels['replytopposting'] = 'start new message above original';
-$labels['replybottomposting'] = 'start new message below original';
+$labels['replyempty'] = 'do not quote the original message';
+$labels['replytopposting'] = 'start new message above the quote';
+$labels['replybottomposting'] = 'start new message below the quote';
$labels['replyremovesignature'] = 'When replying remove original signature from message';
$labels['autoaddsignature'] = 'Automatically add signature';
$labels['newmessageonly'] = 'new message only';
diff --git a/program/localization/pl_PL/labels.inc b/program/localization/pl_PL/labels.inc
index 92da1f6ef..d5ffcaa61 100644
--- a/program/localization/pl_PL/labels.inc
+++ b/program/localization/pl_PL/labels.inc
@@ -134,6 +134,7 @@ $labels['unread'] = 'Nieprzeczytane';
$labels['flagged'] = 'Oznaczone';
$labels['unanswered'] = 'Bez odpowiedzi';
$labels['deleted'] = 'Usunięte';
+$labels['undeleted'] = 'Nieusunięte';
$labels['invert'] = 'Odwróć';
$labels['filter'] = 'Filtr';
$labels['list'] = 'Lista';
diff --git a/program/steps/addressbook/export.inc b/program/steps/addressbook/export.inc
index 84a63aebc..850795c85 100644
--- a/program/steps/addressbook/export.inc
+++ b/program/steps/addressbook/export.inc
@@ -86,7 +86,7 @@ while ($result && ($row = $result->next())) {
foreach ($row as $key => $values) {
list($field, $section) = explode(':', $key);
foreach ((array)$values as $value) {
- if (is_array($value) || strlen($value))
+ if (is_array($value) || @strlen($value))
$vcard->set($field, $value, strtoupper($section));
}
}
diff --git a/program/steps/mail/compose.inc b/program/steps/mail/compose.inc
index 1a1d244e1..29e12675e 100644
--- a/program/steps/mail/compose.inc
+++ b/program/steps/mail/compose.inc
@@ -139,7 +139,7 @@ if (!empty($CONFIG['drafts_mbox'])) {
// set current mailbox in client environment
$OUTPUT->set_env('mailbox', $RCMAIL->storage->get_folder());
$OUTPUT->set_env('sig_above', $RCMAIL->config->get('sig_above', false));
-$OUTPUT->set_env('top_posting', $RCMAIL->config->get('top_posting', false));
+$OUTPUT->set_env('top_posting', intval($RCMAIL->config->get('reply_mode')) > 0);
$OUTPUT->set_env('recipients_separator', trim($RCMAIL->config->get('recipients_separator', ',')));
// default font for HTML editor
@@ -252,7 +252,8 @@ $MESSAGE->identities = $RCMAIL->user->list_identities();
if (count($MESSAGE->identities))
{
foreach ($MESSAGE->identities as $idx => $ident) {
- $email = mb_strtolower(rcube_idn_to_utf8($ident['email']));
+ $ident['email'] = format_email($ident['email']);
+ $email = format_email(rcube_idn_to_utf8($ident['email']));
$MESSAGE->identities[$idx]['email_ascii'] = $ident['email'];
$MESSAGE->identities[$idx]['ident'] = format_email_recipient($ident['email'], $ident['name']);
@@ -277,7 +278,7 @@ else if (count($MESSAGE->identities)) {
$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[] = strtolower($addr['mailto']);
+ $a_recipients[] = format_email($addr['mailto']);
$a_names[] = $addr['name'];
}
}
@@ -286,7 +287,7 @@ else if (count($MESSAGE->identities)) {
$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[] = strtolower($addr['mailto']);
+ $a_recipients[] = format_email($addr['mailto']);
$a_names[] = $addr['name'];
}
}
@@ -294,16 +295,12 @@ else if (count($MESSAGE->identities)) {
}
$from_idx = null;
- $default_identity = null;
+ $found_idx = null;
+ $default_identity = 0; // default identity is always first on the list
$return_path = $MESSAGE->headers->others['return-path'];
// Select identity
foreach ($MESSAGE->identities as $idx => $ident) {
- // save default identity ID
- if ($ident['standard']) {
- $default_identity = $idx;
- }
-
// use From header
if (in_array($compose_mode, array(RCUBE_COMPOSE_DRAFT, RCUBE_COMPOSE_EDIT))) {
if ($MESSAGE->headers->from == $ident['ident']) {
@@ -318,13 +315,22 @@ else if (count($MESSAGE->identities)) {
}
// use replied message recipients
else if (($found = array_search($ident['email_ascii'], $a_recipients)) !== false) {
- // match identity name, prefer default identity
- if ($from_idx === null || ($a_names[$found] && $ident['name'] && $a_names[$found] == $ident['name'])) {
+ if ($found_idx === null) {
+ $found_idx = $idx;
+ }
+ // match identity name
+ if ($a_names[$found] && $ident['name'] && $a_names[$found] == $ident['name']) {
$from_idx = $idx;
+ break;
}
}
}
+ // If matching by name+address doesn't found any amtches, get first found address (identity)
+ if ($from_idx === null) {
+ $from_idx = $found_idx;
+ }
+
// Fallback using Return-Path
if ($from_idx === null && $return_path) {
foreach ($MESSAGE->identities as $idx => $ident) {
@@ -335,12 +341,7 @@ else if (count($MESSAGE->identities)) {
}
}
- // Still no ID, use default/first identity
- if ($from_idx === null) {
- $from_idx = $default_identity !== null ? $default_identity : key(reset($MESSAGE->identities));
- }
-
- $ident = $MESSAGE->identities[$from_idx];
+ $ident = $MESSAGE->identities[$from_idx !== null ? $from_idx : $default_identity];
$from_id = $ident['identity_id'];
$MESSAGE->compose['from_email'] = $ident['email'];
@@ -433,7 +434,7 @@ foreach ($parts as $header) {
if (empty($addr_part['mailto']))
continue;
- $mailto = mb_strtolower(rcube_idn_to_utf8($addr_part['mailto']));
+ $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'])
@@ -609,9 +610,12 @@ function rcmail_compose_editor_mode()
$useHtml = $MESSAGE->has_html_part(false);
}
else if ($compose_mode == RCUBE_COMPOSE_REPLY) {
- $useHtml = ($html_editor == 1 || ($html_editor == 2 && $MESSAGE->has_html_part(false)));
+ $useHtml = ($html_editor == 1 || ($html_editor >= 2 && $MESSAGE->has_html_part(false)));
}
- else { // RCUBE_COMPOSE_FORWARD or NEW
+ else if ($compose_mode == RCUBE_COMPOSE_FORWARD) {
+ $useHtml = ($html_editor == 1 || ($html_editor == 3 && $MESSAGE->has_html_part(false)));
+ }
+ else {
$useHtml = ($html_editor == 1);
}
@@ -640,7 +644,7 @@ function rcmail_prepare_message_body()
rcmail_write_forward_attachment($MESSAGE);
}
// reply/edit/draft/forward
- else if ($compose_mode) {
+ else if ($compose_mode && ($compose_mode != RCUBE_COMPOSE_REPLY || $RCMAIL->config->get('reply_mode') != -1)) {
$isHtml = rcmail_compose_editor_mode();
if (!empty($MESSAGE->parts)) {
@@ -905,8 +909,9 @@ function rcmail_create_reply_body($body, $bodyIsHtml)
$prefix .= "\n";
$suffix = '';
- if ($RCMAIL->config->get('top_posting'))
+ if (intval($RCMAIL->config->get('reply_mode')) > 0) { // top-posting
$prefix = "\n\n\n" . $prefix;
+ }
}
else {
// save inline images to files
@@ -920,7 +925,7 @@ function rcmail_create_reply_body($body, $bodyIsHtml)
$prefix = '<p>' . Q($prefix) . "</p>\n";
$prefix .= '<blockquote>';
- if ($RCMAIL->config->get('top_posting')) {
+ if (intval($RCMAIL->config->get('reply_mode')) > 0) { // top-posting
$prefix = '<br>' . $prefix;
$suffix = '</blockquote>';
}
diff --git a/program/steps/mail/func.inc b/program/steps/mail/func.inc
index 3d65eacb1..8bf80a6ee 100644
--- a/program/steps/mail/func.inc
+++ b/program/steps/mail/func.inc
@@ -90,11 +90,13 @@ if (empty($RCMAIL->action) || $RCMAIL->action == 'list') {
// set current mailbox and some other vars in client environment
$OUTPUT->set_env('mailbox', $mbox_name);
$OUTPUT->set_env('pagesize', $RCMAIL->storage->get_pagesize());
- $OUTPUT->set_env('quota', $RCMAIL->storage->get_capability('QUOTA'));
$OUTPUT->set_env('delimiter', $RCMAIL->storage->get_hierarchy_delimiter());
$OUTPUT->set_env('threading', $threading);
$OUTPUT->set_env('threads', $threading || $RCMAIL->storage->get_capability('THREAD'));
$OUTPUT->set_env('preview_pane_mark_read', $RCMAIL->config->get('preview_pane_mark_read', 0));
+ if ($RCMAIL->storage->get_capability('QUOTA')) {
+ $OUTPUT->set_env('quota', true);
+ }
if ($CONFIG['delete_junk'])
$OUTPUT->set_env('delete_junk', true);
@@ -1053,12 +1055,17 @@ function rcmail_message_full_headers($attrib, $headers=NULL)
global $OUTPUT;
$html = html::div(array('id' => "all-headers", 'class' => "all", 'style' => 'display:none'), html::div(array('id' => 'headers-source'), ''));
- $html .= html::div(array('class' => "more-headers show-headers", 'onclick' => "return ".JS_OBJECT_NAME.".command('show-headers','',this)"), '');
+
+ if (!get_boolean($attrib['no-switch'])) {
+ $html .= html::div(array('class' => "more-headers show-headers", 'onclick' => "return ".JS_OBJECT_NAME.".command('show-headers','',this)"), '');
+ }
+
+ unset($attrib['no-switch']);
$OUTPUT->add_gui_object('all_headers_row', 'all-headers');
$OUTPUT->add_gui_object('all_headers_box', 'headers-source');
- return html::div($attrib, $html);
+ return count($attrib) > 1 ? html::div($attrib, $html) : $html;
}
@@ -1712,8 +1719,10 @@ function rcmail_search_filter($attrib)
$select_filter->add(rcube_label('unread'), 'UNSEEN');
$select_filter->add(rcube_label('flagged'), 'FLAGGED');
$select_filter->add(rcube_label('unanswered'), 'UNANSWERED');
- if (!$CONFIG['skip_deleted'])
+ if (!$CONFIG['skip_deleted']) {
$select_filter->add(rcube_label('deleted'), 'DELETED');
+ $select_filter->add(rcube_label('undeleted'), 'UNDELETED');
+ }
$select_filter->add(rcube_label('priority').': '.rcube_label('highest'), 'HEADER X-PRIORITY 1');
$select_filter->add(rcube_label('priority').': '.rcube_label('high'), 'HEADER X-PRIORITY 2');
$select_filter->add(rcube_label('priority').': '.rcube_label('normal'), 'NOT HEADER X-PRIORITY 1 NOT HEADER X-PRIORITY 2 NOT HEADER X-PRIORITY 4 NOT HEADER X-PRIORITY 5');
diff --git a/program/steps/mail/headers.inc b/program/steps/mail/headers.inc
index 4d6627393..cad113f68 100644
--- a/program/steps/mail/headers.inc
+++ b/program/steps/mail/headers.inc
@@ -24,7 +24,8 @@ if ($uid = get_input_value('_uid', RCUBE_INPUT_POST))
$source = $RCMAIL->storage->get_raw_headers($uid);
if ($source !== false) {
- $source = htmlspecialchars(trim($source));
+ $source = trim(rcube_charset::clean($source));
+ $source = htmlspecialchars($source);
$source = preg_replace(
array(
'/\n[\t\s]+/',
diff --git a/program/steps/settings/folders.inc b/program/steps/settings/folders.inc
index 2691a6e26..6ca704998 100644
--- a/program/steps/settings/folders.inc
+++ b/program/steps/settings/folders.inc
@@ -44,8 +44,8 @@ if ($RCMAIL->action == 'subscribe')
if ($result) {
// Handle subscription of protected folder (#1487656)
- if ($CONFIG['protect_default_folders'] == true
- && in_array($mbox, $CONFIG['default_folders'])
+ if ($RCMAIL->config->get('protect_default_folders')
+ && in_array($mbox, (array)$RCMAIL->config->get('default_folders'))
) {
$OUTPUT->command('disable_subscription', $mbox);
}
@@ -321,8 +321,8 @@ function rcube_subscription_form($attrib)
}
}
}
- // check if the folder is shared, then disable subscription option on it
- if (!$disabled && $folder['virtual'] && !empty($namespace)) {
+ // check if the folder is shared, then disable subscription option on it (if not subscribed already)
+ if (!$disabled && !$subscribed && $folder['virtual'] && !empty($namespace)) {
$tmp_ns = array_merge((array)$namespace['other'], (array)$namespace['shared']);
foreach ($tmp_ns as $item) {
if (strpos($folder['id'], $item[0]) === 0) {
@@ -411,8 +411,10 @@ function rcmail_rename_folder($oldname, $newname)
$OUTPUT->set_pagetitle(rcube_label('folders'));
$OUTPUT->include_script('list.js');
-$OUTPUT->set_env('quota', $STORAGE->get_capability('QUOTA'));
$OUTPUT->set_env('prefix_ns', $STORAGE->get_namespace('prefix'));
+if ($STORAGE->get_capability('QUOTA')) {
+ $OUTPUT->set_env('quota', true);
+}
// add some labels to client
$OUTPUT->add_label('deletefolderconfirm', 'purgefolderconfirm', 'folderdeleting',
diff --git a/program/steps/settings/func.inc b/program/steps/settings/func.inc
index 3f5ef5390..59b4e3735 100644
--- a/program/steps/settings/func.inc
+++ b/program/steps/settings/func.inc
@@ -470,6 +470,7 @@ function rcmail_user_prefs($current=null)
$select_htmleditor->add(rcube_label('never'), 0);
$select_htmleditor->add(rcube_label('always'), 1);
$select_htmleditor->add(rcube_label('htmlonreply'), 2);
+ $select_htmleditor->add(rcube_label('htmlonreplyandforward'), 3);
$blocks['main']['options']['htmleditor'] = array(
'title' => html::label($field_id, Q(rcube_label('htmleditor'))),
@@ -544,16 +545,17 @@ function rcmail_user_prefs($current=null)
);
}
- if (!isset($no_override['top_posting'])) {
- $field_id = 'rcmfd_top_posting';
- $select_replymode = new html_select(array('name' => '_top_posting', 'id' => $field_id,
- 'onchange' => "\$('#rcmfd_sig_above').attr('disabled',this.selectedIndex==0)"));
+ if (!isset($no_override['reply_mode'])) {
+ $field_id = 'rcmfd_reply_mode';
+ $select_replymode = new html_select(array('name' => '_reply_mode', 'id' => $field_id,
+ 'onchange' => "\$('#rcmfd_sig_above').attr('disabled',this.selectedIndex<2)"));
+ $select_replymode->add(rcube_label('replyempty'), -1);
$select_replymode->add(rcube_label('replybottomposting'), 0);
$select_replymode->add(rcube_label('replytopposting'), 1);
- $blocks['main']['options']['top_posting'] = array(
+ $blocks['main']['options']['reply_mode'] = array(
'title' => html::label($field_id, Q(rcube_label('whenreplying'))),
- 'content' => $select_replymode->show($config['top_posting']?1:0),
+ 'content' => $select_replymode->show(intval($config['reply_mode'])),
);
}
@@ -597,7 +599,7 @@ function rcmail_user_prefs($current=null)
if (!isset($no_override['sig_above'])) {
$field_id = 'rcmfd_sig_above';
- $select_sigabove = new html_select(array('name' => '_sig_above', 'id' => $field_id, 'disabled' => !$config['top_posting']));
+ $select_sigabove = new html_select(array('name' => '_sig_above', 'id' => $field_id, 'disabled' => $config['reply_mode'] < 1));
$select_sigabove->add(rcube_label('belowquote'), 0);
$select_sigabove->add(rcube_label('abovequote'), 1);
diff --git a/program/steps/settings/save_folder.inc b/program/steps/settings/save_folder.inc
index 09f76ac27..73cc5e4bf 100644
--- a/program/steps/settings/save_folder.inc
+++ b/program/steps/settings/save_folder.inc
@@ -80,7 +80,10 @@ if (!$error && strlen($path) && (!strlen($old_imap) || $old_imap != $name_imap))
}
}
-if (!$error) {
+if ($error) {
+ $OUTPUT->command('display_message', $error, 'error');
+}
+else {
$folder['name'] = $name_imap;
$folder['oldname'] = $old_imap;
$folder['class'] = '';
diff --git a/program/steps/settings/save_prefs.inc b/program/steps/settings/save_prefs.inc
index 88fa5298a..dc149929e 100644
--- a/program/steps/settings/save_prefs.inc
+++ b/program/steps/settings/save_prefs.inc
@@ -82,9 +82,9 @@ switch ($CURR_SECTION)
'spellcheck_ignore_nums' => isset($_POST['_spellcheck_ignore_nums']) ? TRUE : FALSE,
'spellcheck_ignore_caps' => isset($_POST['_spellcheck_ignore_caps']) ? TRUE : FALSE,
'show_sig' => isset($_POST['_show_sig']) ? intval($_POST['_show_sig']) : 1,
- 'top_posting' => !empty($_POST['_top_posting']),
+ 'reply_mode' => isset($_POST['_reply_mode']) ? intval($_POST['_reply_mode']) : 0,
'strip_existing_sig' => isset($_POST['_strip_existing_sig']),
- 'sig_above' => !empty($_POST['_sig_above']) && !empty($_POST['_top_posting']),
+ 'sig_above' => !empty($_POST['_sig_above']) && $_POST['_reply_mode'] < 1,
'default_font' => get_input_value('_default_font', RCUBE_INPUT_POST),
'forward_attachment' => !empty($_POST['_forward_attachment']),
);
diff --git a/skins/classic/functions.js b/skins/classic/functions.js
index 9b83b9044..73c43f60f 100644
--- a/skins/classic/functions.js
+++ b/skins/classic/functions.js
@@ -28,11 +28,6 @@ function rcube_init_settings_tabs()
$('a', tab).removeAttr('onclick').click(function() { return false; });
}
-function rcube_show_advanced(visible)
-{
- $('tr.advanced').css('display', (visible ? (bw.ie ? 'block' : 'table-row') : 'none'));
-}
-
// Fieldsets-to-tabs converter
// Warning: don't place "caller" <script> inside page element (id)
function rcube_init_tabs(id, current)
@@ -530,7 +525,7 @@ show_header_form: function(id)
if ((row = document.getElementById('compose-' + id))) {
var div = document.getElementById('compose-div'),
headers_div = document.getElementById('compose-headers-div');
- row.style.display = (document.all && !window.opera) ? 'block' : 'table-row';
+ $(row).show();
div.style.top = (parseInt(headers_div.offsetHeight, 10) + 3) + 'px';
this.resize_compose_body();
}
@@ -550,11 +545,11 @@ hide_header_form: function(id)
for (var i=0; i<links.length; i++)
if (links[i].style.display != 'none')
for (var j=i+1; j<links.length; j++)
- if (links[j].style.display != 'none')
+ if (links[j].style.display != 'none')
if ((ns = this.next_sibling(links[i]))) {
- ns.style.display = '';
- break;
- }
+ ns.style.display = '';
+ break;
+ }
document.getElementById('_' + id).value = '';
diff --git a/skins/classic/includes/links.html b/skins/classic/includes/links.html
index 82673220a..6d8d03c52 100644
--- a/skins/classic/includes/links.html
+++ b/skins/classic/includes/links.html
@@ -1,3 +1,4 @@
+<meta http-equiv="X-UA-Compatible" content="IE=EDGE" />
<link rel="index" href="$__comm_path" />
<link rel="shortcut icon" href="/images/favicon.ico"/>
<link rel="stylesheet" type="text/css" href="/common.css" />
diff --git a/skins/classic/splitter.js b/skins/classic/splitter.js
index 59ebb5151..3f1c97302 100644
--- a/skins/classic/splitter.js
+++ b/skins/classic/splitter.js
@@ -47,7 +47,7 @@ function rcube_splitter(attrib)
rcube_event.add_listener({element: window, event:'resize', object:this, method:'onResize'});
// read saved position from cookie
- var cookie = bw.get_cookie(this.id);
+ var cookie = rcmail.get_cookie(this.id);
if (cookie && !isNaN(cookie)) {
this.pos = parseFloat(cookie);
this.resize();
@@ -197,7 +197,7 @@ function rcube_splitter(attrib)
{
var exp = new Date();
exp.setYear(exp.getFullYear() + 1);
- bw.set_cookie(this.id, this.pos, exp);
+ rcmail.set_cookie(this.id, this.pos, exp);
};
} // end class rcube_splitter
diff --git a/skins/classic/templates/compose.html b/skins/classic/templates/compose.html
index caebf31a0..1e1403e18 100644
--- a/skins/classic/templates/compose.html
+++ b/skins/classic/templates/compose.html
@@ -79,7 +79,7 @@
<a href="#bcc" onclick="return rcmail_ui.hide_header_form('bcc');"><img src="/images/icons/minus.gif" alt="" width="13" height="11" title="<roundcube:label name='delete' />" /></a>
<label for="_bcc"><roundcube:label name="bcc" /></label>
</td>
- <td colspan="2" class="editfield"><roundcube:object name="composeHeaders" part="bcc" form="form" id="_bcc" cols="70" rows="2" tabindex="4" /></td>
+ <td class="editfield"><roundcube:object name="composeHeaders" part="bcc" form="form" id="_bcc" cols="70" rows="2" tabindex="4" /></td>
</tr><tr id="compose-replyto">
<td class="title top">
<a href="#replyto" onclick="return rcmail_ui.hide_header_form('replyto');"><img src="/images/icons/minus.gif" alt="" width="13" height="11" title="<roundcube:label name='delete' />" /></a>
diff --git a/skins/classic/templates/message.html b/skins/classic/templates/message.html
index 714540b78..c03376e4a 100644
--- a/skins/classic/templates/message.html
+++ b/skins/classic/templates/message.html
@@ -23,11 +23,9 @@
<div id="mailboxlist-container">
<div id="mailboxlist-title" class="boxtitle"><roundcube:label name="mailboxlist" /></div>
<div class="boxlistcontent">
-<roundcube:object name="mailboxlist" id="mailboxlist" maxlength="25" />
-</div>
-<div class="boxfooter">
- <roundcube:button name="mailboxmenulink" id="mailboxmenulink" type="link" title="folderactions" class="button groupactions" onclick="rcmail_ui.show_popup('mailboxmenu');return false" content=" " />
+ <roundcube:object name="mailboxlist" id="mailboxlist" maxlength="25" />
</div>
+<div class="boxfooter"></div>
</div>
</div>
@@ -57,14 +55,5 @@
rcmail.add_onload('mailviewsplitv.init()');
</script>
-<div id="mailboxoptionsmenu" class="popupmenu">
- <ul>
- <li><roundcube:button command="expunge" type="link" label="compact" classAct="active" /></li>
- <li class="separator_below"><roundcube:button command="purge" type="link" label="empty" classAct="active" /></li>
- <li><roundcube:button command="folders" task="settings" type="link" label="managefolders" classAct="active" /></li>
- <roundcube:container name="mailboxoptions" id="mailboxoptionsmenu" />
- </ul>
-</div>
-
</body>
</html>
diff --git a/skins/classic/templates/messageerror.html b/skins/classic/templates/messageerror.html
index 9af45f432..918e3092a 100644
--- a/skins/classic/templates/messageerror.html
+++ b/skins/classic/templates/messageerror.html
@@ -42,11 +42,9 @@
<div id="mailboxlist-container">
<div class="boxtitle"><roundcube:label name="mailboxlist" /></div>
<div class="boxlistcontent">
-<roundcube:object name="mailboxlist" id="mailboxlist" folder_filter="mail" />
-</div>
-<div class="boxfooter">
- <roundcube:button name="mailboxmenulink" id="mailboxmenulink" type="link" title="folderactions" class="button groupactions" onclick="rcmail_ui.show_popup('mailboxmenu');return false" content=" " />
+ <roundcube:object name="mailboxlist" id="mailboxlist" folder_filter="mail" />
</div>
+<div class="boxfooter"></div>
</div>
</div>
@@ -63,15 +61,6 @@
rcmail.add_onload('mailviewsplitv.init()');
</script>
-<div id="mailboxoptionsmenu" class="popupmenu">
- <ul>
- <li><roundcube:button command="expunge" type="link" label="compact" classAct="active" /></li>
- <li class="separator_below"><roundcube:button command="purge" type="link" label="empty" classAct="active" /></li>
- <li><roundcube:button command="folders" task="settings" type="link" label="managefolders" classAct="active" /></li>
- <roundcube:container name="mailboxoptions" id="mailboxoptionsmenu" />
- </ul>
-</div>
-
</body>
<roundcube:endif />
diff --git a/skins/larry/addressbook.css b/skins/larry/addressbook.css
index fe087aece..74bc0d7d8 100644
--- a/skins/larry/addressbook.css
+++ b/skins/larry/addressbook.css
@@ -34,7 +34,6 @@
position: absolute;
top: -6px;
left: 0;
- right: 260px;
height: 40px;
white-space: nowrap;
z-index: 10;
diff --git a/skins/larry/ie7hacks.css b/skins/larry/ie7hacks.css
index 6161d03dd..935a504fe 100644
--- a/skins/larry/ie7hacks.css
+++ b/skins/larry/ie7hacks.css
@@ -7,10 +7,16 @@
* License. It is allowed to copy, distribute, transmit and to adapt the work
* by keeping credits to the original autors in the README file.
* See http://creativecommons.org/licenses/by-sa/3.0/ for details.
- *
- * $Id$
*/
+/* #1488618 */
+#mainscreen {
+ height: expression((parseInt(document.documentElement.clientHeight)-108)+'px');
+}
+#mainscreen.offset {
+ height: expression((parseInt(document.documentElement.clientHeight)-150)+'px');
+}
+
input.button {
display: inline;
font-size: 90%;
@@ -23,7 +29,7 @@ a.deletebutton,
.boxfooter .listbutton .inner,
.attachmentslist li a.delete,
.attachmentslist li a.cancelupload,
-#messagepreviewheader .iconlink {
+#messageheader .iconlink {
/* workaround for text-indent which also offsets the background image */
text-indent: 0;
font-size: 0;
@@ -39,7 +45,7 @@ a.deletebutton,
.pagenav a.button,
.pagenav a.button span.inner,
-#messagepreviewheader .iconlink,
+#messageheader .iconlink,
#uploadform a.iconlink {
display: inline;
}
@@ -61,7 +67,7 @@ a.deletebutton,
text-align: left;
}
-#messagepreviewheader .iconlink {
+#messageheader .iconlink {
color: #fff;
height: 14px;
}
diff --git a/skins/larry/iehacks.css b/skins/larry/iehacks.css
index 288202111..bba93dc33 100644
--- a/skins/larry/iehacks.css
+++ b/skins/larry/iehacks.css
@@ -143,7 +143,7 @@ ul.toolbarmenu li a.active:hover,
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#005d76', endColorstr='#004558', GradientType=0);
}
-#messageheader, #partheader, #composeheaders {
+#partheader, #composeheaders {
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#e9e9e9', GradientType=0);
}
diff --git a/skins/larry/images/contactpic_32px.png b/skins/larry/images/contactpic_32px.png
index 276f1974f..25a81418d 100644
--- a/skins/larry/images/contactpic_32px.png
+++ b/skins/larry/images/contactpic_32px.png
Binary files differ
diff --git a/skins/larry/images/contactpic_48px.png b/skins/larry/images/contactpic_48px.png
new file mode 100644
index 000000000..9cd3bceaf
--- /dev/null
+++ b/skins/larry/images/contactpic_48px.png
Binary files differ
diff --git a/skins/larry/includes/links.html b/skins/larry/includes/links.html
index 0ddc2e160..8bd8012e1 100644
--- a/skins/larry/includes/links.html
+++ b/skins/larry/includes/links.html
@@ -1,3 +1,4 @@
+<meta http-equiv="X-UA-Compatible" content="IE=EDGE" />
<link rel="shortcut icon" href="/images/favicon.ico"/>
<link rel="stylesheet" type="text/css" href="/styles.css" />
<roundcube:if condition="in_array(env:task, array('mail','addressbook','settings'))" />
diff --git a/skins/larry/mail.css b/skins/larry/mail.css
index 4fff24307..496cbbd15 100644
--- a/skins/larry/mail.css
+++ b/skins/larry/mail.css
@@ -38,10 +38,6 @@
bottom: 28px;
}
-#mailview-top.fullheight {
- border-radius: 4px 4px 0 0;
-}
-
#mailview-bottom {
position: absolute;
left: 0;
@@ -50,6 +46,10 @@
height: 26px;
}
+#mailview-top.fullheight {
+ border-radius: 4px 4px 0 0;
+}
+
#folderlist-header {
width: 100%;
height: 12px;
@@ -341,7 +341,6 @@ a.iconbutton.threadmode.selected {
#messagetoolbar {
position: absolute;
top: -6px;
- right: 390px;
left: 0;
height: 40px;
white-space: nowrap;
@@ -362,7 +361,7 @@ a.iconbutton.threadmode.selected {
position: absolute;
right: 0;
top: 0;
- width: 240px;
+ width: 400px;
}
#mailpreviewtoggle {
@@ -383,11 +382,7 @@ a.iconbutton.threadmode.selected {
/*** message list ***/
#messagelist thead td:first-child {
- border-radius: 4px 0 0 0;
-}
-
-#messagelist thead td:last-child {
- border-radius: 0 4px 0 0;
+ border-radius: 4px 0 0 0; /* for Chrome */
}
#messagelist tr td.attachment,
@@ -680,15 +675,14 @@ a.iconbutton.threadmode.selected {
#messagecontent {
position: absolute;
- top: 140px;
+ top: 0;
left: 0;
width: 100%;
- bottom: 0;
+ bottom: 28px;
overflow: auto;
border-radius: 4px 4px 0 0;
}
-#messageheader,
#partheader,
#composeheaders {
position: relative;
@@ -712,7 +706,7 @@ h2.subject {
h3.subject {
font-size: 14px;
- margin: 0 8em 0 0;
+ margin: 0 13em 0 0;
padding: 8px 8px 4px 8px;
white-space: nowrap;
overflow: hidden;
@@ -787,6 +781,7 @@ h3.subject {
background: -ms-linear-gradient(left, #fbfbfb 0, #e9e9e9 100%);
background: linear-gradient(left, #fbfbfb 0, #e9e9e9 100%);
border-right: 1px solid #dfdfdf;
+ border-radius: 3px 0 0 0; /* for Opera */
}
#previewheaderstoggle .iconlink {
@@ -801,28 +796,29 @@ h3.subject {
#previewheaderstoggle.remove .iconlink {
top: auto;
- bottom: 5px;
+ bottom: 15px;
background-position: -5px -242px;
}
-div.more-headers {
- cursor: pointer;
- height: 10px;
- background: url(images/buttons.png) center -1619px no-repeat;
+#previewheaderstoggle .iconlink.allheaders {
+ display: none;
}
-div.hide-headers {
- background-position: center -1629px;
+#previewheaderstoggle.remove .iconlink.allheaders {
+ top: auto;
+ bottom: 2px;
+ display: inline-block;
+ background-position: -27px -242px;
}
#all-headers {
position: relative;
- margin: 0 10px;
+ margin: 2px 0;
padding: 0;
height: 180px;
- border: 1px solid #bbb;
+ background-color: #f0f0f0;
+ overflow: hidden;
border-radius: 4px;
- background: #fff;
}
#headers-source {
@@ -832,25 +828,30 @@ div.hide-headers {
left: 0;
right: 0;
bottom: 0;
- padding: 2px 5px;
+ padding: 2px;
overflow: auto;
text-align: left;
- color: #333;
+ color: #666;
}
-#messagepreviewheader {
+#messageheader {
position: relative;
height: auto;
margin: 0 8px 0 0;
- padding: 0 0 6px 72px;
+ padding: 0 0 0 72px;
border-bottom: 2px solid #f0f0f0;
}
-#messagepreviewheader h3.subject {
+#messagecontent #messageheader {
+ padding: 0 0 0 90px;
+ min-height: 68px;
+}
+
+#messageheader h3.subject {
padding: 8px 8px 2px 0;
}
-#messagepreviewheader #contactphoto {
+#messageheader #contactphoto {
display: block;
position: absolute;
top: 11px;
@@ -862,52 +863,40 @@ div.hide-headers {
border-radius: 3px;
}
-#messagepreviewheader #contactphoto img {
+#messageheader #contactphoto img {
width: 32px;
height: auto;
border-radius: 3px;
}
-#messageheader #contactphoto {
- display: block;
- position: absolute;
- top: 40px;
- right: 10px;
+#messagecontent #messageheader #contactphoto {
+ top: 11px;
+ left: 31px;
width: 48px;
height: 48px;
- overflow: hidden;
+ background: url(images/contactpic_48px.png) center center no-repeat #fff;
border-radius: 4px;
}
-#messageheader #contactphoto img {
+#messagecontent #messageheader #contactphoto img {
width: 48px;
height: auto;
border-radius: 4px;
}
-#messagepreviewheader #countcontrols,
#messageheader #countcontrols {
position: absolute;
top: 8px;
- right: 8px;
- width: 20em;
+ right: 0;
text-align: right;
white-space: nowrap;
}
-#messageheader .pagenav .countdisplay {
- min-width: 0;
- padding-right: 0.5em;
- white-space: nowrap;
-}
-
-#messagecontent .leftcol,
#messagepreview .leftcol {
margin-right: 252px;
overflow-x: auto;
}
-#messagecontent .rightcol,
#messagepreview .rightcol {
float: right;
/*
@@ -921,6 +910,7 @@ div.hide-headers {
min-height: 200px;
background: #f0f0f0;
padding: 8px;
+ border-radius: 4px;
}
#messagebody {
diff --git a/skins/larry/styles.css b/skins/larry/styles.css
index 0a72c5048..f2d4888b1 100644
--- a/skins/larry/styles.css
+++ b/skins/larry/styles.css
@@ -647,6 +647,7 @@ a.iconlink.upload {
.uibox {
border: 1px solid #a3a3a3;
border-radius: 4px;
+ overflow: hidden;
box-shadow: 0 0 2px #999;
-o-box-shadow: 0 0 2px #999;
-webkit-box-shadow: 0 0 2px #999;
@@ -660,7 +661,7 @@ a.iconlink.upload {
left: 0;
bottom: 0;
width: 100%;
- min-width: 1150px;
+ min-width: 1024px;
}
.scroller {
@@ -698,7 +699,8 @@ a.iconlink.upload {
left: 0;
width: 100%;
bottom: 0;
- overflow: auto;
+ overflow-x: hidden;
+ overflow-y: auto;
}
.listbox .scroller.withfooter {
diff --git a/skins/larry/svggradient.php b/skins/larry/svggradient.php
index c54bdec17..8db2c5f63 100644
--- a/skins/larry/svggradient.php
+++ b/skins/larry/svggradient.php
@@ -11,6 +11,8 @@
* See http://creativecommons.org/licenses/by-sa/3.0/ for details.
*/
+ini_set('error_reporting', E_ALL &~ (E_NOTICE | E_STRICT));
+
header('Content-Type: image/svg+xml');
header("Expires: ".gmdate("D, d M Y H:i:s", time()+864000)." GMT");
header("Cache-Control: max-age=864000");
diff --git a/skins/larry/svggradients.css b/skins/larry/svggradients.css
index 143fb375f..4f1dd8a05 100644
--- a/skins/larry/svggradients.css
+++ b/skins/larry/svggradients.css
@@ -133,7 +133,7 @@ ul.toolbarmenu li a.active:hover,
background-image: url(svggradient.php?c=005d76;004558);
}
-#messageheader, #partheader, #composeheaders {
+#partheader, #composeheaders {
background-image: url(svggradient.php?c=ffffff;e9e9e9);
}
diff --git a/skins/larry/templates/message.html b/skins/larry/templates/message.html
index 1becd711a..89b7bd808 100644
--- a/skins/larry/templates/message.html
+++ b/skins/larry/templates/message.html
@@ -24,20 +24,38 @@
<!-- folders list -->
<div id="mailboxcontainer" class="uibox listbox">
-<div class="scroller">
-<roundcube:object name="mailboxlist" id="mailboxlist" class="listing" folder_filter="mail" unreadwrap="%s" />
-</div>
+ <div class="scroller">
+ <roundcube:object name="mailboxlist" id="mailboxlist" class="listing" folder_filter="mail" unreadwrap="%s" />
+ </div>
</div>
-</div>
+</div><!-- end mailview-left -->
+
+<div id="mailview-right" class="uibox" style="top: 42px">
+
+<div id="messagecontent">
+
+<div id="messageheader">
+<h3 class="subject"><roundcube:object name="messageHeaders" valueOf="subject" /></h3>
+
+<a href="#details" id="previewheaderstoggle"><span class="iconlink"></span><span id="headerstoggleall" class="iconlink allheaders"></span></a>
-<div id="mailview-right">
+<div id="contactphoto"><roundcube:object name="contactphoto" /></div>
-<div id="mailview-top">
-<div id="messageheader" class="uibox">
-<h2 class="subject"><roundcube:object name="messageHeaders" valueOf="subject" /></h2>
-<roundcube:object name="messageHeaders" class="headers-table" addicon="/images/addcontact.png" exclude="subject" />
-<roundcube:object name="messageFullHeaders" id="full-headers" />
+<table class="headers-table" id="preview-shortheaders"><tbody><tr>
+<roundcube:if condition="env:mailbox == config:drafts_mbox || env:mailbox == config:sent_mbox">
+ <td class="header-title"><roundcube:label name="to" /></td>
+ <td class="header from"><roundcube:object name="messageHeaders" valueOf="to" addicon="/images/addcontact.png" /></td>
+<roundcube:else />
+ <td class="header-title"><roundcube:label name="from" /></td>
+ <td class="header from"><roundcube:object name="messageHeaders" valueOf="from" addicon="/images/addcontact.png" /></td>
+<roundcube:endif />
+ <td class="header-title"><roundcube:label name="date" /></td>
+ <td class="header from"><roundcube:object name="messageHeaders" valueOf="date" /></td>
+</tr></tbody></table>
+
+<roundcube:object name="messageHeaders" id="preview-allheaders" class="headers-table" addicon="/images/addcontact.png" exclude="subject,replyto" />
+<roundcube:object name="messageFullHeaders" no-switch="true" />
<!-- record navigation -->
<div id="countcontrols" class="pagenav">
@@ -46,24 +64,21 @@
<roundcube:button command="nextmessage" type="link" class="button nextpage disabled" classAct="button nextpage" classSel="button nextpage pressed" innerClass="inner" title="nextmessage" content="&amp;gt;" />
</div>
-<div id="contactphoto"><roundcube:object name="contactphoto" /></div>
-</div>
+</div><!-- end messageheader -->
-<div id="messagecontent" class="uibox">
-<div class="rightcol">
-<roundcube:object name="messageAttachments" id="attachment-list" class="attachmentslist" />
-</div>
-<div class="leftcol">
-<roundcube:object name="messageObjects" id="message-objects" />
-<roundcube:object name="messageBody" id="messagebody" />
-</div>
+<div id="messagepreview">
+ <div class="rightcol">
+ <roundcube:object name="messageAttachments" id="attachment-list" class="attachmentslist" />
+ </div>
+ <div class="leftcol">
+ <roundcube:object name="messageObjects" id="message-objects" />
+ <roundcube:object name="messageBody" id="messagebody" />
+ </div>
</div>
-</div><!-- end mailview-top -->
+</div><!-- end messagecontent -->
-<div id="mailview-bottom" class="uibox">
<roundcube:object name="message" id="message" class="statusbar" />
-</div>
</div><!-- end mailview-right -->
diff --git a/skins/larry/templates/messageerror.html b/skins/larry/templates/messageerror.html
index 70181f174..2f5243200 100644
--- a/skins/larry/templates/messageerror.html
+++ b/skins/larry/templates/messageerror.html
@@ -27,8 +27,6 @@
</div>
-<div id="mailview-right">
-
<!-- toolbar -->
<div id="messagetoolbar" class="fullwidth">
<div id="mailtoolbar" class="toolbar">
@@ -36,11 +34,11 @@
</div>
</div>
-<div id="mailview-top" class="uibox watermark"></div>
+<div id="mailview-right" class="uibox" style="top: 42px">
-<div id="mailview-bottom" class="uibox">
- <roundcube:object name="message" id="message" class="statusbar" />
-</div>
+<div id="messagecontent" class="watermark"></div>
+
+<roundcube:object name="message" id="message" class="statusbar" />
</div><!-- end mailview-right -->
diff --git a/skins/larry/templates/messagepreview.html b/skins/larry/templates/messagepreview.html
index b53683ec1..74c414b0d 100644
--- a/skins/larry/templates/messagepreview.html
+++ b/skins/larry/templates/messagepreview.html
@@ -6,10 +6,10 @@
</head>
<body class="iframe fullheight">
-<div id="messagepreviewheader">
+<div id="messageheader">
<h3 class="subject"><roundcube:object name="messageHeaders" valueOf="subject" /></h3>
-<a href="#details" id="previewheaderstoggle"><span class="iconlink"></span></a>
+<a href="#details" id="previewheaderstoggle"><span class="iconlink"></span><span id="headerstoggleall" class="iconlink allheaders"></a>
<div id="contactphoto"><roundcube:object name="contactphoto" /></div>
<table class="headers-table" id="preview-shortheaders"><tbody><tr>
@@ -25,6 +25,7 @@
</tr></tbody></table>
<roundcube:object name="messageHeaders" id="preview-allheaders" class="headers-table" addicon="/images/addcontact.png" exclude="subject,replyto" />
+<roundcube:object name="messageFullHeaders" no-switch="true" />
<!-- record navigation -->
<div id="countcontrols" class="pagenav">
diff --git a/skins/larry/ui.js b/skins/larry/ui.js
index b6056b6ee..e3b5eefe4 100644
--- a/skins/larry/ui.js
+++ b/skins/larry/ui.js
@@ -74,9 +74,8 @@ function rcube_mail_ui()
if (rcmail.env.action == 'show' || rcmail.env.action == 'preview') {
layout_messageview();
- rcmail.addEventListener('aftershow-headers', function() { layout_messageview(); });
- rcmail.addEventListener('afterhide-headers', function() { layout_messageview(); });
- $('#previewheaderstoggle').click(function(e){ toggle_preview_headers(this); return false });
+ $('#previewheaderstoggle').click(function(e){ toggle_preview_headers(this); return false; });
+ $('#headerstoggleall').click(function(e){ toggle_all_headers(this); return false; });
}
else if (rcmail.env.action == 'compose') {
rcmail.addEventListener('aftertoggle-editor', function(){ window.setTimeout(function(){ layout_composeview() }, 200); });
@@ -164,6 +163,12 @@ function rcube_mail_ui()
}
}
+ // set min-width to show all toolbar buttons
+ var screen = $('.minwidth');
+ if (screen.length) {
+ screen.css('min-width', $('.toolbar').width() + $('#quicksearchbar').parent().width() + 20);
+ }
+
// turn a group of fieldsets into tabs
$('.tabbed').each(function(idx, elem){ init_tabs(elem); })
@@ -254,11 +259,11 @@ function rcube_mail_ui()
*/
function resize()
{
- if (rcmail.env.task == 'mail' && (rcmail.env.action == 'show' || rcmail.env.action == 'preview')) {
- layout_messageview();
- }
- if (rcmail.env.task == 'mail' && rcmail.env.action == 'compose') {
- layout_composeview();
+ if (rcmail.env.task == 'mail') {
+ if (rcmail.env.action == 'show' || rcmail.env.action == 'preview')
+ layout_messageview();
+ else if (rcmail.env.action == 'compose')
+ layout_composeview();
}
// make iframe footer buttons float if scrolling is active
@@ -267,13 +272,13 @@ function rcube_mail_ui()
body = $(document.body),
floating = footer.hasClass('floating'),
overflow = body.outerHeight(true) > $(window).height();
+
if (overflow != floating) {
var action = overflow ? 'addClass' : 'removeClass';
footer[action]('floating');
body[action]('floatingbuttons');
}
- })
-
+ });
}
/**
@@ -315,7 +320,6 @@ function rcube_mail_ui()
*/
function layout_messageview()
{
- $('#messagecontent').css('top', ($('#messageheader').outerHeight() + 10) + 'px');
$('#message-objects div a').addClass('button');
if (!$('#attachment-list li').length) {
@@ -461,7 +465,7 @@ function rcube_mail_ui()
var button = $(e.target),
frame = $('#mailpreviewframe'),
visible = !frame.is(':visible'),
- splitter = mailviewsplit.pos || parseInt(bw.get_cookie('mailviewsplitter') || 320),
+ splitter = mailviewsplit.pos || parseInt(rcmail.get_cookie('mailviewsplitter') || 320),
topstyles, bottomstyles, uid;
frame.toggle();
@@ -508,13 +512,31 @@ function rcube_mail_ui()
{
$('#preview-shortheaders').toggle();
var full = $('#preview-allheaders').toggle(),
- button = $('a#previewheaderstoggle');
+ button = $('#previewheaderstoggle');
+
+ if (!$('#headerstoggleall').length)
+ $('#all-headers').toggle();
// add toggle button to full headers table
- if (full.is(':visible'))
- button.attr('href', '#hide').removeClass('add').addClass('remove')
- else
- button.attr('href', '#details').removeClass('remove').addClass('add')
+ if (full.is(':visible')) {
+ button.attr('href', '#hide').removeClass('add').addClass('remove');
+ }
+ else {
+ button.attr('href', '#details').removeClass('remove').addClass('add');
+ }
+ }
+
+
+ /**
+ * Show/hide all message headers
+ */
+ function toggle_all_headers(button)
+ {
+ rcmail.command('show-headers', '', button);
+ $(button).remove();
+ $('#previewheaderstoggle span').css({bottom: '5px'});
+
+ return false;
}
@@ -847,6 +869,8 @@ function rcube_mail_ui()
// Select/unselect tab
$('#tab'+idx).toggleClass('selected', idx==index);
});
+
+ resize();
}
/**
@@ -974,7 +998,7 @@ function rcube_splitter(p)
$(window).resize(onResize);
// read saved position from cookie
- var cookie = bw.get_cookie(this.id);
+ var cookie = rcmail.get_cookie(this.id);
if (cookie && !isNaN(cookie)) {
this.pos = parseFloat(cookie);
this.resize();
@@ -1135,7 +1159,7 @@ function rcube_splitter(p)
{
var exp = new Date();
exp.setYear(exp.getFullYear() + 1);
- bw.set_cookie(this.id, this.pos, exp);
+ rcmail.set_cookie(this.id, this.pos, exp);
};
} // end class rcube_splitter
diff --git a/tests/Framework/BaseReplacer.php b/tests/Framework/BaseReplacer.php
new file mode 100644
index 000000000..e00b9e5eb
--- /dev/null
+++ b/tests/Framework/BaseReplacer.php
@@ -0,0 +1,20 @@
+<?php
+
+/**
+ * Test class to test rcube_base_replacer class
+ *
+ * @package Tests
+ */
+class Framework_BaseReplacer extends PHPUnit_Framework_TestCase
+{
+
+ /**
+ * Class constructor
+ */
+ function test_class()
+ {
+ $object = new rcube_base_replacer('test');
+
+ $this->assertInstanceOf('rcube_base_replacer', $object, "Class constructor");
+ }
+}
diff --git a/tests/Framework/Browser.php b/tests/Framework/Browser.php
new file mode 100644
index 000000000..c3860d8a3
--- /dev/null
+++ b/tests/Framework/Browser.php
@@ -0,0 +1,20 @@
+<?php
+
+/**
+ * Test class to test rcube_browser class
+ *
+ * @package Tests
+ */
+class Framework_Browser extends PHPUnit_Framework_TestCase
+{
+
+ /**
+ * Class constructor
+ */
+ function test_class()
+ {
+ $object = new rcube_browser();
+
+ $this->assertInstanceOf('rcube_browser', $object, "Class constructor");
+ }
+}
diff --git a/tests/Framework/Cache.php b/tests/Framework/Cache.php
new file mode 100644
index 000000000..dc026a634
--- /dev/null
+++ b/tests/Framework/Cache.php
@@ -0,0 +1,20 @@
+<?php
+
+/**
+ * Test class to test rcube_cache class
+ *
+ * @package Tests
+ */
+class Framework_Cache extends PHPUnit_Framework_TestCase
+{
+
+ /**
+ * Class constructor
+ */
+ function test_class()
+ {
+ $object = new rcube_cache('db', 1);
+
+ $this->assertInstanceOf('rcube_cache', $object, "Class constructor");
+ }
+}
diff --git a/tests/Framework/Charset.php b/tests/Framework/Charset.php
new file mode 100644
index 000000000..9e3fad4d3
--- /dev/null
+++ b/tests/Framework/Charset.php
@@ -0,0 +1,28 @@
+<?php
+
+/**
+ * Test class to test rcube_charset class
+ *
+ * @package Tests
+ */
+class Framework_Charset extends PHPUnit_Framework_TestCase
+{
+
+ /**
+ * Data for test_clean()
+ */
+ function data_clean()
+ {
+ return array(
+ array('', '', 'Empty string'),
+ );
+ }
+
+ /**
+ * @dataProvider data_clean
+ */
+ function test_clean($input, $output, $title)
+ {
+ $this->assertEquals(rcube_charset::clean($input), $output, $title);
+ }
+}
diff --git a/tests/Framework/ContentFilter.php b/tests/Framework/ContentFilter.php
new file mode 100644
index 000000000..9bee9368b
--- /dev/null
+++ b/tests/Framework/ContentFilter.php
@@ -0,0 +1,20 @@
+<?php
+
+/**
+ * Test class to test rcube_content_filter class
+ *
+ * @package Tests
+ */
+class Framework_ContentFilter extends PHPUnit_Framework_TestCase
+{
+
+ /**
+ * Class constructor
+ */
+ function test_class()
+ {
+ $object = new rcube_content_filter();
+
+ $this->assertInstanceOf('rcube_content_filter', $object, "Class constructor");
+ }
+}
diff --git a/tests/Framework/Html.php b/tests/Framework/Html.php
new file mode 100644
index 000000000..8a27baca8
--- /dev/null
+++ b/tests/Framework/Html.php
@@ -0,0 +1,46 @@
+<?php
+
+/**
+ * Test class to test rcube_html class
+ *
+ * @package Tests
+ */
+class Framework_Html extends PHPUnit_Framework_TestCase
+{
+
+ /**
+ * Class constructor
+ */
+ function test_class()
+ {
+ $object = new html;
+
+ $this->assertInstanceOf('html', $object, "Class constructor");
+ }
+
+ /**
+ * Data for test_quote()
+ */
+ function data_quote()
+ {
+ return array(
+ array('abc', 'abc'),
+ array('?', '?'),
+ array('"', '&quot;'),
+ array('<', '&lt;'),
+ array('>', '&gt;'),
+ array('&', '&amp;'),
+ array('&amp;', '&amp;amp;'),
+ array('&amp;', '&amp;', true),
+ );
+ }
+
+ /**
+ * Test for quote()
+ * @dataProvider data_quote
+ */
+ function test_quote($str, $result, $validate = false)
+ {
+ $this->assertEquals(html::quote($str, $validate), $result);
+ }
+}
diff --git a/tests/Framework/Image.php b/tests/Framework/Image.php
new file mode 100644
index 000000000..31e852042
--- /dev/null
+++ b/tests/Framework/Image.php
@@ -0,0 +1,20 @@
+<?php
+
+/**
+ * Test class to test rcube_image class
+ *
+ * @package Tests
+ */
+class Framework_Image extends PHPUnit_Framework_TestCase
+{
+
+ /**
+ * Class constructor
+ */
+ function test_class()
+ {
+ $object = new rcube_image('test');
+
+ $this->assertInstanceOf('rcube_image', $object, "Class constructor");
+ }
+}
diff --git a/tests/Framework/Imap.php b/tests/Framework/Imap.php
new file mode 100644
index 000000000..3f52e07be
--- /dev/null
+++ b/tests/Framework/Imap.php
@@ -0,0 +1,20 @@
+<?php
+
+/**
+ * Test class to test rcube_imap class
+ *
+ * @package Tests
+ */
+class Framework_Imap extends PHPUnit_Framework_TestCase
+{
+
+ /**
+ * Class constructor
+ */
+ function test_class()
+ {
+ $object = new rcube_imap;
+
+ $this->assertInstanceOf('rcube_imap', $object, "Class constructor");
+ }
+}
diff --git a/tests/Framework/ImapGeneric.php b/tests/Framework/ImapGeneric.php
new file mode 100644
index 000000000..0b2cc3d53
--- /dev/null
+++ b/tests/Framework/ImapGeneric.php
@@ -0,0 +1,20 @@
+<?php
+
+/**
+ * Test class to test rcube_imap_generic class
+ *
+ * @package Tests
+ */
+class Framework_ImapGeneric extends PHPUnit_Framework_TestCase
+{
+
+ /**
+ * Class constructor
+ */
+ function test_class()
+ {
+ $object = new rcube_imap_generic;
+
+ $this->assertInstanceOf('rcube_imap_generic', $object, "Class constructor");
+ }
+}
diff --git a/tests/Framework/MessageHeader.php b/tests/Framework/MessageHeader.php
new file mode 100644
index 000000000..e5bc1752f
--- /dev/null
+++ b/tests/Framework/MessageHeader.php
@@ -0,0 +1,20 @@
+<?php
+
+/**
+ * Test class to test rcube_message_header class
+ *
+ * @package Tests
+ */
+class Framework_MessageHeader extends PHPUnit_Framework_TestCase
+{
+
+ /**
+ * Class constructor
+ */
+ function test_class()
+ {
+ $object = new rcube_message_header;
+
+ $this->assertInstanceOf('rcube_message_header', $object, "Class constructor");
+ }
+}
diff --git a/tests/Framework/MessagePart.php b/tests/Framework/MessagePart.php
new file mode 100644
index 000000000..deb426024
--- /dev/null
+++ b/tests/Framework/MessagePart.php
@@ -0,0 +1,20 @@
+<?php
+
+/**
+ * Test class to test rcube_message_part class
+ *
+ * @package Tests
+ */
+class Framework_MessagePart extends PHPUnit_Framework_TestCase
+{
+
+ /**
+ * Class constructor
+ */
+ function test_class()
+ {
+ $object = new rcube_message_part;
+
+ $this->assertInstanceOf('rcube_message_part', $object, "Class constructor");
+ }
+}
diff --git a/tests/Framework/Mime.php b/tests/Framework/Mime.php
new file mode 100644
index 000000000..dcd55992a
--- /dev/null
+++ b/tests/Framework/Mime.php
@@ -0,0 +1,123 @@
+<?php
+
+/**
+ * Test class to test rcube_mime class
+ *
+ * @package Tests
+ */
+class Framework_Mime extends PHPUnit_Framework_TestCase
+{
+
+ /**
+ * Test decoding of single e-mail address strings
+ * Uses rcube_mime::decode_address_list()
+ */
+ function test_decode_single_address()
+ {
+ $headers = array(
+ 0 => 'test@domain.tld',
+ 1 => '<test@domain.tld>',
+ 2 => 'Test <test@domain.tld>',
+ 3 => 'Test Test <test@domain.tld>',
+ 4 => 'Test Test<test@domain.tld>',
+ 5 => '"Test Test" <test@domain.tld>',
+ 6 => '"Test Test"<test@domain.tld>',
+ 7 => '"Test \\" Test" <test@domain.tld>',
+ 8 => '"Test<Test" <test@domain.tld>',
+ 9 => '=?ISO-8859-1?B?VGVzdAo=?= <test@domain.tld>',
+ 10 => '=?ISO-8859-1?B?VGVzdAo=?=<test@domain.tld>', // #1487068
+ // comments in address (#1487673)
+ 11 => 'Test (comment) <test@domain.tld>',
+ 12 => '"Test" (comment) <test@domain.tld>',
+ 13 => '"Test (comment)" (comment) <test@domain.tld>',
+ 14 => '(comment) <test@domain.tld>',
+ 15 => 'Test <test@(comment)domain.tld>',
+ 16 => 'Test Test ((comment)) <test@domain.tld>',
+ 17 => 'test@domain.tld (comment)',
+ 18 => '"Test,Test" <test@domain.tld>',
+ // 1487939
+ 19 => 'Test <"test test"@domain.tld>',
+ 20 => '<"test test"@domain.tld>',
+ 21 => '"test test"@domain.tld',
+ );
+
+ $results = array(
+ 0 => array(1, '', 'test@domain.tld'),
+ 1 => array(1, '', 'test@domain.tld'),
+ 2 => array(1, 'Test', 'test@domain.tld'),
+ 3 => array(1, 'Test Test', 'test@domain.tld'),
+ 4 => array(1, 'Test Test', 'test@domain.tld'),
+ 5 => array(1, 'Test Test', 'test@domain.tld'),
+ 6 => array(1, 'Test Test', 'test@domain.tld'),
+ 7 => array(1, 'Test " Test', 'test@domain.tld'),
+ 8 => array(1, 'Test<Test', 'test@domain.tld'),
+ 9 => array(1, 'Test', 'test@domain.tld'),
+ 10 => array(1, 'Test', 'test@domain.tld'),
+ 11 => array(1, 'Test', 'test@domain.tld'),
+ 12 => array(1, 'Test', 'test@domain.tld'),
+ 13 => array(1, 'Test (comment)', 'test@domain.tld'),
+ 14 => array(1, '', 'test@domain.tld'),
+ 15 => array(1, 'Test', 'test@domain.tld'),
+ 16 => array(1, 'Test Test', 'test@domain.tld'),
+ 17 => array(1, '', 'test@domain.tld'),
+ 18 => array(1, 'Test,Test', 'test@domain.tld'),
+ 19 => array(1, 'Test', '"test test"@domain.tld'),
+ 20 => array(1, '', '"test test"@domain.tld'),
+ 21 => array(1, '', '"test test"@domain.tld'),
+ );
+
+ foreach ($headers as $idx => $header) {
+ $res = rcube_mime::decode_address_list($header);
+
+ $this->assertEquals($results[$idx][0], count($res), "Rows number in result for header: " . $header);
+ $this->assertEquals($results[$idx][1], $res[1]['name'], "Name part decoding for header: " . $header);
+ $this->assertEquals($results[$idx][2], $res[1]['mailto'], "Email part decoding for header: " . $header);
+ }
+ }
+
+ /**
+ * Test decoding of header values
+ * Uses rcube_mime::decode_mime_string()
+ */
+ function test_header_decode_qp()
+ {
+ $test = array(
+ // #1488232: invalid character "?"
+ 'quoted-printable (1)' => array(
+ 'in' => '=?utf-8?Q?Certifica=C3=A7=C3=A3??=',
+ 'out' => 'Certifica=C3=A7=C3=A3?',
+ ),
+ 'quoted-printable (2)' => array(
+ 'in' => '=?utf-8?Q?Certifica=?= =?utf-8?Q?C3=A7=C3=A3?=',
+ 'out' => 'Certifica=C3=A7=C3=A3',
+ ),
+ 'quoted-printable (3)' => array(
+ 'in' => '=?utf-8?Q??= =?utf-8?Q??=',
+ 'out' => '',
+ ),
+ 'quoted-printable (4)' => array(
+ 'in' => '=?utf-8?Q??= a =?utf-8?Q??=',
+ 'out' => ' a ',
+ ),
+ 'quoted-printable (5)' => array(
+ 'in' => '=?utf-8?Q?a?= =?utf-8?Q?b?=',
+ 'out' => 'ab',
+ ),
+ 'quoted-printable (6)' => array(
+ 'in' => '=?utf-8?Q? ?= =?utf-8?Q?a?=',
+ 'out' => ' a',
+ ),
+ 'quoted-printable (7)' => array(
+ 'in' => '=?utf-8?Q?___?= =?utf-8?Q?a?=',
+ 'out' => ' a',
+ ),
+ );
+
+ foreach ($test as $idx => $item) {
+ $res = rcube_mime::decode_mime_string($item['in'], 'UTF-8');
+ $res = quoted_printable_encode($res);
+
+ $this->assertEquals($item['out'], $res, "Header decoding for: " . $idx);
+ }
+ }
+}
diff --git a/tests/Framework/Rcube.php b/tests/Framework/Rcube.php
new file mode 100644
index 000000000..637558dc9
--- /dev/null
+++ b/tests/Framework/Rcube.php
@@ -0,0 +1,20 @@
+<?php
+
+/**
+ * Test class to test rcube class
+ *
+ * @package Tests
+ */
+class Framework_Rcube extends PHPUnit_Framework_TestCase
+{
+
+ /**
+ * Class constructor
+ */
+ function test_class()
+ {
+ $object = rcube::get_instance();
+
+ $this->assertInstanceOf('rcube', $object, "Class singleton");
+ }
+}
diff --git a/tests/Framework/ResultIndex.php b/tests/Framework/ResultIndex.php
new file mode 100644
index 000000000..efbba6da7
--- /dev/null
+++ b/tests/Framework/ResultIndex.php
@@ -0,0 +1,20 @@
+<?php
+
+/**
+ * Test class to test rcube_result_index class
+ *
+ * @package Tests
+ */
+class Framework_ResultIndex extends PHPUnit_Framework_TestCase
+{
+
+ /**
+ * Class constructor
+ */
+ function test_class()
+ {
+ $object = new rcube_result_index;
+
+ $this->assertInstanceOf('rcube_result_index', $object, "Class constructor");
+ }
+}
diff --git a/tests/Framework/ResultSet.php b/tests/Framework/ResultSet.php
new file mode 100644
index 000000000..2d04e53c0
--- /dev/null
+++ b/tests/Framework/ResultSet.php
@@ -0,0 +1,20 @@
+<?php
+
+/**
+ * Test class to test rcube_result_set class
+ *
+ * @package Tests
+ */
+class Framework_ResultSet extends PHPUnit_Framework_TestCase
+{
+
+ /**
+ * Class constructor
+ */
+ function test_class()
+ {
+ $object = new rcube_result_set;
+
+ $this->assertInstanceOf('rcube_result_set', $object, "Class constructor");
+ }
+}
diff --git a/tests/Framework/ResultThread.php b/tests/Framework/ResultThread.php
new file mode 100644
index 000000000..f980845cc
--- /dev/null
+++ b/tests/Framework/ResultThread.php
@@ -0,0 +1,20 @@
+<?php
+
+/**
+ * Test class to test rcube_result_thread class
+ *
+ * @package Tests
+ */
+class Framework_ResultThread extends PHPUnit_Framework_TestCase
+{
+
+ /**
+ * Class constructor
+ */
+ function test_class()
+ {
+ $object = new rcube_result_thread;
+
+ $this->assertInstanceOf('rcube_result_thread', $object, "Class constructor");
+ }
+}
diff --git a/tests/Framework/Shared.php b/tests/Framework/Shared.php
new file mode 100644
index 000000000..99ef829da
--- /dev/null
+++ b/tests/Framework/Shared.php
@@ -0,0 +1,204 @@
+<?php
+
+/**
+ * Test class to test rcube_shared functions
+ *
+ * @package Tests
+ */
+class Framework_Shared extends PHPUnit_Framework_TestCase
+{
+
+ /**
+ * rcube_shared.inc: in_array_nocase()
+ */
+ function test_in_array_nocase()
+ {
+ $haystack = array('Test');
+ $needle = 'test';
+ $result = in_array_nocase($needle, $haystack);
+
+ $this->assertTrue($result, $title);
+
+ $result = in_array_nocase($needle, null);
+
+ $this->assertFalse($result, $title);
+ }
+
+ /**
+ * rcube_shared.inc: get_boolean()
+ */
+ function test_get_boolean()
+ {
+ $input = array(
+ false, 'false', '0', 'no', 'off', 'nein', 'FALSE', '', null,
+ );
+
+ foreach ($input as $idx => $value) {
+ $this->assertFalse(get_boolean($value), "Invalid result for $idx test item");
+ }
+
+ $input = array(
+ true, 'true', '1', 1, 'yes', 'anything', 1000,
+ );
+
+ foreach ($input as $idx => $value) {
+ $this->assertTrue(get_boolean($value), "Invalid result for $idx test item");
+ }
+ }
+
+ /**
+ * rcube_shared.inc: parse_bytes()
+ */
+ function test_parse_bytes()
+ {
+ $data = array(
+ '1' => 1,
+ '1024' => 1024,
+ '2k' => 2 * 1024,
+ '2 k' => 2 * 1024,
+ '2kb' => 2 * 1024,
+ '2kB' => 2 * 1024,
+ '2m' => 2 * 1048576,
+ '2 m' => 2 * 1048576,
+ '2mb' => 2 * 1048576,
+ '2mB' => 2 * 1048576,
+ '2g' => 2 * 1024 * 1048576,
+ '2 g' => 2 * 1024 * 1048576,
+ '2gb' => 2 * 1024 * 1048576,
+ '2gB' => 2 * 1024 * 1048576,
+ );
+
+ foreach ($data as $value => $expected) {
+ $result = parse_bytes($value);
+ $this->assertEquals($expected, $result, "Invalid parse_bytes() result for $value");
+ }
+ }
+
+ /**
+ * rcube_shared.inc: slashify()
+ */
+ function test_slashify()
+ {
+ $data = array(
+ 'test' => 'test/',
+ 'test/' => 'test/',
+ '' => '/',
+ "\\" => "\\/",
+ );
+
+ foreach ($data as $value => $expected) {
+ $result = slashify($value);
+ $this->assertEquals($expected, $result, "Invalid slashify() result for $value");
+ }
+
+ }
+
+ /**
+ * rcube_shared.inc: unslashify()
+ */
+ function test_unslashify()
+ {
+ $data = array(
+ 'test' => 'test',
+ 'test/' => 'test',
+ '/' => '',
+ "\\/" => "\\",
+ 'test/test' => 'test/test',
+ 'test//' => 'test',
+ );
+
+ foreach ($data as $value => $expected) {
+ $result = unslashify($value);
+ $this->assertEquals($expected, $result, "Invalid unslashify() result for $value");
+ }
+
+ }
+
+ /**
+ * rcube_shared.inc: get_offset_sec()
+ */
+ function test_get_offset_sec()
+ {
+ $data = array(
+ '1s' => 1,
+ '1m' => 1 * 60,
+ '1h' => 1 * 60 * 60,
+ '1d' => 1 * 60 * 60 * 24,
+ '1w' => 1 * 60 * 60 * 24 * 7,
+ '1y' => (int) '1y',
+ 100 => 100,
+ '100' => 100,
+ );
+
+ foreach ($data as $value => $expected) {
+ $result = get_offset_sec($value);
+ $this->assertEquals($expected, $result, "Invalid get_offset_sec() result for $value");
+ }
+
+ }
+
+ /**
+ * rcube_shared.inc: array_keys_recursive()
+ */
+ function test_array_keys_recursive()
+ {
+ $input = array(
+ 'one' => array(
+ 'two' => array(
+ 'three' => array(),
+ 'four' => 'something',
+ ),
+ ),
+ 'five' => 'test',
+ );
+
+ $result = array_keys_recursive($input);
+ $input_str = 'one,two,three,four,five';
+ $result_str = implode(',', $result);
+
+ $this->assertEquals($input_str, $result_str, "Invalid array_keys_recursive() result");
+ }
+
+ /**
+ * rcube_shared.inc: format_email()
+ */
+ function test_format_email()
+ {
+ $data = array(
+ '' => '',
+ 'test' => 'test',
+ 'test@test.tld' => 'test@test.tld',
+ 'test@[127.0.0.1]' => 'test@[127.0.0.1]',
+ 'TEST@TEST.TLD' => 'TEST@test.tld',
+ );
+
+ foreach ($data as $value => $expected) {
+ $result = format_email($value);
+ $this->assertEquals($expected, $result, "Invalid format_email() result for $value");
+ }
+
+ }
+
+ /**
+ * rcube_shared.inc: format_email_recipient()
+ */
+ function test_format_email_recipient()
+ {
+ $data = array(
+ '' => array(''),
+ 'test' => array('test'),
+ 'test@test.tld' => array('test@test.tld'),
+ 'test@[127.0.0.1]' => array('test@[127.0.0.1]'),
+ 'TEST@TEST.TLD' => array('TEST@TEST.TLD'),
+ 'TEST <test@test.tld>' => array('test@test.tld', 'TEST'),
+ '"TEST\"" <test@test.tld>' => array('test@test.tld', 'TEST"'),
+ );
+
+ foreach ($data as $expected => $value) {
+ $result = format_email_recipient($value[0], $value[1]);
+ $this->assertEquals($expected, $result, "Invalid format_email_recipient()");
+ }
+
+ }
+
+}
diff --git a/tests/Framework/Smtp.php b/tests/Framework/Smtp.php
new file mode 100644
index 000000000..4bd78d097
--- /dev/null
+++ b/tests/Framework/Smtp.php
@@ -0,0 +1,20 @@
+<?php
+
+/**
+ * Test class to test rcube_smtp class
+ *
+ * @package Tests
+ */
+class Framework_Smtp extends PHPUnit_Framework_TestCase
+{
+
+ /**
+ * Class constructor
+ */
+ function test_class()
+ {
+ $object = new rcube_smtp;
+
+ $this->assertInstanceOf('rcube_smtp', $object, "Class constructor");
+ }
+}
diff --git a/tests/Framework/Spellchecker.php b/tests/Framework/Spellchecker.php
new file mode 100644
index 000000000..9c3e92ffd
--- /dev/null
+++ b/tests/Framework/Spellchecker.php
@@ -0,0 +1,20 @@
+<?php
+
+/**
+ * Test class to test rcube_spellchecker class
+ *
+ * @package Tests
+ */
+class Framework_Spellchecker extends PHPUnit_Framework_TestCase
+{
+
+ /**
+ * Class constructor
+ */
+ function test_class()
+ {
+ $object = new rcube_spellchecker;
+
+ $this->assertInstanceOf('rcube_spellchecker', $object, "Class constructor");
+ }
+}
diff --git a/tests/Framework/StringReplacer.php b/tests/Framework/StringReplacer.php
new file mode 100644
index 000000000..11210c0da
--- /dev/null
+++ b/tests/Framework/StringReplacer.php
@@ -0,0 +1,20 @@
+<?php
+
+/**
+ * Test class to test rcube_string_replacer class
+ *
+ * @package Tests
+ */
+class Framework_StringReplacer extends PHPUnit_Framework_TestCase
+{
+
+ /**
+ * Class constructor
+ */
+ function test_class()
+ {
+ $sr = new rcube_string_replacer;
+
+ $this->assertInstanceOf('rcube_string_replacer', $sr, "Class constructor");
+ }
+}
diff --git a/tests/Framework/User.php b/tests/Framework/User.php
new file mode 100644
index 000000000..3b1983c58
--- /dev/null
+++ b/tests/Framework/User.php
@@ -0,0 +1,20 @@
+<?php
+
+/**
+ * Test class to test rcube_user class
+ *
+ * @package Tests
+ */
+class Framework_User extends PHPUnit_Framework_TestCase
+{
+
+ /**
+ * Class constructor
+ */
+ function test_class()
+ {
+ $user = new rcube_user;
+
+ $this->assertInstanceOf('rcube_user', $user, "Class constructor");
+ }
+}
diff --git a/tests/Framework/Utils.php b/tests/Framework/Utils.php
new file mode 100644
index 000000000..e58835956
--- /dev/null
+++ b/tests/Framework/Utils.php
@@ -0,0 +1,196 @@
+<?php
+
+/**
+ * Test class to test rcube_utils class
+ *
+ * @package Tests
+ */
+class Framework_Utils extends PHPUnit_Framework_TestCase
+{
+
+ /**
+ * Valid email addresses for test_valid_email()
+ */
+ function data_valid_email()
+ {
+ return array(
+ array('email@domain.com', 'Valid email'),
+ array('firstname.lastname@domain.com', 'Email contains dot in the address field'),
+ array('email@subdomain.domain.com', 'Email contains dot with subdomain'),
+ array('firstname+lastname@domain.com', 'Plus sign is considered valid character'),
+ array('email@[123.123.123.123]', 'Square bracket around IP address'),
+ array('email@[IPv6:::1]', 'Square bracket around IPv6 address (1)'),
+ array('email@[IPv6:::1.2.3.4]', 'Square bracket around IPv6 address (2)'),
+ array('email@[IPv6:2001:2d12:c4fe:5afe::1]', 'Square bracket around IPv6 address (3)'),
+ array('"email"@domain.com', 'Quotes around email is considered valid'),
+ array('1234567890@domain.com', 'Digits in address are valid'),
+ array('email@domain-one.com', 'Dash in domain name is valid'),
+ array('_______@domain.com', 'Underscore in the address field is valid'),
+ array('email@domain.name', '.name is valid Top Level Domain name'),
+ array('email@domain.co.jp', 'Dot in Top Level Domain name also considered valid (use co.jp as example here)'),
+ array('firstname-lastname@domain.com', 'Dash in address field is valid'),
+ );
+ }
+
+ /**
+ * Invalid email addresses for test_invalid_email()
+ */
+ function data_invalid_email()
+ {
+ return array(
+ array('plainaddress', 'Missing @ sign and domain'),
+ array('#@%^%#$@#$@#.com', 'Garbage'),
+ array('@domain.com', 'Missing username'),
+ array('Joe Smith <email@domain.com>', 'Encoded html within email is invalid'),
+ array('email.domain.com', 'Missing @'),
+ array('email@domain@domain.com', 'Two @ sign'),
+ array('.email@domain.com', 'Leading dot in address is not allowed'),
+ array('email.@domain.com', 'Trailing dot in address is not allowed'),
+ array('email..email@domain.com', 'Multiple dots'),
+ array('あいうえお@domain.com', 'Unicode char as address'),
+ array('email@domain.com (Joe Smith)', 'Text followed email is not allowed'),
+ array('email@domain', 'Missing top level domain (.com/.net/.org/etc)'),
+ array('email@-domain.com', 'Leading dash in front of domain is invalid'),
+// array('email@domain.web', '.web is not a valid top level domain'),
+ array('email@123.123.123.123', 'IP address without brackets'),
+ array('email@2001:2d12:c4fe:5afe::1', 'IPv6 address without brackets'),
+ array('email@IPv6:2001:2d12:c4fe:5afe::1', 'IPv6 address without brackets (2)'),
+ array('email@[111.222.333.44444]', 'Invalid IP format'),
+ array('email@[111.222.255.257]', 'Invalid IP format (2)'),
+ array('email@[.222.255.257]', 'Invalid IP format (3)'),
+ array('email@[::1]', 'Invalid IPv6 format (1)'),
+ array('email@[IPv6:2001:23x2:1]', 'Invalid IPv6 format (2)'),
+ array('email@[IPv6:1111:2222:33333::4444:5555]', 'Invalid IPv6 format (3)'),
+ array('email@[IPv6:1111::3333::4444:5555]', 'Invalid IPv6 format (4)'),
+ array('email@domain..com', 'Multiple dot in the domain portion is invalid'),
+ );
+ }
+
+ /**
+ * @dataProvider data_valid_email
+ */
+ function test_valid_email($email, $title)
+ {
+ $this->assertTrue(rcube_utils::check_email($email, false), $title);
+ }
+
+ /**
+ * @dataProvider data_invalid_email
+ */
+ function test_invalid_email($email, $title)
+ {
+ $this->assertFalse(rcube_utils::check_email($email, false), $title);
+ }
+
+ /**
+ * Valid IP addresses for test_valid_ip()
+ */
+ function data_valid_ip()
+ {
+ return array(
+ array('0.0.0.0'),
+ array('123.123.123.123'),
+ array('::'),
+ array('::1'),
+ array('::1.2.3.4'),
+ array('2001:2d12:c4fe:5afe::1'),
+ );
+ }
+
+ /**
+ * Valid IP addresses for test_invalid_ip()
+ */
+ function data_invalid_ip()
+ {
+ return array(
+ array(''),
+ array(0),
+ array('123.123.123.1234'),
+ array('1.1.1.1.1'),
+ array('::1.2.3.260'),
+ array('::1.0'),
+ array('2001::c4fe:5afe::1'),
+ );
+ }
+
+ /**
+ * @dataProvider data_valid_ip
+ */
+ function test_valid_ip($ip)
+ {
+ $this->assertTrue(rcube_utils::check_ip($ip));
+ }
+
+ /**
+ * @dataProvider data_invalid_ip
+ */
+ function test_invalid_ip($ip)
+ {
+ $this->assertFalse(rcube_utils::check_ip($ip));
+ }
+
+ /**
+ * Data for test_rep_specialchars_output()
+ */
+ function data_rep_specialchars_output()
+ {
+ return array(
+ array('', '', 'abc', 'abc'),
+ array('', '', '?', '?'),
+ array('', '', '"', '&quot;'),
+ array('', '', '<', '&lt;'),
+ array('', '', '>', '&gt;'),
+ array('', '', '&', '&amp;'),
+ array('', '', '&amp;', '&amp;amp;'),
+ array('', '', '<a>', '&lt;a&gt;'),
+ array('', 'remove', '<a>', ''),
+ );
+ }
+
+ /**
+ * Test for rep_specialchars_output
+ * @dataProvider data_rep_specialchars_output
+ */
+ function test_rep_specialchars_output($type, $mode, $str, $res)
+ {
+ $result = rcube_utils::rep_specialchars_output(
+ $str, $type ? $type : 'html', $mode ? $mode : 'strict');
+
+ $this->assertEquals($result, $res);
+ }
+
+ /**
+ * rcube_utils::mod_css_styles()
+ */
+ function test_mod_css_styles()
+ {
+ $css = file_get_contents(TESTS_DIR . 'src/valid.css');
+ $mod = rcube_utils::mod_css_styles($css, 'rcmbody');
+
+ $this->assertRegExp('/#rcmbody\s+\{/', $mod, "Replace body style definition");
+ $this->assertRegExp('/#rcmbody h1\s\{/', $mod, "Prefix tag styles (single)");
+ $this->assertRegExp('/#rcmbody h1, #rcmbody h2, #rcmbody h3, #rcmbody textarea\s+\{/', $mod, "Prefix tag styles (multiple)");
+ $this->assertRegExp('/#rcmbody \.noscript\s+\{/', $mod, "Prefix class styles");
+ }
+
+ /**
+ * rcube_utils::mod_css_styles()
+ */
+ function test_mod_css_styles_xss()
+ {
+ $mod = rcube_utils::mod_css_styles("body.main2cols { background-image: url('../images/leftcol.png'); }", 'rcmbody');
+ $this->assertEquals("/* evil! */", $mod, "No url() values allowed");
+
+ $mod = rcube_utils::mod_css_styles("@import url('http://localhost/somestuff/css/master.css');", 'rcmbody');
+ $this->assertEquals("/* evil! */", $mod, "No import statements");
+
+ $mod = rcube_utils::mod_css_styles("left:expression(document.body.offsetWidth-20)", 'rcmbody');
+ $this->assertEquals("/* evil! */", $mod, "No expression properties");
+
+ $mod = rcube_utils::mod_css_styles("left:exp/* */ression( alert(&#039;xss3&#039;) )", 'rcmbody');
+ $this->assertEquals("/* evil! */", $mod, "Don't allow encoding quirks");
+
+ $mod = rcube_utils::mod_css_styles("background:\\0075\\0072\\006c( javascript:alert(&#039;xss&#039;) )", 'rcmbody');
+ $this->assertEquals("/* evil! */", $mod, "Don't allow encoding quirks (2)");
+ }
+}
diff --git a/tests/Framework/VCard.php b/tests/Framework/VCard.php
new file mode 100644
index 000000000..a830c2cbc
--- /dev/null
+++ b/tests/Framework/VCard.php
@@ -0,0 +1,59 @@
+<?php
+
+/**
+ * Unit tests for class rcube_vcard
+ *
+ * @package Tests
+ */
+class Framework_VCard extends PHPUnit_Framework_TestCase
+{
+
+ function _srcpath($fn)
+ {
+ return realpath(dirname(__FILE__) . '/../src/' . $fn);
+ }
+
+ function test_parse_one()
+ {
+ $vcard = new rcube_vcard(file_get_contents($this->_srcpath('apple.vcf')));
+
+ $this->assertTrue($vcard->business, "Identify as business record");
+ $this->assertEquals("Apple Computer AG", $vcard->displayname, "FN => displayname");
+ $this->assertEquals("", $vcard->firstname, "No person name set");
+ }
+
+ function test_parse_two()
+ {
+ $vcard = new rcube_vcard(file_get_contents($this->_srcpath('johndoe.vcf')), null);
+
+ $this->assertFalse($vcard->business, "Identify as private record");
+ $this->assertEquals("John Doë", $vcard->displayname, "Decode according to charset attribute");
+ $this->assertEquals("roundcube.net", $vcard->organization, "Test organization field");
+ $this->assertCount(2, $vcard->email, "List two e-mail addresses");
+ $this->assertEquals("roundcube@gmail.com", $vcard->email[0], "Use PREF e-mail as primary");
+ }
+
+ function test_import()
+ {
+ $input = file_get_contents($this->_srcpath('apple.vcf'));
+ $input .= file_get_contents($this->_srcpath('johndoe.vcf'));
+
+ $vcards = rcube_vcard::import($input);
+
+ $this->assertCount(2, $vcards, "Detected 2 vcards");
+ $this->assertEquals("Apple Computer AG", $vcards[0]->displayname, "FN => displayname");
+ $this->assertEquals("John Doë", $vcards[1]->displayname, "Displayname with correct charset");
+
+ // http://trac.roundcube.net/ticket/1485542
+ $vcards2 = rcube_vcard::import(file_get_contents($this->_srcpath('thebat.vcf')));
+ $this->assertEquals("Iksiñski", $vcards2[0]->surname, "Detect charset in encoded values");
+ }
+
+ function test_encodings()
+ {
+ $input = file_get_contents($this->_srcpath('utf-16_sample.vcf'));
+
+ $vcards = rcube_vcard::import($input);
+ $this->assertEquals("Ǽgean ĽdaMonté", $vcards[0]->displayname, "Decoded from UTF-16");
+ }
+}
diff --git a/tests/html_to_text.php b/tests/HtmlToText.php
index aabc1a800..b90c61adf 100644
--- a/tests/html_to_text.php
+++ b/tests/HtmlToText.php
@@ -5,18 +5,12 @@
*
* @package Tests
*/
-class rcube_test_html2text extends UnitTestCase
+class HtmlToText extends PHPUnit_Framework_TestCase
{
- function __construct()
+ function data_html2text()
{
- $this->UnitTestCase("HTML-to-Text conversion tests");
-
- }
-
- function test_html2text()
- {
- $data = array(
+ return array(
0 => array(
'title' => 'Test entry',
'in' => '',
@@ -48,14 +42,18 @@ class rcube_test_html2text extends UnitTestCase
'out' => 'Ś',
),
);
+ }
+ /**
+ * @dataProvider data_html2text
+ */
+ function test_html2text($title, $in, $out)
+ {
$ht = new html2text(null, false, false);
- foreach ($data as $idx => $item) {
- $ht->set_html($item['in']);
- $res = $ht->get_text();
- $this->assertEqual($item['out'], $res, $item['title'] . "($idx)");
- }
- }
+ $ht->set_html($in);
+ $res = $ht->get_text();
+ $this->assertEquals($out, $res, $title);
+ }
}
diff --git a/tests/MailFunc.php b/tests/MailFunc.php
new file mode 100644
index 000000000..967277c2a
--- /dev/null
+++ b/tests/MailFunc.php
@@ -0,0 +1,172 @@
+<?php
+
+/**
+ * Test class to test steps/mail/func.inc functions
+ *
+ * @package Tests
+ */
+class MailFunc extends PHPUnit_Framework_TestCase
+{
+
+ function setUp()
+ {
+ // simulate environment to successfully include func.inc
+ $GLOBALS['RCMAIL'] = $RCMAIL = rcmail::get_instance();
+ $GLOBALS['OUTPUT'] = $OUTPUT = $RCMAIL->load_gui();
+ $RCMAIL->action = 'autocomplete';
+ $RCMAIL->storage_init(false);
+
+ require_once INSTALL_PATH . 'program/steps/mail/func.inc';
+
+ $GLOBALS['EMAIL_ADDRESS_PATTERN'] = $EMAIL_ADDRESS_PATTERN;
+ }
+
+ /**
+ * Helper method to create a HTML message part object
+ */
+ function get_html_part($body)
+ {
+ $part = new rcube_message_part;
+ $part->ctype_primary = 'text';
+ $part->ctype_secondary = 'html';
+ $part->body = file_get_contents(TESTS_DIR . $body);
+ $part->replaces = array();
+ return $part;
+ }
+
+
+ /**
+ * Test sanitization of a "normal" html message
+ */
+ function test_html()
+ {
+ $part = $this->get_html_part('src/htmlbody.txt');
+ $part->replaces = array('ex1.jpg' => 'part_1.2.jpg', 'ex2.jpg' => 'part_1.2.jpg');
+
+ // render HTML in normal mode
+ $html = rcmail_html4inline(rcmail_print_body($part, array('safe' => false)), 'foo');
+
+ $this->assertRegExp('/src="'.$part->replaces['ex1.jpg'].'"/', $html, "Replace reference to inline image");
+ $this->assertRegExp('#background="./program/resources/blocked.gif"#', $html, "Replace external background image");
+ $this->assertNotRegExp('/ex3.jpg/', $html, "No references to external images");
+ $this->assertNotRegExp('/<meta [^>]+>/', $html, "No meta tags allowed");
+ //$this->assertNoPattern('/<style [^>]+>/', $html, "No style tags allowed");
+ $this->assertNotRegExp('/<form [^>]+>/', $html, "No form tags allowed");
+ $this->assertRegExp('/Subscription form/', $html, "Include <form> contents");
+ $this->assertRegExp('/<!-- link ignored -->/', $html, "No external links allowed");
+ $this->assertRegExp('/<a[^>]+ target="_blank">/', $html, "Set target to _blank");
+ $this->assertTrue($GLOBALS['REMOTE_OBJECTS'], "Remote object detected");
+
+ // render HTML in safe mode
+ $html2 = rcmail_html4inline(rcmail_print_body($part, array('safe' => true)), 'foo');
+
+ $this->assertRegExp('/<style [^>]+>/', $html2, "Allow styles in safe mode");
+ $this->assertRegExp('#src="http://evilsite.net/mailings/ex3.jpg"#', $html2, "Allow external images in HTML (safe mode)");
+ $this->assertRegExp("#url\('?http://evilsite.net/newsletter/image/bg/bg-64.jpg'?\)#", $html2, "Allow external images in CSS (safe mode)");
+ $css = '<link rel="stylesheet" .+_u=tmp-[a-z0-9]+\.css.+_action=modcss';
+ $this->assertRegExp('#'.$css.'#Ui', $html2, "Filter (anonymized) external styleseehts with utils/modcss.inc");
+ }
+
+ /**
+ * Test the elimination of some trivial XSS vulnerabilities
+ */
+ function test_html_xss()
+ {
+ $part = $this->get_html_part('src/htmlxss.txt');
+ $washed = rcmail_print_body($part, array('safe' => true));
+
+ $this->assertNotRegExp('/src="skins/', $washed, "Remove local references");
+ $this->assertNotRegExp('/\son[a-z]+/', $washed, "Remove on* attributes");
+
+ $html = rcmail_html4inline($washed, 'foo');
+ $this->assertNotRegExp('/onclick="return rcmail.command(\'compose\',\'xss@somehost.net\',this)"/', $html, "Clean mailto links");
+ $this->assertNotRegExp('/alert/', $html, "Remove alerts");
+ }
+
+ /**
+ * Test HTML sanitization to fix the CSS Expression Input Validation Vulnerability
+ * reported at http://www.securityfocus.com/bid/26800/
+ */
+ function test_html_xss2()
+ {
+ $part = $this->get_html_part('src/BID-26800.txt');
+ $washed = rcmail_html4inline(rcmail_print_body($part, array('safe' => true)), 'dabody', '', $attr, true);
+
+ $this->assertNotRegExp('/alert|expression|javascript|xss/', $washed, "Remove evil style blocks");
+ $this->assertNotRegExp('/font-style:italic/', $washed, "Allow valid styles");
+ }
+
+ /**
+ * Test washtml class on non-unicode characters (#1487813)
+ */
+ function test_washtml_utf8()
+ {
+ $part = $this->get_html_part('src/invalidchars.html');
+ $washed = rcmail_print_body($part);
+
+ $this->assertRegExp('/<p>символ<\/p>/', $washed, "Remove non-unicode characters from HTML message body");
+ }
+
+ /**
+ * Test links pattern replacements in plaintext messages
+ */
+ function test_plaintext()
+ {
+ $part = new rcube_message_part;
+ $part->ctype_primary = 'text';
+ $part->ctype_secondary = 'plain';
+ $part->body = quoted_printable_decode(file_get_contents(TESTS_DIR . 'src/plainbody.txt'));
+ $html = rcmail_print_body($part, array('safe' => true));
+
+ $this->assertRegExp('/<a href="mailto:nobody@roundcube.net" onclick="return rcmail.command\(\'compose\',\'nobody@roundcube.net\',this\)">nobody@roundcube.net<\/a>/', $html, "Mailto links with onclick");
+ $this->assertRegExp('#<a href="http://www.apple.com/legal/privacy" target="_blank">http://www.apple.com/legal/privacy</a>#', $html, "Links with target=_blank");
+ $this->assertRegExp('#\\[<a href="http://example.com/\\?tx\\[a\\]=5" target="_blank">http://example.com/\\?tx\\[a\\]=5</a>\\]#', $html, "Links with square brackets");
+ }
+
+ /**
+ * Test mailto links in html messages
+ */
+ function test_mailto()
+ {
+ $part = $this->get_html_part('src/mailto.txt');
+
+ // render HTML in normal mode
+ $html = rcmail_html4inline(rcmail_print_body($part, array('safe' => false)), 'foo');
+
+ $mailto = '<a href="mailto:me@me.com?subject=this is the subject&amp;body=this is the body"'
+ .' onclick="return rcmail.command(\'compose\',\'me@me.com?subject=this is the subject&amp;body=this is the body\',this)">e-mail</a>';
+
+ $this->assertRegExp('|'.preg_quote($mailto, '|').'|', $html, "Extended mailto links");
+ }
+
+ /**
+ * Test the elimination of HTML comments
+ */
+ function test_html_comments()
+ {
+ $part = $this->get_html_part('src/htmlcom.txt');
+ $washed = rcmail_print_body($part, array('safe' => true));
+
+ // #1487759
+ $this->assertRegExp('|<p>test1</p>|', $washed, "Buggy HTML comments");
+ // but conditional comments (<!--[if ...) should be removed
+ $this->assertNotRegExp('|<p>test2</p>|', $washed, "Conditional HTML comments");
+ }
+
+ /**
+ * Test URI base resolving in HTML messages
+ */
+ function test_resolve_base()
+ {
+ $html = file_get_contents(TESTS_DIR . 'src/htmlbase.txt');
+ $html = rcmail_resolve_base($html);
+
+ $this->assertRegExp('|src="http://alec\.pl/dir/img1\.gif"|', $html, "URI base resolving [1]");
+ $this->assertRegExp('|src="http://alec\.pl/dir/img2\.gif"|', $html, "URI base resolving [2]");
+ $this->assertRegExp('|src="http://alec\.pl/img3\.gif"|', $html, "URI base resolving [3]");
+
+ // base resolving exceptions
+ $this->assertRegExp('|src="cid:theCID"|', $html, "URI base resolving exception [1]");
+ $this->assertRegExp('|src="http://other\.domain\.tld/img3\.gif"|', $html, "URI base resolving exception [2]");
+ }
+}
diff --git a/tests/runtests.sh b/tests/bootstrap.php
index 9cfeb0a25..a9e25610c 100755..100644
--- a/tests/runtests.sh
+++ b/tests/bootstrap.php
@@ -1,54 +1,35 @@
-#!/usr/bin/env php
<?php
/*
+-----------------------------------------------------------------------+
- | tests/runtests.sh |
+ | tests/bootstrap.php |
| |
| This file is part of the Roundcube Webmail client |
- | Copyright (C) 2009, The Roundcube Dev Team |
+ | Copyright (C) 2009-2012, The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
- | Run-script for unit tests based on http://simpletest.org |
- | All .php files in this folder will be treated as tests |
+ | Environment initialization script for unit tests |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+ | Author: Aleksander Machniak <alec@alec.pl> |
+-----------------------------------------------------------------------+
*/
if (php_sapi_name() != 'cli')
die("Not in shell mode (php-cli)");
-if (!defined('SIMPLETEST')) define('SIMPLETEST', '/www/simpletest/');
if (!defined('INSTALL_PATH')) define('INSTALL_PATH', realpath(dirname(__FILE__) . '/..') . '/' );
define('TESTS_DIR', dirname(__FILE__) . '/');
-define('RCMAIL_CONFIG_DIR', TESTS_DIR . 'config');
-require_once(SIMPLETEST . 'unit_tester.php');
-require_once(SIMPLETEST . 'reporter.php');
-require_once(INSTALL_PATH . 'program/include/iniset.php');
-
-if (count($_SERVER['argv']) > 1) {
- $testfiles = array();
- for ($i=1; $i < count($_SERVER['argv']); $i++)
- $testfiles[] = realpath('./' . $_SERVER['argv'][$i]);
+if (@is_dir(TESTS_DIR . 'config')) {
+ define('RCMAIL_CONFIG_DIR', TESTS_DIR . 'config');
}
-else {
- $testfiles = glob(TESTS_DIR . '*.php');
-}
-
-$test = new TestSuite('Roundcube unit tests');
-$reporter = new TextReporter();
-foreach ($testfiles as $fn) {
- $test->addTestFile($fn);
-}
-
-$test->run($reporter);
+require_once(INSTALL_PATH . 'program/include/iniset.php');
-?>
+rcmail::get_instance()->config->set('devel_mode', false);
diff --git a/tests/maildecode.php b/tests/maildecode.php
deleted file mode 100644
index 4ac499360..000000000
--- a/tests/maildecode.php
+++ /dev/null
@@ -1,130 +0,0 @@
-<?php
-
-/**
- * Test class to test messages decoding functions
- *
- * @package Tests
- */
-class rcube_test_maildecode extends UnitTestCase
-{
- private $app;
-
- function __construct()
- {
- $this->UnitTestCase('Mail headers decoding tests');
- }
-
- /**
- * Test decoding of single e-mail address strings
- * Uses rcube_mime::decode_address_list()
- */
- function test_decode_single_address()
- {
- $headers = array(
- 0 => 'test@domain.tld',
- 1 => '<test@domain.tld>',
- 2 => 'Test <test@domain.tld>',
- 3 => 'Test Test <test@domain.tld>',
- 4 => 'Test Test<test@domain.tld>',
- 5 => '"Test Test" <test@domain.tld>',
- 6 => '"Test Test"<test@domain.tld>',
- 7 => '"Test \\" Test" <test@domain.tld>',
- 8 => '"Test<Test" <test@domain.tld>',
- 9 => '=?ISO-8859-1?B?VGVzdAo=?= <test@domain.tld>',
- 10 => '=?ISO-8859-1?B?VGVzdAo=?=<test@domain.tld>', // #1487068
- // comments in address (#1487673)
- 11 => 'Test (comment) <test@domain.tld>',
- 12 => '"Test" (comment) <test@domain.tld>',
- 13 => '"Test (comment)" (comment) <test@domain.tld>',
- 14 => '(comment) <test@domain.tld>',
- 15 => 'Test <test@(comment)domain.tld>',
- 16 => 'Test Test ((comment)) <test@domain.tld>',
- 17 => 'test@domain.tld (comment)',
- 18 => '"Test,Test" <test@domain.tld>',
- // 1487939
- 19 => 'Test <"test test"@domain.tld>',
- 20 => '<"test test"@domain.tld>',
- 21 => '"test test"@domain.tld',
- );
-
- $results = array(
- 0 => array(1, '', 'test@domain.tld'),
- 1 => array(1, '', 'test@domain.tld'),
- 2 => array(1, 'Test', 'test@domain.tld'),
- 3 => array(1, 'Test Test', 'test@domain.tld'),
- 4 => array(1, 'Test Test', 'test@domain.tld'),
- 5 => array(1, 'Test Test', 'test@domain.tld'),
- 6 => array(1, 'Test Test', 'test@domain.tld'),
- 7 => array(1, 'Test " Test', 'test@domain.tld'),
- 8 => array(1, 'Test<Test', 'test@domain.tld'),
- 9 => array(1, 'Test', 'test@domain.tld'),
- 10 => array(1, 'Test', 'test@domain.tld'),
- 11 => array(1, 'Test', 'test@domain.tld'),
- 12 => array(1, 'Test', 'test@domain.tld'),
- 13 => array(1, 'Test (comment)', 'test@domain.tld'),
- 14 => array(1, '', 'test@domain.tld'),
- 15 => array(1, 'Test', 'test@domain.tld'),
- 16 => array(1, 'Test Test', 'test@domain.tld'),
- 17 => array(1, '', 'test@domain.tld'),
- 18 => array(1, 'Test,Test', 'test@domain.tld'),
- 19 => array(1, 'Test', '"test test"@domain.tld'),
- 20 => array(1, '', '"test test"@domain.tld'),
- 21 => array(1, '', '"test test"@domain.tld'),
- );
-
- foreach ($headers as $idx => $header) {
- $res = rcube_mime::decode_address_list($header);
-
- $this->assertEqual($results[$idx][0], count($res), "Rows number in result for header: " . $header);
- $this->assertEqual($results[$idx][1], $res[1]['name'], "Name part decoding for header: " . $header);
- $this->assertEqual($results[$idx][2], $res[1]['mailto'], "Email part decoding for header: " . $header);
- }
- }
-
- /**
- * Test decoding of header values
- * Uses rcube_mime::decode_mime_string()
- */
- function test_header_decode_qp()
- {
- $test = array(
- // #1488232: invalid character "?"
- 'quoted-printable (1)' => array(
- 'in' => '=?utf-8?Q?Certifica=C3=A7=C3=A3??=',
- 'out' => 'Certifica=C3=A7=C3=A3?',
- ),
- 'quoted-printable (2)' => array(
- 'in' => '=?utf-8?Q?Certifica=?= =?utf-8?Q?C3=A7=C3=A3?=',
- 'out' => 'Certifica=C3=A7=C3=A3',
- ),
- 'quoted-printable (3)' => array(
- 'in' => '=?utf-8?Q??= =?utf-8?Q??=',
- 'out' => '',
- ),
- 'quoted-printable (4)' => array(
- 'in' => '=?utf-8?Q??= a =?utf-8?Q??=',
- 'out' => ' a ',
- ),
- 'quoted-printable (5)' => array(
- 'in' => '=?utf-8?Q?a?= =?utf-8?Q?b?=',
- 'out' => 'ab',
- ),
- 'quoted-printable (6)' => array(
- 'in' => '=?utf-8?Q? ?= =?utf-8?Q?a?=',
- 'out' => ' a',
- ),
- 'quoted-printable (7)' => array(
- 'in' => '=?utf-8?Q?___?= =?utf-8?Q?a?=',
- 'out' => ' a',
- ),
- );
-
- foreach ($test as $idx => $item) {
- $res = rcube_mime::decode_mime_string($item['in'], 'UTF-8');
- $res = quoted_printable_encode($res);
-
- $this->assertEqual($item['out'], $res, "Header decoding for: " . $idx);
- }
-
- }
-}
diff --git a/tests/mailfunc.php b/tests/mailfunc.php
deleted file mode 100644
index 493ce946e..000000000
--- a/tests/mailfunc.php
+++ /dev/null
@@ -1,173 +0,0 @@
-<?php
-
-/**
- * Test class to test steps/mail/func.inc functions
- *
- * @package Tests
- */
-class rcube_test_mailfunc extends UnitTestCase
-{
-
- function __construct()
- {
- $this->UnitTestCase('Mail body rendering tests');
-
- // simulate environment to successfully include func.inc
- $GLOBALS['RCMAIL'] = $RCMAIL = rcmail::get_instance();
- $GLOBALS['OUTPUT'] = $OUTPUT = $RCMAIL->load_gui();
- $RCMAIL->action = 'autocomplete';
- $RCMAIL->storage_init(false);
-
- require_once INSTALL_PATH . 'program/steps/mail/func.inc';
-
- $GLOBALS['EMAIL_ADDRESS_PATTERN'] = $EMAIL_ADDRESS_PATTERN;
- }
-
- /**
- * Helper method to create a HTML message part object
- */
- function get_html_part($body)
- {
- $part = new rcube_message_part;
- $part->ctype_primary = 'text';
- $part->ctype_secondary = 'html';
- $part->body = file_get_contents(TESTS_DIR . $body);
- $part->replaces = array();
- return $part;
- }
-
- /**
- * Test sanitization of a "normal" html message
- */
- function test_html()
- {
- $part = $this->get_html_part('src/htmlbody.txt');
- $part->replaces = array('ex1.jpg' => 'part_1.2.jpg', 'ex2.jpg' => 'part_1.2.jpg');
-
- // render HTML in normal mode
- $html = rcmail_html4inline(rcmail_print_body($part, array('safe' => false)), 'foo');
-
- $this->assertPattern('/src="'.$part->replaces['ex1.jpg'].'"/', $html, "Replace reference to inline image");
- $this->assertPattern('#background="./program/resources/blocked.gif"#', $html, "Replace external background image");
- $this->assertNoPattern('/ex3.jpg/', $html, "No references to external images");
- $this->assertNoPattern('/<meta [^>]+>/', $html, "No meta tags allowed");
- //$this->assertNoPattern('/<style [^>]+>/', $html, "No style tags allowed");
- $this->assertNoPattern('/<form [^>]+>/', $html, "No form tags allowed");
- $this->assertPattern('/Subscription form/', $html, "Include <form> contents");
- $this->assertPattern('/<!-- link ignored -->/', $html, "No external links allowed");
- $this->assertPattern('/<a[^>]+ target="_blank">/', $html, "Set target to _blank");
- $this->assertTrue($GLOBALS['REMOTE_OBJECTS'], "Remote object detected");
-
- // render HTML in safe mode
- $html2 = rcmail_html4inline(rcmail_print_body($part, array('safe' => true)), 'foo');
-
- $this->assertPattern('/<style [^>]+>/', $html2, "Allow styles in safe mode");
- $this->assertPattern('#src="http://evilsite.net/mailings/ex3.jpg"#', $html2, "Allow external images in HTML (safe mode)");
- $this->assertPattern("#url\('?http://evilsite.net/newsletter/image/bg/bg-64.jpg'?\)#", $html2, "Allow external images in CSS (safe mode)");
- $css = '<link rel="stylesheet" .+_u=tmp-[a-z0-9]+\.css.+_action=modcss';
- $this->assertPattern('#'.$css.'#Ui', $html2, "Filter (anonymized) external styleseehts with utils/modcss.inc");
- }
-
- /**
- * Test the elimination of some trivial XSS vulnerabilities
- */
- function test_html_xss()
- {
- $part = $this->get_html_part('src/htmlxss.txt');
- $washed = rcmail_print_body($part, array('safe' => true));
-
- $this->assertNoPattern('/src="skins/', $washed, "Remove local references");
- $this->assertNoPattern('/\son[a-z]+/', $washed, "Remove on* attributes");
-
- $html = rcmail_html4inline($washed, 'foo');
- $this->assertNoPattern('/onclick="return rcmail.command(\'compose\',\'xss@somehost.net\',this)"/', $html, "Clean mailto links");
- $this->assertNoPattern('/alert/', $html, "Remove alerts");
- }
-
- /**
- * Test HTML sanitization to fix the CSS Expression Input Validation Vulnerability
- * reported at http://www.securityfocus.com/bid/26800/
- */
- function test_html_xss2()
- {
- $part = $this->get_html_part('src/BID-26800.txt');
- $washed = rcmail_html4inline(rcmail_print_body($part, array('safe' => true)), 'dabody', '', $attr, true);
-
- $this->assertNoPattern('/alert|expression|javascript|xss/', $washed, "Remove evil style blocks");
- $this->assertNoPattern('/font-style:italic/', $washed, "Allow valid styles");
- }
-
- /**
- * Test washtml class on non-unicode characters (#1487813)
- */
- function test_washtml_utf8()
- {
- $part = $this->get_html_part('src/invalidchars.html');
- $washed = rcmail_print_body($part);
-
- $this->assertPattern('/<p>символ<\/p>/', $washed, "Remove non-unicode characters from HTML message body");
- }
-
- /**
- * Test links pattern replacements in plaintext messages
- */
- function test_plaintext()
- {
- $part = new rcube_message_part;
- $part->ctype_primary = 'text';
- $part->ctype_secondary = 'plain';
- $part->body = quoted_printable_decode(file_get_contents(TESTS_DIR . 'src/plainbody.txt'));
- $html = rcmail_print_body($part, array('safe' => true));
-
- $this->assertPattern('/<a href="mailto:nobody@roundcube.net" onclick="return rcmail.command\(\'compose\',\'nobody@roundcube.net\',this\)">nobody@roundcube.net<\/a>/', $html, "Mailto links with onclick");
- $this->assertPattern('#<a href="http://www.apple.com/legal/privacy" target="_blank">http://www.apple.com/legal/privacy</a>#', $html, "Links with target=_blank");
- $this->assertPattern('#\\[<a href="http://example.com/\\?tx\\[a\\]=5" target="_blank">http://example.com/\\?tx\\[a\\]=5</a>\\]#', $html, "Links with square brackets");
- }
-
- /**
- * Test mailto links in html messages
- */
- function test_mailto()
- {
- $part = $this->get_html_part('src/mailto.txt');
-
- // render HTML in normal mode
- $html = rcmail_html4inline(rcmail_print_body($part, array('safe' => false)), 'foo');
-
- $mailto = '<a href="mailto:me@me.com?subject=this is the subject&amp;body=this is the body"'
- .' onclick="return rcmail.command(\'compose\',\'me@me.com?subject=this is the subject&amp;body=this is the body\',this)">e-mail</a>';
-
- $this->assertPattern('|'.preg_quote($mailto, '|').'|', $html, "Extended mailto links");
- }
-
- /**
- * Test the elimination of HTML comments
- */
- function test_html_comments()
- {
- $part = $this->get_html_part('src/htmlcom.txt');
- $washed = rcmail_print_body($part, array('safe' => true));
-
- // #1487759
- $this->assertPattern('|<p>test1</p>|', $washed, "Buggy HTML comments");
- // but conditional comments (<!--[if ...) should be removed
- $this->assertNoPattern('|<p>test2</p>|', $washed, "Conditional HTML comments");
- }
-
- /**
- * Test URI base resolving in HTML messages
- */
- function test_resolve_base()
- {
- $html = file_get_contents(TESTS_DIR . 'src/htmlbase.txt');
- $html = rcmail_resolve_base($html);
-
- $this->assertPattern('|src="http://alec\.pl/dir/img1\.gif"|', $html, "URI base resolving [1]");
- $this->assertPattern('|src="http://alec\.pl/dir/img2\.gif"|', $html, "URI base resolving [2]");
- $this->assertPattern('|src="http://alec\.pl/img3\.gif"|', $html, "URI base resolving [3]");
-
- // base resolving exceptions
- $this->assertPattern('|src="cid:theCID"|', $html, "URI base resolving exception [1]");
- $this->assertPattern('|src="http://other\.domain\.tld/img3\.gif"|', $html, "URI base resolving exception [2]");
- }
-}
diff --git a/tests/modcss.php b/tests/modcss.php
deleted file mode 100644
index 945cac318..000000000
--- a/tests/modcss.php
+++ /dev/null
@@ -1,45 +0,0 @@
-<?php
-
-/**
- * Test class to test rcmail_mod_css_styles and XSS vulnerabilites
- *
- * @package Tests
- */
-class rcube_test_modcss extends UnitTestCase
-{
-
- function __construct()
- {
- $this->UnitTestCase('CSS modification and vulnerability tests');
- }
-
- function test_modcss()
- {
- $css = file_get_contents(TESTS_DIR . 'src/valid.css');
- $mod = rcmail_mod_css_styles($css, 'rcmbody');
-
- $this->assertPattern('/#rcmbody\s+\{/', $mod, "Replace body style definition");
- $this->assertPattern('/#rcmbody h1\s\{/', $mod, "Prefix tag styles (single)");
- $this->assertPattern('/#rcmbody h1, #rcmbody h2, #rcmbody h3, #rcmbody textarea\s+\{/', $mod, "Prefix tag styles (multiple)");
- $this->assertPattern('/#rcmbody \.noscript\s+\{/', $mod, "Prefix class styles");
- }
-
- function test_xss()
- {
- $mod = rcmail_mod_css_styles("body.main2cols { background-image: url('../images/leftcol.png'); }", 'rcmbody');
- $this->assertEqual("/* evil! */", $mod, "No url() values allowed");
-
- $mod = rcmail_mod_css_styles("@import url('http://localhost/somestuff/css/master.css');", 'rcmbody');
- $this->assertEqual("/* evil! */", $mod, "No import statements");
-
- $mod = rcmail_mod_css_styles("left:expression(document.body.offsetWidth-20)", 'rcmbody');
- $this->assertEqual("/* evil! */", $mod, "No expression properties");
-
- $mod = rcmail_mod_css_styles("left:exp/* */ression( alert(&#039;xss3&#039;) )", 'rcmbody');
- $this->assertEqual("/* evil! */", $mod, "Don't allow encoding quirks");
-
- $mod = rcmail_mod_css_styles("background:\\0075\\0072\\006c( javascript:alert(&#039;xss&#039;) )", 'rcmbody');
- $this->assertEqual("/* evil! */", $mod, "Don't allow encoding quirks (2)");
- }
-
-}
diff --git a/tests/phpunit.xml b/tests/phpunit.xml
new file mode 100644
index 000000000..8b3883223
--- /dev/null
+++ b/tests/phpunit.xml
@@ -0,0 +1,37 @@
+<phpunit backupGlobals="false"
+ bootstrap="bootstrap.php"
+ colors="true">
+ <testsuites>
+ <testsuite name="All Tests">
+ <file>Framework/BaseReplacer.php</file>
+ <file>Framework/Browser.php</file>
+ <file>Framework/Cache.php</file>
+ <file>Framework/Charset.php</file>
+ <file>Framework/ContentFilter.php</file>
+ <file>Framework/Html.php</file>
+ <file>Framework/Imap.php</file>
+ <file>Framework/ImapGeneric.php</file>
+ <file>Framework/Image.php</file>
+ <file>Framework/MessageHeader.php</file>
+ <file>Framework/MessagePart.php</file>
+ <file>Framework/Mime.php</file>
+ <file>Framework/Rcube.php</file>
+ <file>Framework/ResultIndex.php</file>
+ <file>Framework/ResultSet.php</file>
+ <file>Framework/ResultThread.php</file>
+ <file>Framework/Shared.php</file>
+ <file>Framework/Smtp.php</file>
+ <file>Framework/Spellchecker.php</file>
+ <file>Framework/StringReplacer.php</file>
+ <file>Framework/User.php</file>
+ <file>Framework/Utils.php</file>
+ <file>Framework/VCard.php</file>
+ <file>HtmlToText.php</file>
+ <file>MailFunc.php</file>
+ </testsuite>
+ <testsuite name="managesieve">
+ <file>./../plugins/managesieve/tests/Parser.php</file>
+ <file>./../plugins/managesieve/tests/Tokenizer.php</file>
+ </testsuite>
+ </testsuites>
+</phpunit>
diff --git a/tests/vcards.php b/tests/vcards.php
deleted file mode 100644
index 22f7cdd33..000000000
--- a/tests/vcards.php
+++ /dev/null
@@ -1,65 +0,0 @@
-<?php
-
-/**
- * Unit tests for class rcube_vcard
- *
- * @package Tests
- */
-class rcube_test_vcards extends UnitTestCase
-{
-
- function __construct()
- {
- $this->UnitTestCase('Vcard encoding/decoding tests');
- }
-
- function _srcpath($fn)
- {
- return realpath(dirname(__FILE__) . '/src/' . $fn);
- }
-
- function test_parse_one()
- {
- $vcard = new rcube_vcard(file_get_contents($this->_srcpath('apple.vcf')));
-
- $this->assertEqual(true, $vcard->business, "Identify as business record");
- $this->assertEqual("Apple Computer AG", $vcard->displayname, "FN => displayname");
- $this->assertEqual("", $vcard->firstname, "No person name set");
- }
-
- function test_parse_two()
- {
- $vcard = new rcube_vcard(file_get_contents($this->_srcpath('johndoe.vcf')), null);
-
- $this->assertEqual(false, $vcard->business, "Identify as private record");
- $this->assertEqual("John Doë", $vcard->displayname, "Decode according to charset attribute");
- $this->assertEqual("roundcube.net", $vcard->organization, "Test organization field");
- $this->assertEqual(2, count($vcard->email), "List two e-mail addresses");
- $this->assertEqual("roundcube@gmail.com", $vcard->email[0], "Use PREF e-mail as primary");
- }
-
- function test_import()
- {
- $input = file_get_contents($this->_srcpath('apple.vcf'));
- $input .= file_get_contents($this->_srcpath('johndoe.vcf'));
-
- $vcards = rcube_vcard::import($input);
-
- $this->assertEqual(2, count($vcards), "Detected 2 vcards");
- $this->assertEqual("Apple Computer AG", $vcards[0]->displayname, "FN => displayname");
- $this->assertEqual("John Doë", $vcards[1]->displayname, "Displayname with correct charset");
-
- // http://trac.roundcube.net/ticket/1485542
- $vcards2 = rcube_vcard::import(file_get_contents($this->_srcpath('thebat.vcf')));
- $this->assertEqual("Iksiñski", $vcards2[0]->surname, "Detect charset in encoded values");
- }
-
- function test_encodings()
- {
- $input = file_get_contents($this->_srcpath('utf-16_sample.vcf'));
-
- $vcards = rcube_vcard::import($input);
- $this->assertEqual("Ǽgean ĽdaMonté", $vcards[0]->displayname, "Decoded from UTF-16");
- }
-
-}