From 6ab9369eb194e4dde0cc830a84466dd240e95b23 Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Wed, 15 Aug 2012 16:21:34 +0200 Subject: Fix lower-casing email address on replies (#1488598) --- program/include/rcube_shared.inc | 23 +++++++++++++++++++++++ program/steps/mail/compose.inc | 9 +++++---- 2 files changed, 28 insertions(+), 4 deletions(-) (limited to 'program') diff --git a/program/include/rcube_shared.inc b/program/include/rcube_shared.inc index 85f278432..5b839d8d2 100644 --- a/program/include/rcube_shared.inc +++ b/program/include/rcube_shared.inc @@ -305,6 +305,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 */ diff --git a/program/steps/mail/compose.inc b/program/steps/mail/compose.inc index 1a1d244e1..56f4a052b 100644 --- a/program/steps/mail/compose.inc +++ b/program/steps/mail/compose.inc @@ -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']; } } @@ -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']) -- cgit v1.2.3 From 287eff030a7c6437495dc15badb125640cc4c3d3 Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Thu, 16 Aug 2012 09:36:49 +0200 Subject: Make $inline_parts property publicly available --- program/include/rcube_message.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'program') diff --git a/program/include/rcube_message.php b/program/include/rcube_message.php index 9d36acf38..f550b574e 100644 --- a/program/include/rcube_message.php +++ b/program/include/rcube_message.php @@ -50,13 +50,13 @@ class rcube_message */ private $mime; private $opt = array(); - private $inline_parts = array(); private $parse_alternative = false; public $uid = null; public $headers; public $parts = array(); public $mime_parts = array(); + public $inline_parts = array(); public $attachments = array(); public $subject = ''; public $sender = null; -- cgit v1.2.3 From fb001f851f60e99b4ba9d2f837a76a46dfd3fd5f Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Thu, 16 Aug 2012 12:00:35 +0200 Subject: Force at least one subtype of address to be specified. Fixes issue where contact address wasn't displayed at all. --- program/include/rcube_ldap.php | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'program') 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); -- cgit v1.2.3 From 60082ad665cfcb3b517f765a5192e99ddf1a730c Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Fri, 17 Aug 2012 10:51:54 +0200 Subject: Fix PHP Warning: strlen() expects parameter 1 to be string, object given --- program/steps/addressbook/export.inc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'program') 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)); } } -- cgit v1.2.3 From c72325faed3d244170650a1bf62ddca6eb1b5fa9 Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Fri, 17 Aug 2012 15:00:12 +0200 Subject: Fix bug where domain name was converted to lower-case even with login_lc=false (#1488593) --- CHANGELOG | 1 + config/main.inc.php.dist | 7 ++++--- program/include/rcmail.php | 21 ++++++++++++--------- 3 files changed, 17 insertions(+), 12 deletions(-) (limited to 'program') diff --git a/CHANGELOG b/CHANGELOG index 3edbdd494..b1f9ce1cf 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ CHANGELOG Roundcube Webmail =========================== +- Fix bug where domain name was converted to lower-case even with login_lc=false (#1488593) - Rewritten test scripts for PHPUnit - Fix lower-casing email address on replies (#1488598) - Fix line separator in exported messages (#1488603) diff --git a/config/main.inc.php.dist b/config/main.inc.php.dist index 58f9ca865..1e9c1fdcd 100644 --- a/config/main.inc.php.dist +++ b/config/main.inc.php.dist @@ -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; diff --git a/program/include/rcmail.php b/program/include/rcmail.php index a6b0bcd57..d866919d6 100644 --- a/program/include/rcmail.php +++ b/program/include/rcmail.php @@ -453,7 +453,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 +470,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(); -- cgit v1.2.3 From c83b83eeae9806cb60ea3f41f2cff055b0c6ed7e Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Fri, 17 Aug 2012 15:37:04 +0200 Subject: Fix domain part check in email address validation function. Added test cases. --- program/include/rcube_utils.php | 5 +++ tests/Utils.php | 74 +++++++++++++++++++++++++++++++++++++++++ tests/phpunit.xml | 1 + 3 files changed, 80 insertions(+) create mode 100644 tests/Utils.php (limited to 'program') diff --git a/program/include/rcube_utils.php b/program/include/rcube_utils.php index d1a8315ec..9f18b79c4 100644 --- a/program/include/rcube_utils.php +++ b/program/include/rcube_utils.php @@ -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')) { diff --git a/tests/Utils.php b/tests/Utils.php new file mode 100644 index 000000000..648b39989 --- /dev/null +++ b/tests/Utils.php @@ -0,0 +1,74 @@ +', '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@111.222.333.44444', 'Invalid IP format'), + 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); + } + +} diff --git a/tests/phpunit.xml b/tests/phpunit.xml index 4a3b883cf..cfd066e29 100644 --- a/tests/phpunit.xml +++ b/tests/phpunit.xml @@ -7,6 +7,7 @@ MailDecode.php MailFunc.php ModCss.php + Utils.php VCards.php -- cgit v1.2.3 From 31541824ab156f225ede89028d3988688ce42a6e Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Fri, 17 Aug 2012 20:25:40 +0200 Subject: CS fixes, mostly indentation --- program/include/html.php | 2 +- program/include/rcube.php | 1434 +++++++++++++++++++------------------ program/include/rcube_browser.php | 2 - 3 files changed, 740 insertions(+), 698 deletions(-) (limited to 'program') 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/rcube.php b/program/include/rcube.php index 494b5c3dd..84014ef5c 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']); + } } - } /** @@ -492,433 +500,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; + } + + // 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); + } + } - // 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; + // 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_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 -- cgit v1.2.3 From b2631bffd98a51fc8b61aa8452fbaf3e95bcde24 Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Sat, 18 Aug 2012 17:57:08 +0200 Subject: More default->larry fixes --- plugins/hide_blockquote/hide_blockquote.php | 6 +---- plugins/hide_blockquote/skins/default/style.css | 31 ------------------------- plugins/hide_blockquote/skins/larry/style.css | 31 +++++++++++++++++++++++++ program/include/rcube_plugin.php | 2 +- 4 files changed, 33 insertions(+), 37 deletions(-) delete mode 100644 plugins/hide_blockquote/skins/default/style.css create mode 100644 plugins/hide_blockquote/skins/larry/style.css (limited to 'program') 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/default/style.css deleted file mode 100644 index 198172f92..000000000 --- a/plugins/hide_blockquote/skins/default/style.css +++ /dev/null @@ -1,31 +0,0 @@ -span.blockquote-link { - font-family: "Lucida Grande", Verdana, Arial, Helvetica, sans-serif; - top: 0; - cursor: pointer; - right: 5px; - height: 14px; - min-width: 40px; - padding: 0 8px; - font-size: 10px; - font-weight: bold; - color: #a8a8a8; - line-height: 14px; - text-decoration: none; - text-shadow: 0px 1px 1px #fff; - text-align: center; - border: 1px solid #e8e8e8; - border-top: none; - border-bottom-right-radius: 6px; - border-bottom-left-radius: 6px; - background: #f8f8f8; - background: -moz-linear-gradient(top, #f8f8f8 0%, #e8e8e8 100%); - background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#f8f8f8), color-stop(100%,#e8e8e8)); - background: -o-linear-gradient(top, #f8f8f8 0%, #e8e8e8 100%); - background: -ms-linear-gradient(top, #f8f8f8 0%, #e8e8e8 100%); - background: linear-gradient(top, #f8f8f8 0%, #e8e8e8 100%); -} - -blockquote.blockquote-header { - text-overflow: ellipsis !important; - padding-right: 60px !important; -} \ No newline at end of file diff --git a/plugins/hide_blockquote/skins/larry/style.css b/plugins/hide_blockquote/skins/larry/style.css new file mode 100644 index 000000000..198172f92 --- /dev/null +++ b/plugins/hide_blockquote/skins/larry/style.css @@ -0,0 +1,31 @@ +span.blockquote-link { + font-family: "Lucida Grande", Verdana, Arial, Helvetica, sans-serif; + top: 0; + cursor: pointer; + right: 5px; + height: 14px; + min-width: 40px; + padding: 0 8px; + font-size: 10px; + font-weight: bold; + color: #a8a8a8; + line-height: 14px; + text-decoration: none; + text-shadow: 0px 1px 1px #fff; + text-align: center; + border: 1px solid #e8e8e8; + border-top: none; + border-bottom-right-radius: 6px; + border-bottom-left-radius: 6px; + background: #f8f8f8; + background: -moz-linear-gradient(top, #f8f8f8 0%, #e8e8e8 100%); + background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#f8f8f8), color-stop(100%,#e8e8e8)); + background: -o-linear-gradient(top, #f8f8f8 0%, #e8e8e8 100%); + background: -ms-linear-gradient(top, #f8f8f8 0%, #e8e8e8 100%); + background: linear-gradient(top, #f8f8f8 0%, #e8e8e8 100%); +} + +blockquote.blockquote-header { + text-overflow: ellipsis !important; + padding-right: 60px !important; +} \ No newline at end of file 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; -- cgit v1.2.3 From c8f35a17e645ec7328557846b976e38432c9a062 Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Mon, 20 Aug 2012 08:52:00 +0200 Subject: Added "Undeleted" option to messages list filter --- CHANGELOG | 1 + program/localization/en_US/labels.inc | 5 +---- program/localization/pl_PL/labels.inc | 1 + program/steps/mail/func.inc | 4 +++- 4 files changed, 6 insertions(+), 5 deletions(-) (limited to 'program') diff --git a/CHANGELOG b/CHANGELOG index 65b2dcbc8..6ea101e8f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ CHANGELOG Roundcube Webmail =========================== +- 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 diff --git a/program/localization/en_US/labels.inc b/program/localization/en_US/labels.inc index 94bae1974..6085b3898 100644 --- a/program/localization/en_US/labels.inc +++ b/program/localization/en_US/labels.inc @@ -1,7 +1,6 @@ | +-----------------------------------------------------------------------+ - - @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'; 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/mail/func.inc b/program/steps/mail/func.inc index 3d65eacb1..7f0b4db5b 100644 --- a/program/steps/mail/func.inc +++ b/program/steps/mail/func.inc @@ -1712,8 +1712,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'); -- cgit v1.2.3 From ae7027de029e28fdd3894efe919b6171b2b11eab Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Mon, 20 Aug 2012 09:32:25 +0200 Subject: Added session_path config option and unified cookies settings in javascript --- CHANGELOG | 1 + config/main.inc.php.dist | 7 +++++-- program/include/rcube.php | 5 +++++ program/include/rcube_output_html.php | 5 +++++ program/js/app.js | 8 ++++++++ program/js/common.js | 1 + program/js/googiespell.js | 4 ++-- skins/classic/splitter.js | 4 ++-- skins/larry/ui.js | 6 +++--- 9 files changed, 32 insertions(+), 9 deletions(-) (limited to 'program') diff --git a/CHANGELOG b/CHANGELOG index 6ea101e8f..4d18f64b7 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ CHANGELOG Roundcube Webmail =========================== +- 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) diff --git a/config/main.inc.php.dist b/config/main.inc.php.dist index 1e9c1fdcd..8d615f3b0 100644 --- a/config/main.inc.php.dist +++ b/config/main.inc.php.dist @@ -241,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 diff --git a/program/include/rcube.php b/program/include/rcube.php index 84014ef5c..0e40b3c6b 100644 --- a/program/include/rcube.php +++ b/program/include/rcube.php @@ -405,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); diff --git a/program/include/rcube_output_html.php b/program/include/rcube_output_html.php index 0a8f0e364..a071ee354 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); diff --git a/program/js/app.js b/program/js/app.js index e8bb6c1a7..9ca16b39c 100644 --- a/program/js/app.js +++ b/program/js/app.js @@ -6585,6 +6585,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 +6621,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..a08387ecb 100644 --- a/program/js/common.js +++ b/program/js/common.js @@ -635,6 +635,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/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/larry/ui.js b/skins/larry/ui.js index b6056b6ee..ca1680759 100644 --- a/skins/larry/ui.js +++ b/skins/larry/ui.js @@ -461,7 +461,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(); @@ -974,7 +974,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 +1135,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 -- cgit v1.2.3 From fe2f0be4352ba0bc213a2c6d4dfcbbddea1a551c Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Tue, 21 Aug 2012 10:50:14 +0200 Subject: Fix possible PHP warning, read default_folders using config->get() to support deprecated option name (default_imap_folders) --- program/steps/settings/folders.inc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'program') diff --git a/program/steps/settings/folders.inc b/program/steps/settings/folders.inc index 2691a6e26..1031fc136 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); } -- cgit v1.2.3 From 9cb76ad3c2ebcf6f75053de2e686390470dce7ba Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Tue, 21 Aug 2012 11:22:23 +0200 Subject: Fix so subscribed non-existing/non-accessible shared folder can be unsubscribed --- CHANGELOG | 1 + program/steps/settings/folders.inc | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) (limited to 'program') diff --git a/CHANGELOG b/CHANGELOG index 65b2dcbc8..6a7b2143b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ CHANGELOG Roundcube Webmail =========================== +- Fix so subscribed non-existing/non-accessible shared folder can be unsubscribed - Rewritten test scripts for PHPUnit - Add new DB abstraction layer based on PHP PDO, supporting SQLite3 (#1488332) - Removed PEAR::MDB2 package diff --git a/program/steps/settings/folders.inc b/program/steps/settings/folders.inc index 1031fc136..2c2cbdcc2 100644 --- a/program/steps/settings/folders.inc +++ b/program/steps/settings/folders.inc @@ -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) { -- cgit v1.2.3 From 982353884898faf5a4cf0ee593b7078cb736c2d1 Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Tue, 21 Aug 2012 14:23:07 +0200 Subject: Improved internal cache handling --- program/include/rcube_imap_cache.php | 46 +++++++++++++++++++++++------------- 1 file changed, 30 insertions(+), 16 deletions(-) (limited to 'program') diff --git a/program/include/rcube_imap_cache.php b/program/include/rcube_imap_cache.php index a061a1f6e..4d6d5e180 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( @@ -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; } } -- cgit v1.2.3 From ff805351cae78676927f76a1960b8e688558fe95 Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Thu, 23 Aug 2012 20:08:37 +0200 Subject: Fix email address validation for addresses with IP address in domain part --- CHANGELOG | 1 + program/js/common.js | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) (limited to 'program') diff --git a/CHANGELOG b/CHANGELOG index 23fb7b720..d529169e4 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ CHANGELOG Roundcube Webmail =========================== +- 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 diff --git a/program/js/common.js b/program/js/common.js index a08387ecb..2d8d9e176 100644 --- a/program/js/common.js +++ b/program/js/common.js @@ -494,12 +494,13 @@ 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', + ip_addr = '\\[*(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}\\]*', // 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 +528,6 @@ function rcube_check_email(input, inline) return false; }; - // recursively copy an object function rcube_clone_object(obj) { -- cgit v1.2.3 From da28121dcd160045c468b7028ee835b24f0cb965 Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Fri, 24 Aug 2012 10:10:25 +0200 Subject: Improved email address validation with IPv6 support --- program/include/rcube_utils.php | 52 ++++++++++++++++++++++++++++++++++++++--- program/js/common.js | 4 +++- tests/Utils.php | 17 +++++++++++--- 3 files changed, 66 insertions(+), 7 deletions(-) (limited to 'program') diff --git a/program/include/rcube_utils.php b/program/include/rcube_utils.php index 9f18b79c4..defb2aed1 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::ip_check(preg_replace('/^IPv6:/i', '', $matches[1])); // valid IPv4 or IPv6 address } else { // If not an IP address @@ -146,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 ip_check($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 * diff --git a/program/js/common.js b/program/js/common.js index 2d8d9e176..f9e945c05 100644 --- a/program/js/common.js +++ b/program/js/common.js @@ -494,7 +494,9 @@ 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', - ip_addr = '\\[*(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}\\]*', + 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', diff --git a/tests/Utils.php b/tests/Utils.php index 648b39989..ad0aa1dde 100644 --- a/tests/Utils.php +++ b/tests/Utils.php @@ -18,8 +18,10 @@ class Utils extends PHPUnit_Framework_TestCase 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', 'Domain is valid IP address'), - array('email@[123.123.123.123]', 'Square bracket around IP address is considered valid'), + 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'), @@ -50,7 +52,16 @@ class Utils extends PHPUnit_Framework_TestCase 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@111.222.333.44444', 'Invalid IP format'), + 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'), ); } -- cgit v1.2.3 From 553225e1be27f2c58c0995decc10651566add78f Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Fri, 24 Aug 2012 10:31:58 +0200 Subject: Fix setting locales under Solaris - use additional .UTF-8 suffix (#1488628) --- CHANGELOG | 1 + program/include/rcmail.php | 10 ++++++---- 2 files changed, 7 insertions(+), 4 deletions(-) (limited to 'program') diff --git a/CHANGELOG b/CHANGELOG index d529169e4..b8664b97a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ CHANGELOG Roundcube Webmail =========================== +- 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 diff --git a/program/include/rcmail.php b/program/include/rcmail.php index d866919d6..336e9ca71 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'); + } } -- cgit v1.2.3 From e8be303af3b3cb5187101e9d6f160e0aa8f07285 Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Fri, 24 Aug 2012 13:36:12 +0200 Subject: Added tests for shared functions, fixed small issues --- program/include/rcube_shared.inc | 8 ++++---- tests/phpunit.xml | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) (limited to 'program') diff --git a/program/include/rcube_shared.inc b/program/include/rcube_shared.inc index 5b839d8d2..276940cb6 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 * diff --git a/tests/phpunit.xml b/tests/phpunit.xml index cfd066e29..d6212f458 100644 --- a/tests/phpunit.xml +++ b/tests/phpunit.xml @@ -7,6 +7,7 @@ MailDecode.php MailFunc.php ModCss.php + Shared.php Utils.php VCards.php -- cgit v1.2.3 From 4d1515cb0b43117792a37c03a6ca35ea3ab65eee Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Fri, 24 Aug 2012 18:52:50 +0200 Subject: Fix javascript error on Reply-List action --- program/js/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'program') diff --git a/program/js/app.js b/program/js/app.js index 9ca16b39c..f14f22322 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); } -- cgit v1.2.3 From 4877dbd4d2ca63d7f6b8857abcb4d2f895f2542e Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Sun, 26 Aug 2012 10:23:49 +0200 Subject: Fix Larry's messages list filter in IE (#1488632) --- CHANGELOG | 1 + program/js/app.js | 47 +++++++++++++++++++++-------------------------- 2 files changed, 22 insertions(+), 26 deletions(-) (limited to 'program') diff --git a/CHANGELOG b/CHANGELOG index 8af03d52e..05540a5a4 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ CHANGELOG Roundcube Webmail =========================== +- 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 diff --git a/program/js/app.js b/program/js/app.js index f14f22322..838f240f8 100644 --- a/program/js/app.js +++ b/program/js/app.js @@ -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) { @@ -2652,34 +2644,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 Date: Sun, 26 Aug 2012 18:20:28 +0200 Subject: Add option to not include original message on reply, rename option top_posting to reply_mode (#1485149) --- CHANGELOG | 1 + config/main.inc.php.dist | 7 +++++-- installer/rcube_install.php | 11 ++++++----- program/include/rcube_config.php | 1 + program/localization/en_US/labels.inc | 5 +++-- program/steps/mail/compose.inc | 9 +++++---- program/steps/settings/func.inc | 15 ++++++++------- program/steps/settings/save_prefs.inc | 4 ++-- 8 files changed, 31 insertions(+), 22 deletions(-) (limited to 'program') diff --git a/CHANGELOG b/CHANGELOG index 05540a5a4..f48ace4e8 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ CHANGELOG Roundcube Webmail =========================== +- 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) diff --git a/config/main.inc.php.dist b/config/main.inc.php.dist index 504593028..69a6ea279 100644 --- a/config/main.inc.php.dist +++ b/config/main.inc.php.dist @@ -783,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/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/localization/en_US/labels.inc b/program/localization/en_US/labels.inc index 6085b3898..88de277ac 100644 --- a/program/localization/en_US/labels.inc +++ b/program/localization/en_US/labels.inc @@ -429,8 +429,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/steps/mail/compose.inc b/program/steps/mail/compose.inc index 56f4a052b..c243c887e 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 @@ -641,7 +641,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)) { @@ -906,8 +906,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 @@ -921,7 +922,7 @@ function rcmail_create_reply_body($body, $bodyIsHtml) $prefix = '

' . Q($prefix) . "

\n"; $prefix .= '
'; - if ($RCMAIL->config->get('top_posting')) { + if (intval($RCMAIL->config->get('reply_mode')) > 0) { // top-posting $prefix = '
' . $prefix; $suffix = '
'; } diff --git a/program/steps/settings/func.inc b/program/steps/settings/func.inc index 3f5ef5390..6d548ef36 100644 --- a/program/steps/settings/func.inc +++ b/program/steps/settings/func.inc @@ -544,16 +544,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 +598,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_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']), ); -- cgit v1.2.3 From 8eefbb2158c43b51a8c33e6c480cbe61539b9535 Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Mon, 27 Aug 2012 10:16:04 +0200 Subject: Add option to enable HTML editor on forwarding (#1488517) --- CHANGELOG | 1 + config/main.inc.php.dist | 2 +- program/localization/en_US/labels.inc | 3 ++- program/steps/mail/compose.inc | 7 +++++-- program/steps/settings/func.inc | 1 + 5 files changed, 10 insertions(+), 4 deletions(-) (limited to 'program') diff --git a/CHANGELOG b/CHANGELOG index f48ace4e8..d123003f9 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ CHANGELOG Roundcube Webmail =========================== +- 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) diff --git a/config/main.inc.php.dist b/config/main.inc.php.dist index 69a6ea279..7e07341a9 100644 --- a/config/main.inc.php.dist +++ b/config/main.inc.php.dist @@ -725,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 diff --git a/program/localization/en_US/labels.inc b/program/localization/en_US/labels.inc index 88de277ac..9882c19b5 100644 --- a/program/localization/en_US/labels.inc +++ b/program/localization/en_US/labels.inc @@ -381,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'; diff --git a/program/steps/mail/compose.inc b/program/steps/mail/compose.inc index c243c887e..e57b44ae6 100644 --- a/program/steps/mail/compose.inc +++ b/program/steps/mail/compose.inc @@ -610,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); } diff --git a/program/steps/settings/func.inc b/program/steps/settings/func.inc index 6d548ef36..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'))), -- cgit v1.2.3 From 4c127375ce2c560ad60c61b0ec3ec5cc21405d4f Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Mon, 27 Aug 2012 11:19:05 +0200 Subject: Fix identity selection on reply (#1488101) --- CHANGELOG | 1 + program/steps/mail/compose.inc | 28 ++++++++++++++-------------- 2 files changed, 15 insertions(+), 14 deletions(-) (limited to 'program') diff --git a/CHANGELOG b/CHANGELOG index d123003f9..8e9dcb3c7 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ CHANGELOG Roundcube Webmail =========================== +- 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) diff --git a/program/steps/mail/compose.inc b/program/steps/mail/compose.inc index e57b44ae6..29e12675e 100644 --- a/program/steps/mail/compose.inc +++ b/program/steps/mail/compose.inc @@ -295,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']) { @@ -319,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) { @@ -336,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']; -- cgit v1.2.3 From 6075f084ecbff71490fc5594f2d9470d87938317 Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Mon, 27 Aug 2012 11:47:42 +0200 Subject: Fix deprecated functions usage --- program/include/rcmail.php | 4 ++-- program/include/rcube_bc.inc | 5 ----- program/include/rcube_imap_cache.php | 6 +++--- program/include/rcube_mime.php | 4 ++-- program/include/rcube_vcard.php | 2 +- 5 files changed, 8 insertions(+), 13 deletions(-) (limited to 'program') diff --git a/program/include/rcmail.php b/program/include/rcmail.php index 336e9ca71..02f38e647 100644 --- a/program/include/rcmail.php +++ b/program/include/rcmail.php @@ -308,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 @@ -1209,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_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_imap_cache.php b/program/include/rcube_imap_cache.php index 4d6d5e180..f36ace0eb 100644 --- a/program/include/rcube_imap_cache.php +++ b/program/include/rcube_imap_cache.php @@ -617,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)); } 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_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; -- cgit v1.2.3 From a65ce5d3b07deb578cc4c4aba5695bcea8c07a87 Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Mon, 27 Aug 2012 12:23:30 +0200 Subject: Rename ip_check to check_ip, add IP checking tests --- program/include/rcube_utils.php | 4 ++-- tests/Framework/Utils.php | 47 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 2 deletions(-) (limited to 'program') diff --git a/program/include/rcube_utils.php b/program/include/rcube_utils.php index defb2aed1..aa748dc7f 100644 --- a/program/include/rcube_utils.php +++ b/program/include/rcube_utils.php @@ -94,7 +94,7 @@ class rcube_utils // Validate domain part if (preg_match('/^\[((IPv6:[0-9a-f:.]+)|([0-9.]+))\]$/i', $domain_part, $matches)) { - return self::ip_check(preg_replace('/^IPv6:/i', '', $matches[1])); // valid IPv4 or IPv6 address + return self::check_ip(preg_replace('/^IPv6:/i', '', $matches[1])); // valid IPv4 or IPv6 address } else { // If not an IP address @@ -154,7 +154,7 @@ class rcube_utils * * @return bool True if the address is valid */ - public static function ip_check($ip) + public static function check_ip($ip) { // IPv6, but there's no build-in IPv6 support if (strpos($ip, ':') !== false && !defined('AF_INET6')) { diff --git a/tests/Framework/Utils.php b/tests/Framework/Utils.php index b6cc5d577..503b69a4a 100644 --- a/tests/Framework/Utils.php +++ b/tests/Framework/Utils.php @@ -82,6 +82,53 @@ class Framework_Utils extends PHPUnit_Framework_TestCase $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)); + } + /** * rcube_utils::mod_css_styles() */ -- cgit v1.2.3 From be71abeff913611086e4f36f6888bf57666042c5 Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Mon, 27 Aug 2012 12:25:55 +0200 Subject: Fix deprecated function usage --- program/include/rcube_utils.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'program') diff --git a/program/include/rcube_utils.php b/program/include/rcube_utils.php index aa748dc7f..23bf556e4 100644 --- a/program/include/rcube_utils.php +++ b/program/include/rcube_utils.php @@ -200,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']; } -- cgit v1.2.3 From 397cf794b975e8128a6d155957229106cd065d0c Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Tue, 28 Aug 2012 12:22:43 +0200 Subject: Don't terminate script execution when parsed template doesn't exist and parse was called with 3rd argument = false. --- program/include/rcube_output_html.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'program') diff --git a/program/include/rcube_output_html.php b/program/include/rcube_output_html.php index a071ee354..00378d7d2 100644 --- a/program/include/rcube_output_html.php +++ b/program/include/rcube_output_html.php @@ -400,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; } -- cgit v1.2.3 From b7d33e35186d47dcd3609e1ef97cdb011c6f82f6 Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Tue, 28 Aug 2012 12:59:29 +0200 Subject: Added template object 'frame' --- CHANGELOG | 1 + program/include/rcube_output_html.php | 29 +++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+) (limited to 'program') diff --git a/CHANGELOG b/CHANGELOG index 8e9dcb3c7..028eb196e 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ CHANGELOG Roundcube Webmail =========================== +- 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) diff --git a/program/include/rcube_output_html.php b/program/include/rcube_output_html.php index 00378d7d2..960002112 100644 --- a/program/include/rcube_output_html.php +++ b/program/include/rcube_output_html.php @@ -698,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']) { @@ -1275,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['name']); + $this->set_env('blankpage', $attrib['src'] ? $this->abs_url($attrib['src']) : 'program/resources/blank.gif'); + + return html::iframe($attrib); + } + + /* ************* common functions delivering gui objects ************** */ -- cgit v1.2.3 From c59a822a65bac2aa53eb41978b1ac2d5192710e6 Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Wed, 29 Aug 2012 08:04:36 +0200 Subject: Remove reference to MDB2 from autoloader --- program/include/rcube_shared.inc | 2 -- 1 file changed, 2 deletions(-) (limited to 'program') diff --git a/program/include/rcube_shared.inc b/program/include/rcube_shared.inc index 276940cb6..c15305c08 100644 --- a/program/include/rcube_shared.inc +++ b/program/include/rcube_shared.inc @@ -422,7 +422,6 @@ function rcube_autoload($classname) { $filename = preg_replace( array( - '/MDB2_(.+)/', '/Mail_(.+)/', '/Net_(.+)/', '/Auth_(.+)/', @@ -430,7 +429,6 @@ function rcube_autoload($classname) '/^utf8$/', ), array( - 'Mail/\\1', 'Mail/\\1', 'Net/\\1', 'Auth/\\1', -- cgit v1.2.3 From 46d0012e26d18b1a780ff71cd8aa2f04e6c4a11b Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Wed, 29 Aug 2012 08:32:06 +0200 Subject: Fix quota capability detection so it can be overwritten by a plugin (#1488655) --- CHANGELOG | 1 + program/steps/mail/func.inc | 4 +++- program/steps/settings/folders.inc | 4 +++- 3 files changed, 7 insertions(+), 2 deletions(-) (limited to 'program') diff --git a/CHANGELOG b/CHANGELOG index 8e9dcb3c7..dcaf3f861 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ CHANGELOG Roundcube Webmail =========================== +- Fix quota capability detection so it can be overwritten by a plugin (#1488655) - 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) diff --git a/program/steps/mail/func.inc b/program/steps/mail/func.inc index 7f0b4db5b..45582d40d 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); diff --git a/program/steps/settings/folders.inc b/program/steps/settings/folders.inc index 2c2cbdcc2..6ca704998 100644 --- a/program/steps/settings/folders.inc +++ b/program/steps/settings/folders.inc @@ -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', -- cgit v1.2.3 From 764641d4a38e0a9856cced092ac7c294a2956dcf Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Wed, 29 Aug 2012 09:22:07 +0200 Subject: Fix decoding of HTML messages with UTF-16 charset specified (#1488654) --- CHANGELOG | 1 + program/include/rcube_charset.php | 6 ++++++ 2 files changed, 7 insertions(+) (limited to 'program') diff --git a/CHANGELOG b/CHANGELOG index cddfc96f8..7654cdef8 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ CHANGELOG Roundcube Webmail =========================== +- 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) 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; } -- cgit v1.2.3 From 10562d8a7760eae6b6cb22aa336605e09c7a8bce Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Wed, 29 Aug 2012 11:05:02 +0200 Subject: Keep current folder context when fetching message parts, to make sure that proper folder is used in case when current folder has been changed in meantime. --- program/include/rcube_message.php | 26 +++++++++++++++++++------- program/include/rcube_storage.php | 2 +- 2 files changed, 20 insertions(+), 8 deletions(-) (limited to 'program') diff --git a/program/include/rcube_message.php b/program/include/rcube_message.php index f550b574e..6af1d0133 100644 --- a/program/include/rcube_message.php +++ b/program/include/rcube_message.php @@ -52,7 +52,8 @@ class rcube_message private $opt = array(); private $parse_alternative = false; - public $uid = null; + public $uid; + public $folder; public $headers; public $parts = array(); public $mime_parts = array(); @@ -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_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; } -- cgit v1.2.3 From d317a59d3041223c5070ae72d3d7187ef0c089ca Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Wed, 29 Aug 2012 12:40:09 +0200 Subject: Fix frame() so frame ID is set as 'contentframe' env variable (not name) --- program/include/rcube_output_html.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'program') diff --git a/program/include/rcube_output_html.php b/program/include/rcube_output_html.php index 960002112..2743e7705 100644 --- a/program/include/rcube_output_html.php +++ b/program/include/rcube_output_html.php @@ -1297,7 +1297,7 @@ class rcube_output_html extends rcube_output $attrib['name'] = $attrib['id']; } - $this->set_env('contentframe', $attrib['name']); + $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); -- cgit v1.2.3 From 24fa5d315413fe4042bca175507457e1e660de6f Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Wed, 29 Aug 2012 13:26:34 +0200 Subject: Fix so contentframe can have name != id --- program/js/app.js | 95 +++++++++++++++++++++++++++++++++---------------------- 1 file changed, 57 insertions(+), 38 deletions(-) (limited to 'program') diff --git a/program/js/app.js b/program/js/app.js index 838f240f8..48de21764 100644 --- a/program/js/app.js +++ b/program/js/app.js @@ -1538,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); } }; @@ -1910,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'; } @@ -1952,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) @@ -2009,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 = {}; @@ -2048,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; } @@ -4015,7 +4035,7 @@ function rcube_webmail() this.list_contacts = function(src, group, page) { - var folder, url = {}, + var win, folder, url = {}, target = window; if (!src) @@ -4047,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; } @@ -4104,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 @@ -4726,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(); } @@ -4852,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); } @@ -4881,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')) { @@ -5263,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'; } -- cgit v1.2.3 From b5652641be7ebf4f1b7f115333989d2c971275ea Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Fri, 31 Aug 2012 09:55:00 +0200 Subject: Small improvements in APPEND command handling --- program/include/rcube_imap.php | 4 ++++ program/include/rcube_imap_generic.php | 5 +++-- 2 files changed, 7 insertions(+), 2 deletions(-) (limited to 'program') 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_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; -- cgit v1.2.3 From 10141cd6fd94105c757a0b91f11897cc5ae2c808 Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Mon, 3 Sep 2012 19:00:34 +0200 Subject: Fix displaying all headers when they contain malformed characters (#1488666) --- CHANGELOG | 1 + program/steps/mail/headers.inc | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) (limited to 'program') diff --git a/CHANGELOG b/CHANGELOG index 7654cdef8..e47673647 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ CHANGELOG Roundcube Webmail =========================== +- 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' 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]+/', -- cgit v1.2.3 From 26fc0defe374734db81baa8177466639ed65c900 Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Mon, 3 Sep 2012 19:56:59 +0200 Subject: Fix bug where parentnotwritable error wasn't displayed in the interface --- program/steps/settings/save_folder.inc | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'program') 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'] = ''; -- cgit v1.2.3 From af276f21e5364eb65a56b64c3455bc284ff1e2f2 Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Tue, 4 Sep 2012 09:32:24 +0200 Subject: Add full headers view in message preview window (#1488538) Fix message display page issues - unified with message preview (#1488590, #1488642) --- CHANGELOG | 2 + program/steps/mail/func.inc | 9 +++- skins/larry/ie7hacks.css | 6 +-- skins/larry/iehacks.css | 2 +- skins/larry/images/contactpic_32px.png | Bin 4883 -> 3422 bytes skins/larry/images/contactpic_48px.png | Bin 0 -> 3812 bytes skins/larry/mail.css | 78 ++++++++++++++---------------- skins/larry/svggradients.css | 2 +- skins/larry/templates/message.html | 61 ++++++++++++++--------- skins/larry/templates/messageerror.html | 10 ++-- skins/larry/templates/messagepreview.html | 5 +- skins/larry/ui.js | 34 +++++++++---- 12 files changed, 120 insertions(+), 89 deletions(-) create mode 100644 skins/larry/images/contactpic_48px.png (limited to 'program') diff --git a/CHANGELOG b/CHANGELOG index 8e9dcb3c7..3a14694c9 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,8 @@ CHANGELOG Roundcube Webmail =========================== +- Add full headers view in message preview window (#1488538) +- Fix message display page issues - unified with message preview (#1488590, #1488642) - 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) diff --git a/program/steps/mail/func.inc b/program/steps/mail/func.inc index 7f0b4db5b..6f451e14f 100644 --- a/program/steps/mail/func.inc +++ b/program/steps/mail/func.inc @@ -1053,12 +1053,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; } diff --git a/skins/larry/ie7hacks.css b/skins/larry/ie7hacks.css index 024c35bb2..935a504fe 100644 --- a/skins/larry/ie7hacks.css +++ b/skins/larry/ie7hacks.css @@ -29,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; @@ -45,7 +45,7 @@ a.deletebutton, .pagenav a.button, .pagenav a.button span.inner, -#messagepreviewheader .iconlink, +#messageheader .iconlink, #uploadform a.iconlink { display: inline; } @@ -67,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 Binary files a/skins/larry/images/contactpic_32px.png and b/skins/larry/images/contactpic_32px.png differ diff --git a/skins/larry/images/contactpic_48px.png b/skins/larry/images/contactpic_48px.png new file mode 100644 index 000000000..9cd3bceaf Binary files /dev/null and b/skins/larry/images/contactpic_48px.png differ diff --git a/skins/larry/mail.css b/skins/larry/mail.css index 4fff24307..d2ff11808 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; @@ -680,15 +680,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 +711,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 +786,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 +801,27 @@ 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; - border-radius: 4px; - background: #fff; + background-color: #f0f0f0; } #headers-source { @@ -832,25 +831,30 @@ div.hide-headers { left: 0; right: 0; bottom: 0; - padding: 2px 5px; + padding: 0; 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,45 +866,35 @@ 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; 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 @@
-
- -
+
+ +
- + + +
+ +
+ +
+

+ + -
+
-
-
-

- - + + + + + + + + + + +
+ + + -
-
+
-
-
- -
-
- - -
+
+
+ +
+
+ + +
-
+
-
-
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 @@
-
-
@@ -36,11 +34,11 @@
-
+
-
- -
+
+ +
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 @@ -
+

- +
@@ -25,6 +25,7 @@
+