diff options
52 files changed, 1291 insertions, 605 deletions
@@ -1,6 +1,23 @@ CHANGELOG Roundcube Webmail =========================== +- Fix zipdownload plugin issue with filenames charset (#1489156) +- Fix database cache expunge issues (#1489149) +- Fix date format issues on MS SQL Server (#1488918) +- Fix so non-inline images aren't skipped on forward (#1489150) +- Add imap_cache_ttl option to configure TTL of imap_cache +- Make LDAP cache engine configurable via ldap_cache and ldap_cache_ttl options +- Fix legacy options handling +- Fix "duplicate entry" errors on inserts to imap cache tables (#1489146) +- Fix so bounces addresses in Sender headers are skipped on Reply-All (#1489011) +- Fix bug where serialized strings were truncated in PDO::quote() (#1489142) +- Improved handling of Reply-To/Bcc addresses of identity in compose form (#1489016) +- Fix displaying messages with invalid self-closing HTML tags (#1489137) +- Fix PHP warning when responding to a message with many Return-Path headers (#1489136) +- Added user preference to open all popups as standard windows +- Fix unintentional compose window resize (#1489114) +- Fix performance regression in text wrapping function (#1489133) +- Fix connection to posgtres db using unix socket (#1489132) - Implemented shared cache (rcube_cache_shared) - Fix handling of comma when adding contact from contacts widget (#1489107) - Fix bug where a message was opened in both preview pane and new window on double-click (#1489122) diff --git a/SQL/mssql.initial.sql b/SQL/mssql.initial.sql index 23e7c7d7f..1027867e3 100644 --- a/SQL/mssql.initial.sql +++ b/SQL/mssql.initial.sql @@ -2,6 +2,7 @@ CREATE TABLE [dbo].[cache] ( [user_id] [int] NOT NULL ,
[cache_key] [varchar] (128) COLLATE Latin1_General_CI_AI NOT NULL ,
[created] [datetime] NOT NULL ,
+ [expires] [datetime] NULL ,
[data] [text] COLLATE Latin1_General_CI_AI NOT NULL
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO
@@ -9,6 +10,7 @@ GO CREATE TABLE [dbo].[cache_shared] (
[cache_key] [varchar] (255) COLLATE Latin1_General_CI_AI NOT NULL ,
[created] [datetime] NOT NULL ,
+ [expires] [datetime] NULL ,
[data] [text] COLLATE Latin1_General_CI_AI NOT NULL
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO
@@ -16,7 +18,7 @@ GO CREATE TABLE [dbo].[cache_index] (
[user_id] [int] NOT NULL ,
[mailbox] [varchar] (128) COLLATE Latin1_General_CI_AI NOT NULL ,
- [changed] [datetime] NOT NULL ,
+ [expires] [datetime] NULL ,
[valid] [char] (1) COLLATE Latin1_General_CI_AI NOT NULL ,
[data] [text] COLLATE Latin1_General_CI_AI NOT NULL
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
@@ -25,7 +27,7 @@ GO CREATE TABLE [dbo].[cache_thread] (
[user_id] [int] NOT NULL ,
[mailbox] [varchar] (128) COLLATE Latin1_General_CI_AI NOT NULL ,
- [changed] [datetime] NOT NULL ,
+ [expires] [datetime] NULL ,
[data] [text] COLLATE Latin1_General_CI_AI NOT NULL
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO
@@ -34,7 +36,7 @@ CREATE TABLE [dbo].[cache_messages] ( [user_id] [int] NOT NULL ,
[mailbox] [varchar] (128) COLLATE Latin1_General_CI_AI NOT NULL ,
[uid] [int] NOT NULL ,
- [changed] [datetime] NOT NULL ,
+ [expires] [datetime] NULL ,
[data] [text] COLLATE Latin1_General_CI_AI NOT NULL ,
[flags] [int] NOT NULL
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
@@ -211,46 +213,49 @@ ALTER TABLE [dbo].[cache] ADD CONSTRAINT [DF_cache_created] DEFAULT (getdate()) FOR [created]
GO
-CREATE INDEX [IX_cache_user_id] ON [dbo].[cache]([user_id]) ON [PRIMARY]
+ALTER TABLE [dbo].[cache_shared] ADD
+ CONSTRAINT [DF_cache_shared_created] DEFAULT (getdate()) FOR [created]
GO
-CREATE INDEX [IX_cache_cache_key] ON [dbo].[cache]([cache_key]) ON [PRIMARY]
+ALTER TABLE [dbo].[cache_index] ADD
+ CONSTRAINT [DF_cache_index_valid] DEFAULT ('0') FOR [valid]
GO
-CREATE INDEX [IX_cache_created] ON [dbo].[cache]([created]) ON [PRIMARY]
+ALTER TABLE [dbo].[cache_messages] ADD
+ CONSTRAINT [DF_cache_messages_flags] DEFAULT (0) FOR [flags]
GO
-ALTER TABLE [dbo].[cache_shared] ADD
- CONSTRAINT [DF_cache_shared_created] DEFAULT (getdate()) FOR [created]
+CREATE INDEX [IX_cache_user_id] ON [dbo].[cache]([user_id]) ON [PRIMARY]
+GO
+
+CREATE INDEX [IX_cache_cache_key] ON [dbo].[cache]([cache_key]) ON [PRIMARY]
GO
CREATE INDEX [IX_cache_shared_cache_key] ON [dbo].[cache_shared]([cache_key]) ON [PRIMARY]
GO
-CREATE INDEX [IX_cache_shared_created] ON [dbo].[cache_shared]([created]) ON [PRIMARY]
+CREATE INDEX [IX_cache_index_user_id] ON [dbo].[cache_index]([user_id]) ON [PRIMARY]
GO
-ALTER TABLE [dbo].[cache_index] ADD
- CONSTRAINT [DF_cache_index_changed] DEFAULT (getdate()) FOR [changed],
- CONSTRAINT [DF_cache_index_valid] DEFAULT ('0') FOR [valid]
+CREATE INDEX [IX_cache_thread_user_id] ON [dbo].[cache_thread]([user_id]) ON [PRIMARY]
GO
-CREATE INDEX [IX_cache_index_user_id] ON [dbo].[cache_index]([user_id]) ON [PRIMARY]
+CREATE INDEX [IX_cache_messages_user_id] ON [dbo].[cache_messages]([user_id]) ON [PRIMARY]
GO
-ALTER TABLE [dbo].[cache_thread] ADD
- CONSTRAINT [DF_cache_thread_changed] DEFAULT (getdate()) FOR [changed]
+CREATE INDEX [IX_cache_expires] ON [dbo].[cache]([expires]) ON [PRIMARY]
GO
-CREATE INDEX [IX_cache_thread_user_id] ON [dbo].[cache_thread]([user_id]) ON [PRIMARY]
+CREATE INDEX [IX_cache_shared_expires] ON [dbo].[cache_shared]([expires]) ON [PRIMARY]
GO
-ALTER TABLE [dbo].[cache_messages] ADD
- CONSTRAINT [DF_cache_messages_changed] DEFAULT (getdate()) FOR [changed],
- CONSTRAINT [DF_cache_messages_flags] DEFAULT (0) FOR [flags]
+CREATE INDEX [IX_cache_index_expires] ON [dbo].[cache_index]([expires]) ON [PRIMARY]
GO
-CREATE INDEX [IX_cache_messages_user_id] ON [dbo].[cache_messages]([user_id]) ON [PRIMARY]
+CREATE INDEX [IX_cache_thread_expires] ON [dbo].[cache_thread]([expires]) ON [PRIMARY]
+GO
+
+CREATE INDEX [IX_cache_messages_expires] ON [dbo].[cache_messages]([expires]) ON [PRIMARY]
GO
ALTER TABLE [dbo].[contacts] ADD
@@ -388,6 +393,6 @@ CREATE TRIGGER [contact_delete_member] ON [dbo].[contacts] WHERE [contact_id] IN (SELECT [contact_id] FROM deleted)
GO
-INSERT INTO [dbo].[system] ([name], [value]) VALUES ('roundcube-version', '2013052500')
+INSERT INTO [dbo].[system] ([name], [value]) VALUES ('roundcube-version', '2013061000')
GO
\ No newline at end of file diff --git a/SQL/mssql/2013061000.sql b/SQL/mssql/2013061000.sql new file mode 100644 index 000000000..55b5ec3a5 --- /dev/null +++ b/SQL/mssql/2013061000.sql @@ -0,0 +1,44 @@ +ALTER TABLE [dbo].[cache] ADD COLUMN [expires] [datetime] NULL
+GO
+ALTER TABLE [dbo].[cache_shared] ADD COLUMN [expires] [datetime] NULL
+GO
+ALTER TABLE [dbo].[cache_index] ADD COLUMN [expires] [datetime] NULL
+GO
+ALTER TABLE [dbo].[cache_thread] ADD COLUMN [expires] [datetime] NULL
+GO
+ALTER TABLE [dbo].[cache_messages] ADD COLUMN [expires] [datetime] NULL
+GO
+
+UPDATE [dbo].[cache] SET [expires] = DATEADD(second, 604800, [created])
+GO
+UPDATE [dbo].[cache_shared] SET [expires] = DATEADD(second, 604800, [created])
+GO
+UPDATE [dbo].[cache_index] SET [expires] = DATEADD(second, 604800, [changed])
+GO
+UPDATE [dbo].[cache_thread] SET [expires] = DATEADD(second, 604800, [changed])
+GO
+UPDATE [dbo].[cache_messages] SET [expires] = DATEADD(second, 604800, [changed])
+GO
+
+DROP INDEX [IX_cache_created]
+GO
+DROP INDEX [IX_cache_shared_created]
+GO
+ALTER TABLE [dbo].[cache_index] DROP COLUMN [changed]
+GO
+ALTER TABLE [dbo].[cache_thread] DROP COLUMN [changed]
+GO
+ALTER TABLE [dbo].[cache_messages] DROP COLUMN [changed]
+GO
+
+CREATE INDEX [IX_cache_expires] ON [dbo].[cache]([expires]) ON [PRIMARY]
+GO
+CREATE INDEX [IX_cache_shared_expires] ON [dbo].[cache_shared]([expires]) ON [PRIMARY]
+GO
+CREATE INDEX [IX_cache_index_expires] ON [dbo].[cache_index]([expires]) ON [PRIMARY]
+GO
+CREATE INDEX [IX_cache_thread_expires] ON [dbo].[cache_thread]([expires]) ON [PRIMARY]
+GO
+CREATE INDEX [IX_cache_messages_expires] ON [dbo].[cache_messages]([expires]) ON [PRIMARY]
+GO
+
\ No newline at end of file diff --git a/SQL/mysql.initial.sql b/SQL/mysql.initial.sql index 75268ca03..4e4833a62 100644 --- a/SQL/mysql.initial.sql +++ b/SQL/mysql.initial.sql @@ -35,12 +35,13 @@ CREATE TABLE `users` ( CREATE TABLE `cache` ( `user_id` int(10) UNSIGNED NOT NULL, - `cache_key` varchar(128) /*!40101 CHARACTER SET ascii COLLATE ascii_general_ci */ NOT NULL , + `cache_key` varchar(128) /*!40101 CHARACTER SET ascii COLLATE ascii_general_ci */ NOT NULL, `created` datetime NOT NULL DEFAULT '1000-01-01 00:00:00', + `expires` datetime DEFAULT NULL, `data` longtext NOT NULL, CONSTRAINT `user_id_fk_cache` FOREIGN KEY (`user_id`) REFERENCES `users`(`user_id`) ON DELETE CASCADE ON UPDATE CASCADE, - INDEX `created_index` (`created`), + INDEX `expires_index` (`expires`), INDEX `user_cache_index` (`user_id`,`cache_key`) ) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */; @@ -48,10 +49,11 @@ CREATE TABLE `cache` ( -- Table structure for table `cache_shared` CREATE TABLE `cache_shared` ( - `cache_key` varchar(255) /*!40101 CHARACTER SET ascii COLLATE ascii_general_ci */ NOT NULL , + `cache_key` varchar(255) /*!40101 CHARACTER SET ascii COLLATE ascii_general_ci */ NOT NULL, `created` datetime NOT NULL DEFAULT '1000-01-01 00:00:00', + `expires` datetime DEFAULT NULL, `data` longtext NOT NULL, - INDEX `created_index` (`created`), + INDEX `expires_index` (`expires`), INDEX `cache_key_index` (`cache_key`) ) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */; @@ -61,12 +63,12 @@ CREATE TABLE `cache_shared` ( CREATE TABLE `cache_index` ( `user_id` int(10) UNSIGNED NOT NULL, `mailbox` varchar(255) BINARY NOT NULL, - `changed` datetime NOT NULL DEFAULT '1000-01-01 00:00:00', + `expires` datetime DEFAULT NULL, `valid` tinyint(1) NOT NULL DEFAULT '0', `data` longtext NOT NULL, CONSTRAINT `user_id_fk_cache_index` FOREIGN KEY (`user_id`) REFERENCES `users`(`user_id`) ON DELETE CASCADE ON UPDATE CASCADE, - INDEX `changed_index` (`changed`), + INDEX `expires_index` (`expires`), PRIMARY KEY (`user_id`, `mailbox`) ) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */; @@ -76,11 +78,11 @@ CREATE TABLE `cache_index` ( CREATE TABLE `cache_thread` ( `user_id` int(10) UNSIGNED NOT NULL, `mailbox` varchar(255) BINARY NOT NULL, - `changed` datetime NOT NULL DEFAULT '1000-01-01 00:00:00', + `expires` datetime DEFAULT NULL, `data` longtext NOT NULL, CONSTRAINT `user_id_fk_cache_thread` FOREIGN KEY (`user_id`) REFERENCES `users`(`user_id`) ON DELETE CASCADE ON UPDATE CASCADE, - INDEX `changed_index` (`changed`), + INDEX `expires_index` (`expires`), PRIMARY KEY (`user_id`, `mailbox`) ) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */; @@ -91,12 +93,12 @@ CREATE TABLE `cache_messages` ( `user_id` int(10) UNSIGNED NOT NULL, `mailbox` varchar(255) BINARY NOT NULL, `uid` int(11) UNSIGNED NOT NULL DEFAULT '0', - `changed` datetime NOT NULL DEFAULT '1000-01-01 00:00:00', + `expires` datetime DEFAULT NULL, `data` longtext NOT NULL, `flags` int(11) NOT NULL DEFAULT '0', CONSTRAINT `user_id_fk_cache_messages` FOREIGN KEY (`user_id`) REFERENCES `users`(`user_id`) ON DELETE CASCADE ON UPDATE CASCADE, - INDEX `changed_index` (`changed`), + INDEX `expires_index` (`expires`), PRIMARY KEY (`user_id`, `mailbox`, `uid`) ) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */; @@ -207,4 +209,4 @@ CREATE TABLE `system` ( /*!40014 SET FOREIGN_KEY_CHECKS=1 */; -INSERT INTO system (name, value) VALUES ('roundcube-version', '2013052500'); +INSERT INTO system (name, value) VALUES ('roundcube-version', '2013061000'); diff --git a/SQL/mysql/2013061000.sql b/SQL/mysql/2013061000.sql new file mode 100644 index 000000000..54041b340 --- /dev/null +++ b/SQL/mysql/2013061000.sql @@ -0,0 +1,24 @@ +ALTER TABLE `cache` ADD `expires` datetime DEFAULT NULL; +ALTER TABLE `cache_shared` ADD `expires` datetime DEFAULT NULL; +ALTER TABLE `cache_index` ADD `expires` datetime DEFAULT NULL; +ALTER TABLE `cache_thread` ADD `expires` datetime DEFAULT NULL; +ALTER TABLE `cache_messages` ADD `expires` datetime DEFAULT NULL; + +-- initialize expires column with created/changed date + 7days +UPDATE `cache` SET `expires` = `created` + interval 604800 second; +UPDATE `cache_shared` SET `expires` = `created` + interval 604800 second; +UPDATE `cache_index` SET `expires` = `changed` + interval 604800 second; +UPDATE `cache_thread` SET `expires` = `changed` + interval 604800 second; +UPDATE `cache_messages` SET `expires` = `changed` + interval 604800 second; + +ALTER TABLE `cache` DROP INDEX `created_index`; +ALTER TABLE `cache_shared` DROP INDEX `created_index`; +ALTER TABLE `cache_index` DROP `changed`; +ALTER TABLE `cache_thread` DROP `changed`; +ALTER TABLE `cache_messages` DROP `changed`; + +ALTER TABLE `cache` ADD INDEX `expires_index` (`expires`); +ALTER TABLE `cache_shared` ADD INDEX `expires_index` (`expires`); +ALTER TABLE `cache_index` ADD INDEX `expires_index` (`expires`); +ALTER TABLE `cache_thread` ADD INDEX `expires_index` (`expires`); +ALTER TABLE `cache_messages` ADD INDEX `expires_index` (`expires`); diff --git a/SQL/postgres.initial.sql b/SQL/postgres.initial.sql index c54f05f0b..f18cb6ad0 100644 --- a/SQL/postgres.initial.sql +++ b/SQL/postgres.initial.sql @@ -164,14 +164,15 @@ CREATE INDEX contactgroupmembers_contact_id_idx ON contactgroupmembers (contact_ CREATE TABLE "cache" ( user_id integer NOT NULL - REFERENCES users (user_id) ON DELETE CASCADE ON UPDATE CASCADE, + REFERENCES users (user_id) ON DELETE CASCADE ON UPDATE CASCADE, cache_key varchar(128) DEFAULT '' NOT NULL, created timestamp with time zone DEFAULT now() NOT NULL, + expires timestamp with time zone DEFAULT NULL, data text NOT NULL ); CREATE INDEX cache_user_id_idx ON "cache" (user_id, cache_key); -CREATE INDEX cache_created_idx ON "cache" (created); +CREATE INDEX cache_expires_idx ON "cache" (expires); -- -- Table "cache_shared" @@ -181,11 +182,12 @@ CREATE INDEX cache_created_idx ON "cache" (created); CREATE TABLE "cache_shared" ( cache_key varchar(255) NOT NULL, created timestamp with time zone DEFAULT now() NOT NULL, + expires timestamp with time zone DEFAULT NULL, data text NOT NULL ); CREATE INDEX cache_shared_cache_key_idx ON "cache_shared" (cache_key); -CREATE INDEX cache_shared_created_idx ON "cache_shared" (created); +CREATE INDEX cache_shared_expires_idx ON "cache_shared" (expires); -- -- Table "cache_index" @@ -194,15 +196,15 @@ CREATE INDEX cache_shared_created_idx ON "cache_shared" (created); CREATE TABLE cache_index ( user_id integer NOT NULL - REFERENCES users (user_id) ON DELETE CASCADE ON UPDATE CASCADE, + REFERENCES users (user_id) ON DELETE CASCADE ON UPDATE CASCADE, mailbox varchar(255) NOT NULL, - changed timestamp with time zone DEFAULT now() NOT NULL, + expires timestamp with time zone DEFAULT NULL, valid smallint NOT NULL DEFAULT 0, data text NOT NULL, PRIMARY KEY (user_id, mailbox) ); -CREATE INDEX cache_index_changed_idx ON cache_index (changed); +CREATE INDEX cache_index_expires_idx ON cache_index (expires); -- -- Table "cache_thread" @@ -211,14 +213,14 @@ CREATE INDEX cache_index_changed_idx ON cache_index (changed); CREATE TABLE cache_thread ( user_id integer NOT NULL - REFERENCES users (user_id) ON DELETE CASCADE ON UPDATE CASCADE, + REFERENCES users (user_id) ON DELETE CASCADE ON UPDATE CASCADE, mailbox varchar(255) NOT NULL, - changed timestamp with time zone DEFAULT now() NOT NULL, + expires timestamp with time zone DEFAULT NULL, data text NOT NULL, PRIMARY KEY (user_id, mailbox) ); -CREATE INDEX cache_thread_changed_idx ON cache_thread (changed); +CREATE INDEX cache_thread_expires_idx ON cache_thread (expires); -- -- Table "cache_messages" @@ -227,16 +229,16 @@ CREATE INDEX cache_thread_changed_idx ON cache_thread (changed); CREATE TABLE cache_messages ( user_id integer NOT NULL - REFERENCES users (user_id) ON DELETE CASCADE ON UPDATE CASCADE, + REFERENCES users (user_id) ON DELETE CASCADE ON UPDATE CASCADE, mailbox varchar(255) NOT NULL, uid integer NOT NULL, - changed timestamp with time zone DEFAULT now() NOT NULL, + expires timestamp with time zone DEFAULT NULL, data text NOT NULL, flags integer NOT NULL DEFAULT 0, PRIMARY KEY (user_id, mailbox, uid) ); -CREATE INDEX cache_messages_changed_idx ON cache_messages (changed); +CREATE INDEX cache_messages_expires_idx ON cache_messages (expires); -- -- Table "dictionary" @@ -245,7 +247,7 @@ CREATE INDEX cache_messages_changed_idx ON cache_messages (changed); CREATE TABLE dictionary ( user_id integer DEFAULT NULL - REFERENCES users (user_id) ON DELETE CASCADE ON UPDATE CASCADE, + REFERENCES users (user_id) ON DELETE CASCADE ON UPDATE CASCADE, "language" varchar(5) NOT NULL, data text NOT NULL, CONSTRAINT dictionary_user_id_language_key UNIQUE (user_id, "language") @@ -288,4 +290,4 @@ CREATE TABLE "system" ( value text ); -INSERT INTO system (name, value) VALUES ('roundcube-version', '2013052500'); +INSERT INTO system (name, value) VALUES ('roundcube-version', '2013061000'); diff --git a/SQL/postgres/2013061000.sql b/SQL/postgres/2013061000.sql new file mode 100644 index 000000000..9db0ebcd7 --- /dev/null +++ b/SQL/postgres/2013061000.sql @@ -0,0 +1,24 @@ +ALTER TABLE "cache" ADD expires timestamp with time zone DEFAULT NULL; +ALTER TABLE "cache_shared" ADD expires timestamp with time zone DEFAULT NULL; +ALTER TABLE "cache_index" ADD expires timestamp with time zone DEFAULT NULL; +ALTER TABLE "cache_thread" ADD expires timestamp with time zone DEFAULT NULL; +ALTER TABLE "cache_messages" ADD expires timestamp with time zone DEFAULT NULL; + +-- initialize expires column with created/changed date + 7days +UPDATE "cache" SET expires = created + interval '604800 seconds'; +UPDATE "cache_shared" SET expires = created + interval '604800 seconds'; +UPDATE "cache_index" SET expires = changed + interval '604800 seconds'; +UPDATE "cache_thread" SET expires = changed + interval '604800 seconds'; +UPDATE "cache_messages" SET expires = changed + interval '604800 seconds'; + +DROP INDEX cache_created_idx; +DROP INDEX cache_shared_created_idx; +ALTER TABLE "cache_index" DROP "changed"; +ALTER TABLE "cache_thread" DROP "changed"; +ALTER TABLE "cache_messages" DROP "changed"; + +CREATE INDEX cache_expires_idx ON "cache" (expires); +CREATE INDEX cache_shared_expires_idx ON "cache_shared" (expires); +CREATE INDEX cache_index_expires_idx ON "cache_index" (expires); +CREATE INDEX cache_thread_expires_idx ON "cache_thread" (expires); +CREATE INDEX cache_messages_expires_idx ON "cache_messages" (expires); diff --git a/SQL/sqlite.initial.sql b/SQL/sqlite.initial.sql index 1b5b62b31..28a43680d 100644 --- a/SQL/sqlite.initial.sql +++ b/SQL/sqlite.initial.sql @@ -126,11 +126,12 @@ CREATE TABLE cache ( user_id integer NOT NULL default 0, cache_key varchar(128) NOT NULL default '', created datetime NOT NULL default '0000-00-00 00:00:00', + expires datetime DEFAULT NOT, data text NOT NULL ); CREATE INDEX ix_cache_user_cache_key ON cache(user_id, cache_key); -CREATE INDEX ix_cache_created ON cache(created); +CREATE INDEX ix_cache_expires ON cache(expires); -- -- Table structure for table cache_shared @@ -139,11 +140,12 @@ CREATE INDEX ix_cache_created ON cache(created); CREATE TABLE cache_shared ( cache_key varchar(255) NOT NULL, created datetime NOT NULL default '0000-00-00 00:00:00', + expires datetime DEFAULT NULL, data text NOT NULL ); CREATE INDEX ix_cache_shared_cache_key ON cache_shared(cache_key); -CREATE INDEX ix_cache_shared_created ON cache_shared(created); +CREATE INDEX ix_cache_shared_expires ON cache_shared(expires); -- -- Table structure for table cache_index @@ -152,13 +154,13 @@ CREATE INDEX ix_cache_shared_created ON cache_shared(created); CREATE TABLE cache_index ( user_id integer NOT NULL, mailbox varchar(255) NOT NULL, - changed datetime NOT NULL default '0000-00-00 00:00:00', + expires datetime DEFAULT NULL, valid smallint NOT NULL DEFAULT '0', data text NOT NULL, PRIMARY KEY (user_id, mailbox) ); -CREATE INDEX ix_cache_index_changed ON cache_index (changed); +CREATE INDEX ix_cache_index_expires ON cache_index (expires); -- -- Table structure for table cache_thread @@ -167,12 +169,12 @@ CREATE INDEX ix_cache_index_changed ON cache_index (changed); CREATE TABLE cache_thread ( user_id integer NOT NULL, mailbox varchar(255) NOT NULL, - changed datetime NOT NULL default '0000-00-00 00:00:00', + expires datetime DEFAULT NULL, data text NOT NULL, PRIMARY KEY (user_id, mailbox) ); -CREATE INDEX ix_cache_thread_changed ON cache_thread (changed); +CREATE INDEX ix_cache_thread_expires ON cache_thread (expires); -- -- Table structure for table cache_messages @@ -182,13 +184,13 @@ CREATE TABLE cache_messages ( user_id integer NOT NULL, mailbox varchar(255) NOT NULL, uid integer NOT NULL, - changed datetime NOT NULL default '0000-00-00 00:00:00', + expires datetime DEFAULT NULL, data text NOT NULL, flags integer NOT NULL DEFAULT '0', PRIMARY KEY (user_id, mailbox, uid) ); -CREATE INDEX ix_cache_messages_changed ON cache_messages (changed); +CREATE INDEX ix_cache_messages_expires ON cache_messages (expires); -- -- Table structure for table system @@ -199,4 +201,4 @@ CREATE TABLE system ( value text NOT NULL ); -INSERT INTO system (name, value) VALUES ('roundcube-version', '2013052500'); +INSERT INTO system (name, value) VALUES ('roundcube-version', '2013061000'); diff --git a/SQL/sqlite/2013061000.sql b/SQL/sqlite/2013061000.sql new file mode 100644 index 000000000..3c6b43eac --- /dev/null +++ b/SQL/sqlite/2013061000.sql @@ -0,0 +1,48 @@ +DROP TABLE cache_index; +DROP TABLE cache_thread; +DROP TABLE cache_messages; + +ALTER TABLE cache ADD expires datetime DEFAULT NULL; +DROP INDEX ix_cache_created; + +ALTER TABLE cache_shared ADD expires datetime DEFAULT NULL; +DROP INDEX ix_cache_shared_created; + +UPDATE cache SET expires = datetime(created, '+604800 seconds'); +UPDATE cache_shared SET expires = datetime(created, '+604800 seconds'); + +CREATE INDEX ix_cache_expires ON cache(expires); +CREATE INDEX ix_cache_shared_expires ON cache_shared(expires); + +CREATE TABLE cache_index ( + user_id integer NOT NULL, + mailbox varchar(255) NOT NULL, + expires datetime DEFAULT NULL, + valid smallint NOT NULL DEFAULT '0', + data text NOT NULL, + PRIMARY KEY (user_id, mailbox) +); + +CREATE INDEX ix_cache_index_expires ON cache_index (expires); + +CREATE TABLE cache_thread ( + user_id integer NOT NULL, + mailbox varchar(255) NOT NULL, + expires datetime DEFAULT NULL, + data text NOT NULL, + PRIMARY KEY (user_id, mailbox) +); + +CREATE INDEX ix_cache_thread_expires ON cache_thread (expires); + +CREATE TABLE cache_messages ( + user_id integer NOT NULL, + mailbox varchar(255) NOT NULL, + uid integer NOT NULL, + expires datetime DEFAULT NULL, + data text NOT NULL, + flags integer NOT NULL DEFAULT '0', + PRIMARY KEY (user_id, mailbox, uid) +); + +CREATE INDEX ix_cache_messages_expires ON cache_messages (expires); diff --git a/bin/gc.sh b/bin/gc.sh new file mode 100755 index 000000000..1ee610741 --- /dev/null +++ b/bin/gc.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env php +<?php +/* + +-----------------------------------------------------------------------+ + | bin/gc.sh | + | | + | This file is part of the Roundcube Webmail client | + | Copyright (C) 2013, The Roundcube Dev Team | + | | + | Licensed under the GNU General Public License version 3 or | + | any later version with exceptions for skins & plugins. | + | See the README file for a full license statement. | + | | + | PURPOSE: | + | Trigger garbage collecting routines manually (e.g. via cronjob) | + | | + +-----------------------------------------------------------------------+ + | Author: Thomas Bruederli <roundcube@gmail.com> | + +-----------------------------------------------------------------------+ +*/ + +define('INSTALL_PATH', realpath(dirname(__FILE__) . '/..') . '/' ); + +require INSTALL_PATH.'program/include/clisetup.php'; + +$rcmail = rcube::get_instance(); +$rcmail->gc(); diff --git a/config/main.inc.php.dist b/config/main.inc.php.dist index 281cf4bbf..1091b8718 100644 --- a/config/main.inc.php.dist +++ b/config/main.inc.php.dist @@ -128,6 +128,11 @@ $rcmail_config['imap_cache'] = null; // Enables messages cache. Only 'db' cache is supported. $rcmail_config['messages_cache'] = false; +// Lifetime of IMAP indexes cache. Possible units: s, m, h, d, w +$rcmail_config['imap_cache_ttl'] = '10d'; + +// Lifetime of messages cache. Possible units: s, m, h, d, w +$rcmail_config['messages_cache_ttl'] = '10d'; // ---------------------------------- // SMTP @@ -170,13 +175,23 @@ $rcmail_config['smtp_auth_pw'] = null; // SMTP HELO host // Hostname to give to the remote server for SMTP 'HELO' or 'EHLO' messages // Leave this blank and you will get the server variable 'server_name' or -// localhost if that isn't defined. +// localhost if that isn't defined. $rcmail_config['smtp_helo_host'] = ''; // SMTP connection timeout, in seconds. Default: 0 (no limit) $rcmail_config['smtp_timeout'] = 0; // ---------------------------------- +// LDAP +// ---------------------------------- + +// Type of LDAP cache. Supported values: 'db', 'apc' and 'memcache'. +$rcmail_config['ldap_cache'] = 'db'; + +// Lifetime of LDAP cache. Possible units: s, m, h, d, w +$rcmail_config['ldap_cache_ttl'] = '10m'; + +// ---------------------------------- // SYSTEM // ---------------------------------- @@ -210,10 +225,6 @@ $rcmail_config['log_dir'] = 'logs/'; // use this folder to store temp files (must be writeable for apache user) $rcmail_config['temp_dir'] = 'temp/'; -// lifetime of message cache -// possible units: s, m, h, d, w -$rcmail_config['message_cache_lifetime'] = '10d'; - // enforce connections over https // with this option enabled, all non-secure connections will be redirected. // set the port for the ssl connection as value of this option if it differs from the default 443 @@ -254,9 +265,10 @@ $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' +// Backend to use for session storage. Can either be 'db' (default), 'memcache' or 'php' +// 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 +// Setting this value to 'php' will use the default session save handler configured in PHP $rcmail_config['session_storage'] = 'db'; // Use these hosts for accessing memcached @@ -726,6 +738,10 @@ $rcmail_config['default_charset'] = 'ISO-8859-1'; // skin name: folder from skins/ $rcmail_config['skin'] = 'larry'; +// Enables using standard browser windows (that can be handled as tabs) +// instead of popup windows +$rcmail_config['standard_windows'] = false; + // show up to X items in messages list view $rcmail_config['mail_pagesize'] = 50; diff --git a/plugins/acl/localization/en_US.inc b/plugins/acl/localization/en_US.inc index 033ac29b2..3c61266be 100644 --- a/plugins/acl/localization/en_US.inc +++ b/plugins/acl/localization/en_US.inc @@ -89,7 +89,7 @@ $messages['saving'] = 'Saving access rights...'; $messages['updatesuccess'] = 'Successfully changed access rights'; $messages['deletesuccess'] = 'Successfully deleted access rights'; $messages['createsuccess'] = 'Successfully added access rights'; -$messages['updateerror'] = 'Ubable to update access rights'; +$messages['updateerror'] = 'Unable to update access rights'; $messages['deleteerror'] = 'Unable to delete access rights'; $messages['createerror'] = 'Unable to add access rights'; $messages['deleteconfirm'] = 'Are you sure, you want to remove access rights of selected user(s)?'; diff --git a/plugins/managesieve/localization/tr_TR.inc b/plugins/managesieve/localization/tr_TR.inc index c36869d29..984cb94c4 100644 --- a/plugins/managesieve/localization/tr_TR.inc +++ b/plugins/managesieve/localization/tr_TR.inc @@ -165,8 +165,8 @@ $messages['setcreateerror'] = 'Filtre setleri oluşturulamadı. Sunucuda hata ol $messages['setcreated'] = 'Filtre setleri başarıyla oluşturuldu.'; $messages['activateerror'] = 'Seçilen filtre(ler) etkinleştirilemedi. Sunucuda hata oluştu.'; $messages['deactivateerror'] = 'Seçilen filtre(ler) pasifleştirilemedi. Sunucuda hata oluştu.'; -$messages['deactivated'] = 'Filtre(ler) başarıyla etkinleştirildi.'; -$messages['activated'] = 'Filtre(ler) başarıyla iptal edildi.'; +$messages['deactivated'] = 'Filtre(ler) başarıyla iptal edildi.'; +$messages['activated'] = 'Filtre(ler) başarıyla etkinleştirildi.'; $messages['moved'] = 'Filtre başarıyla taşındı.'; $messages['moveerror'] = 'Seçilen filtre(ler) taşınamadı. Sunucuda hata oluştu.'; $messages['nametoolong'] = 'İsim çok uzun.'; diff --git a/plugins/virtuser_file/virtuser_file.php b/plugins/virtuser_file/virtuser_file.php index 2c705b2d0..974f33d3d 100644 --- a/plugins/virtuser_file/virtuser_file.php +++ b/plugins/virtuser_file/virtuser_file.php @@ -19,13 +19,13 @@ class virtuser_file extends rcube_plugin function init() { - $this->app = rcmail::get_instance(); - $this->file = $this->app->config->get('virtuser_file'); + $this->app = rcmail::get_instance(); + $this->file = $this->app->config->get('virtuser_file'); - if ($this->file) { - $this->add_hook('user2email', array($this, 'user2email')); - $this->add_hook('email2user', array($this, 'email2user')); - } + if ($this->file) { + $this->add_hook('user2email', array($this, 'user2email')); + $this->add_hook('email2user', array($this, 'email2user')); + } } /** @@ -34,25 +34,24 @@ class virtuser_file extends rcube_plugin function user2email($p) { $r = $this->findinvirtual('/\s' . preg_quote($p['user'], '/') . '\s*$/'); - $result = array(); + $result = array(); - for ($i=0; $i<count($r); $i++) - { - $arr = preg_split('/\s+/', $r[$i]); + for ($i=0; $i<count($r); $i++) { + $arr = preg_split('/\s+/', $r[$i]); - if (count($arr) > 0 && strpos($arr[0], '@')) { - $result[] = rcube_utils::idn_to_ascii(trim(str_replace('\\@', '@', $arr[0]))); + if (count($arr) > 0 && strpos($arr[0], '@')) { + $result[] = rcube_utils::idn_to_ascii(trim(str_replace('\\@', '@', $arr[0]))); - if ($p['first']) { - $p['email'] = $result[0]; - break; - } - } - } + if ($p['first']) { + $p['email'] = $result[0]; + break; + } + } + } - $p['email'] = empty($result) ? NULL : $result; + $p['email'] = empty($result) ? NULL : $result; - return $p; + return $p; } /** @@ -60,18 +59,18 @@ class virtuser_file extends rcube_plugin */ function email2user($p) { - $r = $this->findinvirtual('/^' . preg_quote($p['email'], '/') . '\s/'); + $r = $this->findinvirtual('/^' . preg_quote($p['email'], '/') . '\s/'); - for ($i=0; $i<count($r); $i++) { - $arr = preg_split('/\s+/', trim($r[$i])); + for ($i=0; $i<count($r); $i++) { + $arr = preg_split('/\s+/', trim($r[$i])); - if (count($arr) > 0) { - $p['user'] = trim($arr[count($arr)-1]); - break; - } - } + if (count($arr) > 0) { + $p['user'] = trim($arr[count($arr)-1]); + break; + } + } - return $p; + return $p; } /** @@ -82,26 +81,25 @@ class virtuser_file extends rcube_plugin */ private function findinvirtual($pattern) { - $result = array(); - $virtual = null; + $result = array(); + $virtual = null; - if ($this->file) - $virtual = file($this->file); + if ($this->file) + $virtual = file($this->file); - if (empty($virtual)) - return $result; + if (empty($virtual)) + return $result; - // check each line for matches - foreach ($virtual as $line) { - $line = trim($line); - if (empty($line) || $line[0]=='#') - continue; + // check each line for matches + foreach ($virtual as $line) { + $line = trim($line); + if (empty($line) || $line[0]=='#') + continue; - if (preg_match($pattern, $line)) - $result[] = $line; - } + if (preg_match($pattern, $line)) + $result[] = $line; + } - return $result; + return $result; } - } diff --git a/plugins/zipdownload/zipdownload.php b/plugins/zipdownload/zipdownload.php index fbf1d2342..59431267d 100644 --- a/plugins/zipdownload/zipdownload.php +++ b/plugins/zipdownload/zipdownload.php @@ -108,7 +108,7 @@ class zipdownload extends rcube_plugin foreach ($message->attachments as $part) { $pid = $part->mime_id; $part = $message->mime_parts[$pid]; - $disp_name = $this->_convert_filename($part->filename, $part->charset); + $disp_name = $this->_convert_filename($part->filename); if ($part->body) { $orig_message_raw = $part->body; @@ -263,9 +263,9 @@ class zipdownload extends rcube_plugin /** * Helper function to convert filenames to the configured charset */ - private function _convert_filename($str, $from = RCUBE_CHARSET) + private function _convert_filename($str) { - $str = rcube_charset::convert($str, $from == '' ? RCUBE_CHARSET : $from, $this->charset); + $str = rcube_charset::convert($str, RCUBE_CHARSET, $this->charset); return strtr($str, array(':'=>'', '/'=>'-')); } diff --git a/program/include/bc.php b/program/include/bc.php index 0ddfb3215..a7d7b5ac1 100644 --- a/program/include/bc.php +++ b/program/include/bc.php @@ -62,7 +62,7 @@ function rcmail_url($action, $p=array(), $task=null) function rcmail_temp_gc() { - rcmail::get_instance()->temp_gc(); + rcmail::get_instance()->gc_temp(); } function rcube_charset_convert($str, $from, $to=NULL) @@ -405,6 +405,16 @@ function enriched_to_html($data) return rcube_enriched::to_html($data); } +function strip_quotes($str) +{ + return str_replace(array("'", '"'), '', $str); +} + +function strip_newlines($str) +{ + return preg_replace('/[\r\n]/', '', $str); +} + class rcube_html_page extends rcmail_html_page { } diff --git a/program/include/rcmail.php b/program/include/rcmail.php index a0bc03633..17c95d7ed 100644 --- a/program/include/rcmail.php +++ b/program/include/rcmail.php @@ -746,7 +746,7 @@ class rcmail extends rcube // before closing the database connection, write session data if ($_SERVER['REMOTE_ADDR'] && is_object($this->session)) { - session_write_close(); + $this->session->write_close(); } // write performance stats to logs/console @@ -1564,11 +1564,7 @@ class rcmail extends rcube $quota_result = (array) $quota; $quota_result['type'] = isset($_SESSION['quota_display']) ? $_SESSION['quota_display'] : ''; - if (!$quota['total'] && $this->config->get('quota_zero_as_unlimited')) { - $quota_result['title'] = $this->gettext('unlimited'); - $quota_result['percent'] = 0; - } - else if ($quota['total']) { + if ($quota['total'] > 0) { if (!isset($quota['percent'])) { $quota_result['percent'] = min(100, round(($quota['used']/max(1,$quota['total']))*100)); } @@ -1587,7 +1583,8 @@ class rcmail extends rcube } } else { - $quota_result['title'] = $this->gettext('unknown'); + $unlimited = $this->config->get('quota_zero_as_unlimited'); + $quota_result['title'] = $this->gettext($unlimited ? 'unlimited' : 'unknown'); $quota_result['percent'] = 0; } diff --git a/program/include/rcmail_output_html.php b/program/include/rcmail_output_html.php index 02eef2fd1..29a86b9f7 100644 --- a/program/include/rcmail_output_html.php +++ b/program/include/rcmail_output_html.php @@ -67,6 +67,7 @@ class rcmail_output_html extends rcmail_output //$this->framed = $framed; $this->set_env('task', $task); $this->set_env('x_frame_options', $this->config->get('x_frame_options', 'sameorigin')); + $this->set_env('standard_windows', (bool) $this->config->get('standard_windows')); // add cookie info $this->set_env('cookie_domain', ini_get('session.cookie_domain')); @@ -655,7 +656,7 @@ class rcmail_output_html extends rcmail_output protected function file_callback($matches) { $file = $matches[3]; - $file[0] = preg_replace('!^/this/!', '/', $file[0]); + $file = preg_replace('!^/this/!', '/', $file); // correct absolute paths if ($file[0] == '/') { diff --git a/program/js/app.js b/program/js/app.js index 101be65a3..d691c9228 100644 --- a/program/js/app.js +++ b/program/js/app.js @@ -44,7 +44,9 @@ function rcube_webmail() comm_path: './', blankpage: 'program/resources/blank.gif', recipients_separator: ',', - recipients_delimiter: ', ' + recipients_delimiter: ', ', + popup_width: 1150, + popup_width_small: 900 }; // create protected reference to myself @@ -596,15 +598,16 @@ function rcube_webmail() case 'extwin': if (this.env.action == 'compose') { - var form = this.gui_objects.messageform; + var form = this.gui_objects.messageform, + win = this.open_window(''); $("input[name='_action']", form).val('compose'); form.action = this.url('mail/compose', { _id: this.env.compose_id, _extwin: 1 }); - form.target = this.open_window('', 1100, 900); + form.target = win.name; form.submit(); } else { - this.open_window(this.env.permaurl, 900, 900); + this.open_window(this.env.permaurl, true); } break; @@ -857,11 +860,8 @@ function rcube_webmail() // open attachment in frame if it's of a supported mimetype if (command != 'download-attachment' && mimetype && this.env.mimetypes && $.inArray(mimetype, this.env.mimetypes) >= 0) { - var attachment_win = window.open(this.env.comm_path+'&_action=get&'+qstring+'&_frame=1', this.html_identifier('rcubemailattachment'+this.env.uid+props)); - if (attachment_win) { - setTimeout(function(){ attachment_win.focus(); }, 10); + if (this.open_window(this.env.comm_path+'&_action=get&'+qstring+'&_frame=1', true, true)) break; - } } this.goto_url('get', qstring+'&_download=1', false); @@ -1045,7 +1045,7 @@ function rcube_webmail() case 'print': if (uid = this.get_single_uid()) { - ref.printwin = window.open(this.env.comm_path+'&_action=print&_uid='+uid+'&_mbox='+urlencode(this.env.mailbox)+(this.env.safemode ? '&_safe=1' : '')); + ref.printwin = this.open_window(this.env.comm_path+'&_action=print&_uid='+uid+'&_mbox='+urlencode(this.env.mailbox)+(this.env.safemode ? '&_safe=1' : ''), true, true); if (this.printwin) { setTimeout(function(){ ref.printwin.focus(); }, 20); if (this.env.action != 'show') @@ -1055,11 +1055,8 @@ function rcube_webmail() break; case 'viewsource': - if (uid = this.get_single_uid()) { - ref.sourcewin = window.open(this.env.comm_path+'&_action=viewsource&_uid='+uid+'&_mbox='+urlencode(this.env.mailbox)); - if (this.sourcewin) - setTimeout(function(){ ref.sourcewin.focus(); }, 20); - } + if (uid = this.get_single_uid()) + this.open_window(this.env.comm_path+'&_action=viewsource&_uid='+uid+'&_mbox='+urlencode(this.env.mailbox), true, true); break; case 'download': @@ -1629,19 +1626,28 @@ function rcube_webmail() return 0; }; - this.open_window = function(url, width, height) + // open popup window + this.open_window = function(url, small, toolbar) { - var dh = (window.outerHeight || 0) - (window.innerHeight || 0), - dw = (window.outerWidth || 0) - (window.innerWidth || 0), - sh = screen.availHeight || screen.height, - sw = screen.availWidth || screen.width, - w = Math.min(width, sw), - h = Math.min(height, sh), - l = Math.max(0, (sw - w) / 2 + (screen.left || 0)), - t = Math.max(0, (sh - h) / 2 + (screen.top || 0)), - wname = 'rcmextwin' + new Date().getTime(), - extwin = window.open(url + (url.match(/\?/) ? '&' : '?') + '_extwin=1', wname, - 'width='+(w-dw)+',height='+(h-dh)+',top='+t+',left='+l+',resizable=yes,toolbar=no,status=no,location=no'); + var wname = 'rcmextwin' + new Date().getTime(); + + url += (url.match(/\?/) ? '&' : '?') + '_extwin=1'; + + if (this.env.standard_windows) + extwin = window.open(url, wname); + else { + var win = this.is_framed() ? parent.window : window, + page = $(win), + page_width = page.width(), + page_height = bw.mz ? $('body', win).height() : page.height(), + w = Math.min(small ? this.env.popup_width_small : this.env.popup_width, page_width), + h = page_height, // always use same height + l = (win.screenLeft || win.screenX) + 20, + t = (win.screenTop || win.screenY) + 20, + extwin = window.open(url, wname, + 'width='+w+',height='+h+',top='+t+',left='+l+',resizable=yes,location=no,scrollbars=yes' + +(toolbar ? ',toolbar=yes,menubar=yes,status=yes' : ',toolbar=no,menubar=no,status=no')); + } // write loading... message to empty windows if (!url && extwin.document) { @@ -1651,7 +1657,7 @@ function rcube_webmail() // focus window, delayed to bring to front window.setTimeout(function() { extwin.focus(); }, 10); - return wname; + return extwin; }; @@ -1945,7 +1951,7 @@ function rcube_webmail() } else { if (!preview && this.env.message_extwin && !this.env.extwin) - this.open_window(this.env.comm_path+url, 1000, 1200); + this.open_window(this.env.comm_path+url, true); else this.location_href(this.env.comm_path+url, target, true); @@ -2971,11 +2977,12 @@ function rcube_webmail() // open new compose window if (this.env.compose_extwin && !this.env.extwin) { - this.open_window(url, 1150, 900); + this.open_window(url); } else { this.redirect(url); - window.resizeTo(Math.max(1150, $(window).width()), Math.max(900, $(window).height())); + if (this.env.extwin) + window.resizeTo(Math.max(this.env.popup_width, $(window).width()), $(window).height() + 24); } }; @@ -3348,12 +3355,45 @@ function rcube_webmail() if (!show_sig) show_sig = this.env.show_sig; - var cursor_pos, p = -1, + var i, rx, cursor_pos, p = -1, id = obj.options[obj.selectedIndex].value, input_message = $("[name='_message']"), message = input_message.val(), is_html = ($("input[name='_is_html']").val() == '1'), - sig = this.env.identity; + sig = this.env.identity, + delim = this.env.recipients_delimiter, + headers = ['replyto', 'bcc']; + + // update reply-to/bcc fields with addresses defined in identities + for (i in headers) { + var key = headers[i], + old_val = sig && this.env.identities[sig] ? this.env.identities[sig][key] : '', + new_val = id && this.env.identities[id] ? this.env.identities[id][key] : '', + input = $('[name="_'+key+'"]'), input_val = input.val(); + + // remove old address(es) + if (old_val && input_val) { + rx = new RegExp('\\s*' + RegExp.escape(old_val) + '\\s*'); + input_val = input_val.replace(rx, ''); + } + + // cleanup + rx = new RegExp(RegExp.escape(delim) + '\\s*' + RegExp(delim), 'g'); + input_val = input_val.replace(rx, delim) + rx = new RegExp('^\\s*' + RegExp.escape(delim) + '\\s*$'); + input_val = input_val.replace(rx, '') + + // add new address(es) + if (new_val) { + rx = new RegExp(RegExp.escape(delim) + '\\s*$'); + if (input_val && !rx.test(input_val)) + input_val += delim + ' '; + input_val += new_val + delim + ' '; + } + + if (old_val || new_val) + input.val(input_val).change(); + } // enable manual signature insert if (this.env.signatures && this.env.signatures[id]) { @@ -5765,7 +5805,7 @@ function rcube_webmail() for (c=0, len=repl.length; c < len; c++) { cell = document.createElement('td'); - cell.innerHTML = repl[c].html; + cell.innerHTML = repl[c].html || ''; if (repl[c].id) cell.id = repl[c].id; if (repl[c].className) cell.className = repl[c].className; tr.appendChild(cell); diff --git a/program/js/common.js b/program/js/common.js index 53dce8cc2..de07ec131 100644 --- a/program/js/common.js +++ b/program/js/common.js @@ -251,7 +251,7 @@ remove_listener: function(p) }, /** - * Prevent event propagation and bubbeling + * Prevent event propagation and bubbling */ cancel: function(evt) { @@ -371,117 +371,6 @@ triggerEvent: function(evt, e) }; // end rcube_event_engine.prototype - -/** - * Roundcube generic layer (floating box) class - * - * @constructor - */ -function rcube_layer(id, attributes) -{ - this.name = id; - - // create a new layer in the current document - this.create = function(arg) - { - var l = (arg.x) ? arg.x : 0, - t = (arg.y) ? arg.y : 0, - w = arg.width, - h = arg.height, - z = arg.zindex, - vis = arg.vis, - parent = arg.parent, - obj = document.createElement('DIV'); - - obj.id = this.name; - obj.style.position = 'absolute'; - obj.style.visibility = (vis) ? (vis==2) ? 'inherit' : 'visible' : 'hidden'; - obj.style.left = l+'px'; - obj.style.top = t+'px'; - if (w) - obj.style.width = w.toString().match(/\%$/) ? w : w+'px'; - if (h) - obj.style.height = h.toString().match(/\%$/) ? h : h+'px'; - if (z) - obj.style.zIndex = z; - - if (parent) - parent.appendChild(obj); - else - document.body.appendChild(obj); - - this.elm = obj; - }; - - // create new layer - if (attributes != null) { - this.create(attributes); - this.name = this.elm.id; - } - else // just refer to the object - this.elm = document.getElementById(id); - - if (!this.elm) - return false; - - - // ********* layer object properties ********* - - this.css = this.elm.style; - this.event = this.elm; - this.width = this.elm.offsetWidth; - this.height = this.elm.offsetHeight; - this.x = parseInt(this.elm.offsetLeft); - this.y = parseInt(this.elm.offsetTop); - this.visible = (this.css.visibility=='visible' || this.css.visibility=='show' || this.css.visibility=='inherit') ? true : false; - - - // ********* layer object methods ********* - - // move the layer to a specific position - this.move = function(x, y) - { - this.x = x; - this.y = y; - this.css.left = Math.round(this.x)+'px'; - this.css.top = Math.round(this.y)+'px'; - }; - - // change the layers width and height - this.resize = function(w,h) - { - this.css.width = w+'px'; - this.css.height = h+'px'; - this.width = w; - this.height = h; - }; - - // show or hide the layer - this.show = function(a) - { - if(a == 1) { - this.css.visibility = 'visible'; - this.visible = true; - } - else if(a == 2) { - this.css.visibility = 'inherit'; - this.visible = true; - } - else { - this.css.visibility = 'hidden'; - this.visible = false; - } - }; - - // write new content into a Layer - this.write = function(cont) - { - this.elm.innerHTML = cont; - }; - -}; - - // check if input is a valid email address // By Cal Henderson <cal@iamcal.com> // http://code.iamcal.com/php/rfc822/ @@ -536,7 +425,7 @@ function rcube_clone_object(obj) for (var key in obj) { if (obj[key] && typeof obj[key] === 'object') - out[key] = clone_object(obj[key]); + out[key] = rcube_clone_object(obj[key]); else out[key] = obj[key]; } diff --git a/program/lib/Roundcube/bootstrap.php b/program/lib/Roundcube/bootstrap.php index b7e69cb2a..5a371d2f0 100644 --- a/program/lib/Roundcube/bootstrap.php +++ b/program/lib/Roundcube/bootstrap.php @@ -293,32 +293,6 @@ function is_ascii($str, $control_chars = true) /** - * Remove single and double quotes from a given string - * - * @param string Input value - * - * @return string Dequoted string - */ -function strip_quotes($str) -{ - return str_replace(array("'", '"'), '', $str); -} - - -/** - * Remove new lines characters from given string - * - * @param string $str Input value - * - * @return string Stripped string - */ -function strip_newlines($str) -{ - return preg_replace('/[\r\n]/', '', $str); -} - - -/** * Compose a valid representation of name and e-mail address * * @param string $email E-mail address diff --git a/program/lib/Roundcube/rcube.php b/program/lib/Roundcube/rcube.php index 4471acec0..8d17827be 100644 --- a/program/lib/Roundcube/rcube.php +++ b/program/lib/Roundcube/rcube.php @@ -457,30 +457,40 @@ class rcube ini_set('session.name', $sess_name ? $sess_name : 'roundcube_sessid'); ini_set('session.use_cookies', 1); ini_set('session.use_only_cookies', 1); - ini_set('session.serialize_handler', 'php'); ini_set('session.cookie_httponly', 1); // use database for storing session data $this->session = new rcube_session($this->get_dbh(), $this->config); - $this->session->register_gc_handler(array($this, 'temp_gc')); - $this->session->register_gc_handler(array($this, 'cache_gc')); - + $this->session->register_gc_handler(array($this, 'gc_handler')); $this->session->set_secret($this->config->get('des_key') . dirname($_SERVER['SCRIPT_NAME'])); $this->session->set_ip_check($this->config->get('ip_check')); // start PHP session (if not in CLI mode) if ($_SERVER['REMOTE_ADDR']) { - session_start(); + $this->session->start(); } } /** + * Garbage collector - cache/temp cleaner + */ + public function gc() + { + rcube_cache::gc(); + rcube_cache_shared::gc(); + $this->get_storage()->cache_gc(); + + $this->gc_temp(); + } + + + /** * Garbage collector function for temp files. * Remove temp files older than two days */ - public function temp_gc() + public function gc_temp() { $tmp = unslashify($this->config->get('temp_dir')); $expire = time() - 172800; // expire in 48 hours @@ -505,7 +515,7 @@ class rcube * Garbage collector for cache entries. * Set flag to expunge caches on shutdown */ - public function cache_gc() + public function gc_handler() { // because this gc function is called before storage is initialized, // we just set a flag to expunge storage cache on shutdown. @@ -514,6 +524,25 @@ class rcube /** + * Runs garbage collector with probability based on + * session settings. This is intended for environments + * without a session. + */ + public function gc_run() + { + $probability = (int) ini_get('session.gc_probability'); + $divisor = (int) ini_get('session.gc_divisor'); + + if ($divisor > 0 && $probability > 0) { + $random = mt_rand(1, $divisor); + if ($random <= $probability) { + $this->gc(); + } + } + } + + + /** * Get localized text in the desired language * * @param mixed $attrib Named parameters array or label name @@ -897,19 +926,17 @@ class rcube $this->smtp->disconnect(); } + if ($this->expunge_cache) { + $this->gc(); + } + foreach ($this->caches as $cache) { if (is_object($cache)) { - if ($this->expunge_cache) { - $cache->expunge(); - } $cache->close(); } } if (is_object($this->storage)) { - if ($this->expunge_cache) { - $this->storage->expunge_cache(); - } $this->storage->close(); } } diff --git a/program/lib/Roundcube/rcube_cache.php b/program/lib/Roundcube/rcube_cache.php index 129f3242d..a708cb292 100644 --- a/program/lib/Roundcube/rcube_cache.php +++ b/program/lib/Roundcube/rcube_cache.php @@ -38,6 +38,7 @@ class rcube_cache private $type; private $userid; private $prefix; + private $table; private $ttl; private $packed; private $index; @@ -71,8 +72,9 @@ class rcube_cache $this->db = function_exists('apc_exists'); // APC 3.1.4 required } else { - $this->type = 'db'; - $this->db = $rcube->get_dbh(); + $this->type = 'db'; + $this->db = $rcube->get_dbh(); + $this->table = $this->db->table_name('cache'); } // convert ttl string to seconds @@ -145,7 +147,7 @@ class rcube_cache */ function write($key, $data) { - return $this->write_record($key, $this->packed ? serialize($data) : $data); + return $this->write_record($key, $this->serialize($data)); } @@ -194,18 +196,29 @@ class rcube_cache { if ($this->type == 'db' && $this->db && $this->ttl) { $this->db->query( - "DELETE FROM ".$this->db->table_name('cache'). + "DELETE FROM ".$this->table. " WHERE user_id = ?". " AND cache_key LIKE ?". - " AND " . $this->db->unixtimestamp('created')." < ?", + " AND expires < " . $this->db->now(), $this->userid, - $this->prefix.'.%', - time() - $this->ttl); + $this->prefix.'.%'); } } /** + * Remove expired records of all caches + */ + static function gc() + { + $rcube = rcube::get_instance(); + $db = $rcube->get_dbh(); + + $db->query("DELETE FROM " . $db->table_name('cache') . " WHERE expires < " . $db->now()); + } + + + /** * Writes the cache back to the DB. */ function close() @@ -219,7 +232,7 @@ class rcube_cache if ($this->cache_changes[$key]) { // Make sure we're not going to write unchanged data // by comparing current md5 sum with the sum calculated on DB read - $data = $this->packed ? serialize($data) : $data; + $data = $this->serialize($data); if (!$this->cache_sums[$key] || $this->cache_sums[$key] != md5($data)) { $this->write_record($key, $data); @@ -255,7 +268,7 @@ class rcube_cache if ($data) { $md5sum = md5($data); - $data = $this->packed ? unserialize($data) : $data; + $data = $this->unserialize($data); if ($nostore) { return $data; @@ -271,7 +284,7 @@ class rcube_cache else { $sql_result = $this->db->limitquery( "SELECT data, cache_key". - " FROM ".$this->db->table_name('cache'). + " FROM " . $this->table. " WHERE user_id = ?". " AND cache_key = ?". // for better performance we allow more records for one key @@ -283,7 +296,7 @@ class rcube_cache $key = substr($sql_arr['cache_key'], strlen($this->prefix)+1); $md5sum = $sql_arr['data'] ? md5($sql_arr['data']) : null; if ($sql_arr['data']) { - $data = $this->packed ? unserialize($sql_arr['data']) : $sql_arr['data']; + $data = $this->unserialize($sql_arr['data']); } if ($nostore) { @@ -326,7 +339,7 @@ class rcube_cache // Remove NULL rows (here we don't need to check if the record exist) if ($data == 'N;') { $this->db->query( - "DELETE FROM ".$this->db->table_name('cache'). + "DELETE FROM " . $this->table. " WHERE user_id = ?". " AND cache_key = ?", $this->userid, $key); @@ -337,8 +350,10 @@ class rcube_cache // update existing cache record if ($key_exists) { $result = $this->db->query( - "UPDATE ".$this->db->table_name('cache'). - " SET created = ". $this->db->now().", data = ?". + "UPDATE " . $this->table. + " SET created = " . $this->db->now(). + ", expires = " . ($this->ttl ? $this->db->now($this->ttl) : 'NULL'). + ", data = ?". " WHERE user_id = ?". " AND cache_key = ?", $data, $this->userid, $key); @@ -348,9 +363,9 @@ class rcube_cache // for better performance we allow more records for one key // so, no need to check if record exist (see rcube_cache::read_record()) $result = $this->db->query( - "INSERT INTO ".$this->db->table_name('cache'). - " (created, user_id, cache_key, data)". - " VALUES (".$this->db->now().", ?, ?, ?)", + "INSERT INTO " . $this->table. + " (created, expires, user_id, cache_key, data)". + " VALUES (" . $this->db->now() . ", " . ($this->ttl ? $this->db->now($this->ttl) : 'NULL') . ", ?, ?, ?)", $this->userid, $key, $data); } @@ -364,7 +379,6 @@ class rcube_cache * @param string $key Cache key name or pattern * @param boolean $prefix_mode Enable it to clear all keys starting * with prefix specified in $key - * */ private function remove_record($key=null, $prefix_mode=false) { @@ -412,7 +426,7 @@ class rcube_cache } $this->db->query( - "DELETE FROM ".$this->db->table_name('cache'). + "DELETE FROM " . $this->table. " WHERE user_id = ?" . $where, $this->userid); } @@ -553,4 +567,28 @@ class rcube_cache // This way each cache will have its own index return sprintf('%d:%s%s', $this->userid, $this->prefix, 'INDEX'); } + + /** + * Serializes data for storing + */ + private function serialize($data) + { + if ($this->type == 'db') { + return $this->db->encode($data, $this->packed); + } + + return $this->packed ? serialize($data) : $data; + } + + /** + * Unserializes serialized data + */ + private function unserialize($data) + { + if ($this->type == 'db') { + return $this->db->decode($data, $this->packed); + } + + return $this->packed ? @unserialize($data) : $data; + } } diff --git a/program/lib/Roundcube/rcube_cache_shared.php b/program/lib/Roundcube/rcube_cache_shared.php index 5983bd36b..8f2574046 100644 --- a/program/lib/Roundcube/rcube_cache_shared.php +++ b/program/lib/Roundcube/rcube_cache_shared.php @@ -144,7 +144,7 @@ class rcube_cache_shared */ function write($key, $data) { - return $this->write_record($key, $this->packed ? serialize($data) : $data); + return $this->write_record($key, $this->serialize($data)); } @@ -195,14 +195,25 @@ class rcube_cache_shared $this->db->query( "DELETE FROM " . $this->table . " WHERE cache_key LIKE ?" - . " AND " . $this->db->unixtimestamp('created') . " < ?", - $this->prefix . '.%', - time() - $this->ttl); + . " AND expires < " . $this->db->now(), + $this->prefix . '.%'); } } /** + * Remove expired records of all caches + */ + static function gc() + { + $rcube = rcube::get_instance(); + $db = $rcube->get_dbh(); + + $db->query("DELETE FROM " . $db->table_name('cache_shared') . " WHERE expires < " . $db->now()); + } + + + /** * Writes the cache back to the DB. */ function close() @@ -216,7 +227,7 @@ class rcube_cache_shared if ($this->cache_changes[$key]) { // Make sure we're not going to write unchanged data // by comparing current md5 sum with the sum calculated on DB read - $data = $this->packed ? serialize($data) : $data; + $data = $this->serialize($data); if (!$this->cache_sums[$key] || $this->cache_sums[$key] != md5($data)) { $this->write_record($key, $data); @@ -252,7 +263,7 @@ class rcube_cache_shared if ($data) { $md5sum = md5($data); - $data = $this->packed ? unserialize($data) : $data; + $data = $this->unserialize($data); if ($nostore) { return $data; @@ -278,7 +289,7 @@ class rcube_cache_shared if ($sql_arr = $this->db->fetch_assoc($sql_result)) { $md5sum = $sql_arr['data'] ? md5($sql_arr['data']) : null; if ($sql_arr['data']) { - $data = $this->packed ? unserialize($sql_arr['data']) : $sql_arr['data']; + $data = $this->unserialize($sql_arr['data']); } if ($nostore) { @@ -328,7 +339,9 @@ class rcube_cache_shared if ($key_exists) { $result = $this->db->query( "UPDATE " . $this->table . - " SET created = " . $this->db->now() . ", data = ?" . + " SET created = " . $this->db->now() . + ", expires = " . ($this->ttl ? $this->db->now($this->ttl) : 'NULL') . + ", data = ?". " WHERE cache_key = ?", $data, $key); } @@ -338,8 +351,8 @@ class rcube_cache_shared // so, no need to check if record exist (see rcube_cache::read_record()) $result = $this->db->query( "INSERT INTO ".$this->table. - " (created, cache_key, data)". - " VALUES (".$this->db->now().", ?, ?)", + " (created, expires, cache_key, data)". + " VALUES (".$this->db->now().", " . ($this->ttl ? $this->db->now($this->ttl) : 'NULL') . ", ?, ?)", $key, $data); } @@ -541,4 +554,28 @@ class rcube_cache_shared // This way each cache will have its own index return $this->prefix . 'INDEX'; } + + /** + * Serializes data for storing + */ + private function serialize($data) + { + if ($this->type == 'db') { + return $this->db->encode($data, $this->packed); + } + + return $this->packed ? serialize($data) : $data; + } + + /** + * Unserializes serialized data + */ + private function unserialize($data) + { + if ($this->type == 'db') { + return $this->db->decode($data, $this->packed); + } + + return $this->packed ? @unserialize($data) : $data; + } } diff --git a/program/lib/Roundcube/rcube_config.php b/program/lib/Roundcube/rcube_config.php index 2190dc4c2..bd97583c3 100644 --- a/program/lib/Roundcube/rcube_config.php +++ b/program/lib/Roundcube/rcube_config.php @@ -43,6 +43,7 @@ class rcube_config 'reply_mode' => 'top_posting', 'refresh_interval' => 'keep_alive', 'min_refresh_interval' => 'min_keep_alive', + 'messages_cache_ttl' => 'message_cache_lifetime', ); @@ -174,7 +175,7 @@ class rcube_config ob_end_clean(); if (is_array($rcmail_config)) { - $this->prop = array_merge($this->prop, $rcmail_config, $this->userprefs); + $this->merge($rcmail_config); return true; } } @@ -195,9 +196,6 @@ class rcube_config if (isset($this->prop[$name])) { $result = $this->prop[$name]; } - else if (isset($this->legacy_props[$name])) { - return $this->get($this->legacy_props[$name], $def); - } else { $result = $def; } @@ -241,6 +239,7 @@ class rcube_config public function merge($prefs) { $this->prop = array_merge($this->prop, $prefs, $this->userprefs); + $this->fix_legacy_props(); } @@ -273,6 +272,8 @@ class rcube_config $this->userprefs = $prefs; $this->prop = array_merge($this->prop, $prefs); + $this->fix_legacy_props(); + // override timezone settings with client values if ($this->prop['timezone'] == 'auto') { $this->prop['_timezone_value'] = isset($_SESSION['timezone']) ? $this->client_timezone() : $this->prop['_timezone_value']; @@ -435,4 +436,18 @@ class rcube_config return date_default_timezone_get(); } + /** + * Convert legacy options into new ones + */ + private function fix_legacy_props() + { + foreach ($this->legacy_props as $new => $old) { + if (isset($this->prop[$old])) { + if (!isset($this->prop[$new])) { + $this->prop[$new] = $this->prop[$old]; + } + unset($this->prop[$old]); + } + } + } } diff --git a/program/lib/Roundcube/rcube_db.php b/program/lib/Roundcube/rcube_db.php index 5da38c899..852070073 100644 --- a/program/lib/Roundcube/rcube_db.php +++ b/program/lib/Roundcube/rcube_db.php @@ -100,27 +100,15 @@ class rcube_db $this->db_dsnw_array = self::parse_dsn($db_dsnw); $this->db_dsnr_array = self::parse_dsn($db_dsnr); - - // Initialize driver class - $this->init(); - } - - /** - * Initialization of the object with driver specific code - */ - protected function init() - { - // To be used by driver classes } /** * Connect to specific database * - * @param array $dsn DSN for DB connections - * - * @return PDO database handle + * @param array $dsn DSN for DB connections + * @param string $mode Connection mode (r|w) */ - protected function dsn_connect($dsn) + protected function dsn_connect($dsn, $mode) { $this->db_error = false; $this->db_error_msg = null; @@ -158,9 +146,10 @@ class rcube_db return null; } + $this->dbh = $dbh; + $this->db_mode = $mode; + $this->db_connected = true; $this->conn_configure($dsn, $dbh); - - return $dbh; } /** @@ -183,16 +172,6 @@ class rcube_db } /** - * Driver-specific database character set setting - * - * @param string $charset Character set name - */ - protected function set_charset($charset) - { - $this->query("SET NAMES 'utf8'"); - } - - /** * Connect to appropriate database depending on the operation * * @param string $mode Connection mode (r|w) @@ -219,23 +198,14 @@ class rcube_db $dsn = ($mode == 'r') ? $this->db_dsnr_array : $this->db_dsnw_array; - $this->dbh = $this->dsn_connect($dsn); - $this->db_connected = is_object($this->dbh); + $this->dsn_connect($dsn, $mode); // use write-master when read-only fails if (!$this->db_connected && $mode == 'r' && $this->is_replicated()) { - $mode = 'w'; - $this->dbh = $this->dsn_connect($this->db_dsnw_array); - $this->db_connected = is_object($this->dbh); + $this->dsn_connect($this->db_dsnw_array, 'w'); } - if ($this->db_connected) { - $this->db_mode = $mode; - $this->set_charset('utf8'); - } - else { - $this->conn_failure = true; - } + $this->conn_failure = !$this->db_connected; } /** @@ -368,8 +338,10 @@ class rcube_db */ protected function _query($query, $offset, $numrows, $params) { + $query = trim($query); + // Read or write ? - $mode = preg_match('/^(select|show)/i', ltrim($query)) ? 'r' : 'w'; + $mode = preg_match('/^(select|show|set)/i', $query) ? 'r' : 'w'; $this->db_connect($mode); @@ -415,13 +387,16 @@ class rcube_db if ($result === false) { $error = $this->dbh->errorInfo(); - $this->db_error = true; - $this->db_error_msg = sprintf('[%s] %s', $error[1], $error[2]); - rcube::raise_error(array('code' => 500, 'type' => 'db', - 'line' => __LINE__, 'file' => __FILE__, - 'message' => $this->db_error_msg . " (SQL Query: $query)" - ), true, false); + if (empty($this->options['ignore_key_errors']) || $error[0] != '23000') { + $this->db_error = true; + $this->db_error_msg = sprintf('[%s] %s', $error[1], $error[2]); + + rcube::raise_error(array('code' => 500, 'type' => 'db', + 'line' => __LINE__, 'file' => __FILE__, + 'message' => $this->db_error_msg . " (SQL Query: $query)" + ), true, false); + } } $this->last_result = $result; @@ -708,11 +683,19 @@ class rcube_db /** * Return SQL function for current time and date * + * @param int $interval Optional interval (in seconds) to add/subtract + * * @return string SQL function to use in query */ - public function now() + public function now($interval = 0) { - return "now()"; + if ($interval) { + $add = ' ' . ($interval > 0 ? '+' : '-') . ' INTERVAL '; + $add .= $interval > 0 ? intval($interval) : intval($interval) * -1; + $add .= ' SECOND'; + } + + return "now()" . $add; } /** @@ -795,12 +778,19 @@ class rcube_db /** * Encodes non-UTF-8 characters in string/array/object (recursive) * - * @param mixed $input Data to fix + * @param mixed $input Data to fix + * @param bool $serialized Enable serialization * * @return mixed Properly UTF-8 encoded data */ - public static function encode($input) + public static function encode($input, $serialized = false) { + // use Base64 encoding to workaround issues with invalid + // or null characters in serialized string (#1489142) + if ($serialized) { + return base64_encode(serialize($input)); + } + if (is_object($input)) { foreach (get_object_vars($input) as $idx => $value) { $input->$idx = self::encode($value); @@ -811,6 +801,7 @@ class rcube_db foreach ($input as $idx => $value) { $input[$idx] = self::encode($value); } + return $input; } @@ -820,12 +811,24 @@ class rcube_db /** * Decodes encoded UTF-8 string/object/array (recursive) * - * @param mixed $input Input data + * @param mixed $input Input data + * @param bool $serialized Enable serialization * * @return mixed Decoded data */ - public static function decode($input) + public static function decode($input, $serialized = false) { + // use Base64 encoding to workaround issues with invalid + // or null characters in serialized string (#1489142) + if ($serialized) { + // Keep backward compatybility where base64 wasn't used + if (strpos(substr($input, 0, 16), ':') !== false) { + return self::decode(@unserialize($input)); + } + + return @unserialize(base64_decode($input)); + } + if (is_object($input)) { foreach (get_object_vars($input) as $idx => $value) { $input->$idx = self::decode($value); @@ -862,6 +865,17 @@ class rcube_db } /** + * Set class option value + * + * @param string $name Option name + * @param mixed $value Option value + */ + public function set_option($name, $value) + { + $this->options[$name] = $value; + } + + /** * MDB2 DSN string parser * * @param string $sequence Secuence name diff --git a/program/lib/Roundcube/rcube_db_mssql.php b/program/lib/Roundcube/rcube_db_mssql.php index 37a42678a..3c1b9d71f 100644 --- a/program/lib/Roundcube/rcube_db_mssql.php +++ b/program/lib/Roundcube/rcube_db_mssql.php @@ -29,38 +29,52 @@ class rcube_db_mssql extends rcube_db public $db_provider = 'mssql'; /** - * Driver initialization + * Object constructor + * + * @param string $db_dsnw DSN for read/write operations + * @param string $db_dsnr Optional DSN for read only operations + * @param bool $pconn Enables persistent connections */ - protected function init() + public function __construct($db_dsnw, $db_dsnr = '', $pconn = false) { + parent::__construct($db_dsnw, $db_dsnr, $pconn); + $this->options['identifier_start'] = '['; $this->options['identifier_end'] = ']'; } /** - * Character setting + * Driver-specific configuration of database connection + * + * @param array $dsn DSN for DB connections + * @param PDO $dbh Connection handler */ - protected function set_charset($charset) + protected function conn_configure($dsn, $dbh) { - // UTF-8 is default + // Set date format in case of non-default language (#1488918) + $this->query("SET DATEFORMAT ymd"); } /** * Return SQL function for current time and date * + * @param int $interval Optional interval (in seconds) to add/subtract + * * @return string SQL function to use in query */ - public function now() + public function now($interval = 0) { + if ($interval) { + $interval = intval($interval); + return "dateadd(second, $interval, getdate())"; + } + return "getdate()"; } /** * Return SQL statement to convert a field value into a unix timestamp * - * This method is deprecated and should not be used anymore due to limitations - * of timestamp functions in Mysql (year 2038 problem) - * * @param string $field Field name * * @return string SQL statement to use in query diff --git a/program/lib/Roundcube/rcube_db_mysql.php b/program/lib/Roundcube/rcube_db_mysql.php index 2d42610b6..6fa5ad768 100644 --- a/program/lib/Roundcube/rcube_db_mysql.php +++ b/program/lib/Roundcube/rcube_db_mysql.php @@ -30,9 +30,13 @@ class rcube_db_mysql extends rcube_db public $db_provider = 'mysql'; /** - * Driver initialization/configuration + * Object constructor + * + * @param string $db_dsnw DSN for read/write operations + * @param string $db_dsnr Optional DSN for read only operations + * @param bool $pconn Enables persistent connections */ - protected function init() + public function __construct($db_dsnw, $db_dsnr = '', $pconn = false) { if (version_compare(PHP_VERSION, '5.3.0', '<')) { rcube::raise_error(array('code' => 600, 'type' => 'db', @@ -41,12 +45,25 @@ class rcube_db_mysql extends rcube_db true, true); } + parent::__construct($db_dsnw, $db_dsnr, $pconn); + // SQL identifiers quoting $this->options['identifier_start'] = '`'; $this->options['identifier_end'] = '`'; } /** + * Driver-specific configuration of database connection + * + * @param array $dsn DSN for DB connections + * @param PDO $dbh Connection handler + */ + protected function conn_configure($dsn, $dbh) + { + $this->query("SET NAMES 'utf8'"); + } + + /** * Abstract SQL statement for value concatenation * * @return string SQL statement to be used in query diff --git a/program/lib/Roundcube/rcube_db_pgsql.php b/program/lib/Roundcube/rcube_db_pgsql.php index adfd2207b..d72c9d6b3 100644 --- a/program/lib/Roundcube/rcube_db_pgsql.php +++ b/program/lib/Roundcube/rcube_db_pgsql.php @@ -29,6 +29,17 @@ class rcube_db_pgsql extends rcube_db public $db_provider = 'postgres'; /** + * Driver-specific configuration of database connection + * + * @param array $dsn DSN for DB connections + * @param PDO $dbh Connection handler + */ + protected function conn_configure($dsn, $dbh) + { + $this->query("SET NAMES 'utf8'"); + } + + /** * Get last inserted record ID * * @param string $table Table name (to find the incremented sequence) @@ -75,9 +86,6 @@ class rcube_db_pgsql extends rcube_db /** * Return SQL statement to convert a field value into a unix timestamp * - * This method is deprecated and should not be used anymore due to limitations - * of timestamp functions in Mysql (year 2038 problem) - * * @param string $field Field name * * @return string SQL statement to use in query @@ -89,6 +97,24 @@ class rcube_db_pgsql extends rcube_db } /** + * Return SQL function for current time and date + * + * @param int $interval Optional interval (in seconds) to add/subtract + * + * @return string SQL function to use in query + */ + public function now($interval = 0) + { + if ($interval) { + $add = ' ' . ($interval > 0 ? '+' : '-') . " interval '"; + $add .= $interval > 0 ? intval($interval) : intval($interval) * -1; + $add .= " seconds'"; + } + + return "now()" . $add; + } + + /** * Return SQL statement for case insensitive LIKE * * @param string $column Field name @@ -130,4 +156,38 @@ class rcube_db_pgsql extends rcube_db return isset($this->variables[$varname]) ? $this->variables[$varname] : $default; } + /** + * Returns PDO DSN string from DSN array + * + * @param array $dsn DSN parameters + * + * @return string DSN string + */ + protected function dsn_string($dsn) + { + $params = array(); + $result = 'pgsql:'; + + if ($dsn['hostspec']) { + $params[] = 'host=' . $dsn['hostspec']; + } + else if ($dsn['socket']) { + $params[] = 'host=' . $dsn['socket']; + } + + if ($dsn['port']) { + $params[] = 'port=' . $dsn['port']; + } + + if ($dsn['database']) { + $params[] = 'dbname=' . $dsn['database']; + } + + if (!empty($params)) { + $result .= implode(';', $params); + } + + return $result; + } + } diff --git a/program/lib/Roundcube/rcube_db_sqlite.php b/program/lib/Roundcube/rcube_db_sqlite.php index 145b8a371..b66c56097 100644 --- a/program/lib/Roundcube/rcube_db_sqlite.php +++ b/program/lib/Roundcube/rcube_db_sqlite.php @@ -29,13 +29,6 @@ class rcube_db_sqlite extends rcube_db public $db_provider = 'sqlite'; /** - * Database character set - */ - protected function set_charset($charset) - { - } - - /** * Prepare connection */ protected function conn_prepare($dsn) @@ -56,10 +49,6 @@ class rcube_db_sqlite extends rcube_db */ protected function conn_configure($dsn, $dbh) { - // we emulate via callback some missing functions - $dbh->sqliteCreateFunction('unix_timestamp', array('rcube_db_sqlite', 'sqlite_unix_timestamp'), 1); - $dbh->sqliteCreateFunction('now', array('rcube_db_sqlite', 'sqlite_now'), 0); - // Initialize database structure in file is empty if (!empty($dsn['database']) && !filesize($dsn['database'])) { $data = file_get_contents(RCUBE_INSTALL_PATH . 'SQL/sqlite.initial.sql'); @@ -83,30 +72,32 @@ class rcube_db_sqlite extends rcube_db } /** - * Callback for sqlite: unix_timestamp() + * Return SQL statement to convert a field value into a unix timestamp + * + * @param string $field Field name + * + * @return string SQL statement to use in query + * @deprecated */ - public static function sqlite_unix_timestamp($timestamp = '') + public function unixtimestamp($field) { - $timestamp = trim($timestamp); - if (!$timestamp) { - $ret = time(); - } - else if (!preg_match('/^[0-9]+$/s', $timestamp)) { - $ret = strtotime($timestamp); - } - else { - $ret = $timestamp; - } - - return $ret; + return "strftime('%s', $field)"; } /** - * Callback for sqlite: now() + * Return SQL function for current time and date + * + * @param int $interval Optional interval (in seconds) to add/subtract + * + * @return string SQL function to use in query */ - public static function sqlite_now() + public function now($interval = 0) { - return date("Y-m-d H:i:s"); + if ($interval) { + $add = ($interval > 0 ? '+' : '') . intval($interval) . ' seconds'; + } + + return "datetime('now'" . ($add ? ",'$add'" : "") . ")"; } /** diff --git a/program/lib/Roundcube/rcube_db_sqlsrv.php b/program/lib/Roundcube/rcube_db_sqlsrv.php index e5dfb1154..45c41cdaf 100644 --- a/program/lib/Roundcube/rcube_db_sqlsrv.php +++ b/program/lib/Roundcube/rcube_db_sqlsrv.php @@ -29,29 +29,46 @@ class rcube_db_sqlsrv extends rcube_db public $db_provider = 'mssql'; /** - * Driver initialization + * Object constructor + * + * @param string $db_dsnw DSN for read/write operations + * @param string $db_dsnr Optional DSN for read only operations + * @param bool $pconn Enables persistent connections */ - protected function init() + public function __construct($db_dsnw, $db_dsnr = '', $pconn = false) { + parent::__construct($db_dsnw, $db_dsnr, $pconn); + $this->options['identifier_start'] = '['; $this->options['identifier_end'] = ']'; } /** - * Database character set setting + * Driver-specific configuration of database connection + * + * @param array $dsn DSN for DB connections + * @param PDO $dbh Connection handler */ - protected function set_charset($charset) + protected function conn_configure($dsn, $dbh) { - // UTF-8 is default + // Set date format in case of non-default language (#1488918) + $this->query("SET DATEFORMAT ymd"); } /** * Return SQL function for current time and date * + * @param int $interval Optional interval (in seconds) to add/subtract + * * @return string SQL function to use in query */ - public function now() + public function now($interval = 0) { + if ($interval) { + $interval = intval($interval); + return "dateadd(second, $interval, getdate())"; + } + return "getdate()"; } diff --git a/program/lib/Roundcube/rcube_image.php b/program/lib/Roundcube/rcube_image.php index 735a0df01..09bb4e81b 100644 --- a/program/lib/Roundcube/rcube_image.php +++ b/program/lib/Roundcube/rcube_image.php @@ -93,6 +93,10 @@ class rcube_image $convert = $rcube->config->get('im_convert_path', false); $props = $this->props(); + if (empty($props)) { + return false; + } + if (!$filename) { $filename = $this->image_file; } @@ -148,6 +152,10 @@ class rcube_image return false; } + if ($image === false) { + return false; + } + $scale = $size / max($props['width'], $props['height']); // Imagemagick resize is implemented in shrinking mode (see -resize argument above) diff --git a/program/lib/Roundcube/rcube_imap.php b/program/lib/Roundcube/rcube_imap.php index 31e7079fb..39057a89a 100644 --- a/program/lib/Roundcube/rcube_imap.php +++ b/program/lib/Roundcube/rcube_imap.php @@ -812,20 +812,22 @@ class rcube_imap extends rcube_storage return $mcache->get_thread($folder); } - if (empty($this->icache['threads'])) { - if (!$this->check_connection()) { - return new rcube_result_thread(); + if (!empty($this->icache['threads'])) { + if ($this->icache['threads']->get_parameters('MAILBOX') == $folder) { + return $this->icache['threads']; } + } - // get all threads - $result = $this->conn->thread($folder, $this->threading, - $this->options['skip_deleted'] ? 'UNDELETED' : '', true); - - // add to internal (fast) cache - $this->icache['threads'] = $result; + if (!$this->check_connection()) { + return new rcube_result_thread(); } - return $this->icache['threads']; + // get all threads + $result = $this->conn->thread($folder, $this->threading, + $this->options['skip_deleted'] ? 'UNDELETED' : '', true); + + // add to internal (fast) cache + return $this->icache['threads'] = $result; } @@ -3691,7 +3693,7 @@ class rcube_imap extends rcube_storage { if ($this->caching && !$this->cache) { $rcube = rcube::get_instance(); - $ttl = $rcube->config->get('message_cache_lifetime', '10d'); + $ttl = $rcube->config->get('imap_cache_ttl', '10d'); $this->cache = $rcube->get_cache('IMAP', $this->caching, $ttl); } @@ -3739,24 +3741,6 @@ class rcube_imap extends rcube_storage } } - /** - * Delete outdated cache entries - */ - public function expunge_cache() - { - if ($this->mcache) { - $ttl = rcube::get_instance()->config->get('message_cache_lifetime', '10d'); - $this->mcache->expunge($ttl); - } - -/* - // this cache is expunged by rcube class - if ($this->cache) { - $this->cache->expunge(); - } -*/ - } - /* -------------------------------- * message caching methods @@ -3790,8 +3774,9 @@ class rcube_imap extends rcube_storage if ($this->messages_caching && !$this->mcache) { $rcube = rcube::get_instance(); if (($dbh = $rcube->get_dbh()) && ($userid = $rcube->get_user_id())) { + $ttl = $rcube->config->get('messages_cache_ttl', '10d'); $this->mcache = new rcube_imap_cache( - $dbh, $this, $userid, $this->options['skip_deleted']); + $dbh, $this, $userid, $this->options['skip_deleted'], $ttl); } } @@ -3813,6 +3798,15 @@ class rcube_imap extends rcube_storage } + /** + * Delete outdated cache entries + */ + function cache_gc() + { + rcube_imap_cache::gc(); + } + + /* -------------------------------- * protected methods * --------------------------------*/ diff --git a/program/lib/Roundcube/rcube_imap_cache.php b/program/lib/Roundcube/rcube_imap_cache.php index 47d9aaf4a..e2fd2a98b 100644 --- a/program/lib/Roundcube/rcube_imap_cache.php +++ b/program/lib/Roundcube/rcube_imap_cache.php @@ -49,6 +49,13 @@ class rcube_imap_cache private $userid; /** + * Expiration time in seconds + * + * @var int + */ + private $ttl; + + /** * Internal (in-memory) cache * * @var array @@ -83,13 +90,25 @@ class rcube_imap_cache /** * Object constructor. + * + * @param rcube_db $db DB handler + * @param rcube_imap $imap IMAP handler + * @param int $userid User identifier + * @param bool $skip_deleted skip_deleted flag + * @param string $ttl Expiration time of memcache/apc items + * */ - function __construct($db, $imap, $userid, $skip_deleted) + function __construct($db, $imap, $userid, $skip_deleted, $ttl=0) { + // convert ttl string to seconds + $ttl = get_offset_sec($ttl); + if ($ttl > 2592000) $ttl = 2592000; + $this->db = $db; $this->imap = $imap; $this->userid = $userid; $this->skip_deleted = $skip_deleted; + $this->ttl = $ttl; } @@ -407,8 +426,8 @@ class rcube_imap_cache return; } - $msg = serialize($this->db->encode(clone $message)); $flags = 0; + $msg = clone $message; if (!empty($message->flags)) { foreach ($this->flags as $idx => $flag) { @@ -417,14 +436,16 @@ class rcube_imap_cache } } } + unset($msg->flags); + $msg = $this->db->encode($msg, true); // update cache record (even if it exists, the update // here will work as select, assume row exist if affected_rows=0) if (!$force) { $res = $this->db->query( "UPDATE ".$this->db->table_name('cache_messages') - ." SET flags = ?, data = ?, changed = ".$this->db->now() + ." SET flags = ?, data = ?, expires = " . ($this->ttl ? $this->db->now($this->ttl) : 'NULL') ." WHERE user_id = ?" ." AND mailbox = ?" ." AND uid = ?", @@ -435,12 +456,29 @@ class rcube_imap_cache } } + $this->db->set_option('ignore_key_errors', true); + // insert new record - $this->db->query( + $res = $this->db->query( "INSERT INTO ".$this->db->table_name('cache_messages') - ." (user_id, mailbox, uid, flags, changed, data)" - ." VALUES (?, ?, ?, ?, ".$this->db->now().", ?)", + ." (user_id, mailbox, uid, flags, expires, data)" + ." VALUES (?, ?, ?, ?, ". ($this->ttl ? $this->db->now($this->ttl) : 'NULL') . ", ?)", $this->userid, $mailbox, (int) $message->uid, $flags, $msg); + + // race-condition, insert failed so try update (#1489146) + // thanks to ignore_key_errors "duplicate row" errors will be ignored + if ($force && !$res && !$this->db->is_error($res)) { + $this->db->query( + "UPDATE ".$this->db->table_name('cache_messages') + ." SET expires = " . ($this->ttl ? $this->db->now($this->ttl) : 'NULL') + .", flags = ?, data = ?" + ." WHERE user_id = ?" + ." AND mailbox = ?" + ." AND uid = ?", + $flags, $msg, $this->userid, $mailbox, (int) $message->uid); + } + + $this->db->set_option('ignore_key_errors', false); } @@ -481,7 +519,7 @@ class rcube_imap_cache $this->db->query( "UPDATE ".$this->db->table_name('cache_messages') - ." SET changed = ".$this->db->now() + ." SET expires = ". ($this->ttl ? $this->db->now($this->ttl) : 'NULL') .", flags = flags ".($enabled ? "+ $idx" : "- $idx") ." WHERE user_id = ?" ." AND mailbox = ?" @@ -604,23 +642,21 @@ class rcube_imap_cache /** - * Delete cache entries older than TTL - * - * @param string $ttl Lifetime of message cache entries + * Delete expired cache entries */ - function expunge($ttl) + static function gc() { - // get expiration timestamp - $ts = get_offset_time($ttl, -1); + $rcube = rcube::get_instance(); + $db = $rcube->get_dbh(); - $this->db->query("DELETE FROM ".$this->db->table_name('cache_messages') - ." WHERE changed < " . $this->db->fromunixtime($ts)); + $db->query("DELETE FROM ".$db->table_name('cache_messages') + ." WHERE expired < " . $db->now()); - $this->db->query("DELETE FROM ".$this->db->table_name('cache_index') - ." WHERE changed < " . $this->db->fromunixtime($ts)); + $db->query("DELETE FROM ".$db->table_name('cache_index') + ." WHERE expired < " . $db->now()); - $this->db->query("DELETE FROM ".$this->db->table_name('cache_thread') - ." WHERE changed < " . $this->db->fromunixtime($ts)); + $db->query("DELETE FROM ".$db->table_name('cache_thread') + ." WHERE expired < " . $db->now()); } @@ -639,7 +675,7 @@ class rcube_imap_cache if ($sql_arr = $this->db->fetch_assoc($sql_result)) { $data = explode('@', $sql_arr['data']); - $index = @unserialize($data[0]); + $index = $this->db->decode($data[0], true); unset($data[0]); if (empty($index)) { @@ -676,7 +712,7 @@ class rcube_imap_cache if ($sql_arr = $this->db->fetch_assoc($sql_result)) { $data = explode('@', $sql_arr['data']); - $thread = @unserialize($data[0]); + $thread = $this->db->decode($data[0], true); unset($data[0]); if (empty($thread)) { @@ -702,7 +738,7 @@ class rcube_imap_cache $data, $mbox_data = array(), $exists = false, $modseq = null) { $data = array( - serialize($data), + $this->db->encode($data, true), $sort_field, (int) $this->skip_deleted, (int) $mbox_data['UIDVALIDITY'], @@ -712,20 +748,38 @@ class rcube_imap_cache $data = implode('@', $data); if ($exists) { - $sql_result = $this->db->query( + $res = $this->db->query( "UPDATE ".$this->db->table_name('cache_index') - ." SET data = ?, valid = 1, changed = ".$this->db->now() + ." SET data = ?, valid = 1, expires = " . ($this->ttl ? $this->db->now($this->ttl) : 'NULL') ." WHERE user_id = ?" ." AND mailbox = ?", $data, $this->userid, $mailbox); + + if ($this->db->affected_rows($res)) { + return; + } } - else { - $sql_result = $this->db->query( - "INSERT INTO ".$this->db->table_name('cache_index') - ." (user_id, mailbox, data, valid, changed)" - ." VALUES (?, ?, ?, 1, ".$this->db->now().")", - $this->userid, $mailbox, $data); + + $this->db->set_option('ignore_key_errors', true); + + $res = $this->db->query( + "INSERT INTO ".$this->db->table_name('cache_index') + ." (user_id, mailbox, valid, expires, data)" + ." VALUES (?, ?, 1, ". ($this->ttl ? $this->db->now($this->ttl) : 'NULL') .", ?)", + $this->userid, $mailbox, $data); + + // race-condition, insert failed so try update (#1489146) + // thanks to ignore_key_errors "duplicate row" errors will be ignored + if (!$exists && !$res && !$this->db->is_error($res)) { + $res = $this->db->query( + "UPDATE ".$this->db->table_name('cache_index') + ." SET data = ?, valid = 1, expires = " . ($this->ttl ? $this->db->now($this->ttl) : 'NULL') + ." WHERE user_id = ?" + ." AND mailbox = ?", + $data, $this->userid, $mailbox); } + + $this->db->set_option('ignore_key_errors', false); } @@ -735,28 +789,48 @@ class rcube_imap_cache private function add_thread_row($mailbox, $data, $mbox_data = array(), $exists = false) { $data = array( - serialize($data), + $this->db->encode($data, true), (int) $this->skip_deleted, (int) $mbox_data['UIDVALIDITY'], (int) $mbox_data['UIDNEXT'], ); $data = implode('@', $data); + $expires = ($this->ttl ? $this->db->now($this->ttl) : 'NULL'); + if ($exists) { - $sql_result = $this->db->query( + $res = $this->db->query( "UPDATE ".$this->db->table_name('cache_thread') - ." SET data = ?, changed = ".$this->db->now() + ." SET data = ?, expires = $expires" ." WHERE user_id = ?" ." AND mailbox = ?", $data, $this->userid, $mailbox); + + if ($this->db->affected_rows($res)) { + return; + } } - else { - $sql_result = $this->db->query( - "INSERT INTO ".$this->db->table_name('cache_thread') - ." (user_id, mailbox, data, changed)" - ." VALUES (?, ?, ?, ".$this->db->now().")", - $this->userid, $mailbox, $data); + + $this->db->set_option('ignore_key_errors', true); + + $res = $this->db->query( + "INSERT INTO ".$this->db->table_name('cache_thread') + ." (user_id, mailbox, expires, data)" + ." VALUES (?, ?, $expires, ?)", + $this->userid, $mailbox, $data); + + // race-condition, insert failed so try update (#1489146) + // thanks to ignore_key_errors "duplicate row" errors will be ignored + if (!$exists && !$res && !$this->db->is_error($res)) { + $this->db->query( + "UPDATE ".$this->db->table_name('cache_thread') + ." SET expires = $expires, data = ?" + ." WHERE user_id = ?" + ." AND mailbox = ?", + $data, $this->userid, $mailbox); } + + $this->db->set_option('ignore_key_errors', false); } @@ -1004,7 +1078,7 @@ class rcube_imap_cache $this->db->query( "UPDATE ".$this->db->table_name('cache_messages') - ." SET flags = ?, changed = ".$this->db->now() + ." SET flags = ?, expires = " . ($this->ttl ? $this->db->now($this->ttl) : 'NULL') ." WHERE user_id = ?" ." AND mailbox = ?" ." AND uid = ?" @@ -1067,7 +1141,7 @@ class rcube_imap_cache */ private function build_message($sql_arr) { - $message = $this->db->decode(unserialize($sql_arr['data'])); + $message = $this->db->decode($sql_arr['data'], true); if ($message) { $message->flags = array(); diff --git a/program/lib/Roundcube/rcube_ldap.php b/program/lib/Roundcube/rcube_ldap.php index 70163b21c..39a48b456 100644 --- a/program/lib/Roundcube/rcube_ldap.php +++ b/program/lib/Roundcube/rcube_ldap.php @@ -185,8 +185,12 @@ class rcube_ldap extends rcube_addressbook $this->mail_domain = $mail_domain; // initialize cache - $rcube = rcube::get_instance(); - $this->cache = $rcube->get_cache('LDAP.' . asciiwords($this->prop['name']), 'db', 600); + $rcube = rcube::get_instance(); + $cache_type = $rcube->config->get('ldap_cache', 'db'); + $cache_ttl = $rcube->config->get('ldap_cache_ttl', '10m'); + $cache_name = 'LDAP.' . asciiwords($this->prop['name']); + + $this->cache = $rcube->get_cache($cache_name, $cache_type, $cache_ttl); $this->_connect(); } diff --git a/program/lib/Roundcube/rcube_mime.php b/program/lib/Roundcube/rcube_mime.php index 5258af5bf..572540f47 100644 --- a/program/lib/Roundcube/rcube_mime.php +++ b/program/lib/Roundcube/rcube_mime.php @@ -587,23 +587,20 @@ class rcube_mime */ public static function wordwrap($string, $width=75, $break="\n", $cut=false, $charset=null, $wrap_quoted=true) { - if (!$charset) { - $charset = RCUBE_CHARSET; - } + // Note: Never try to use iconv instead of mbstring functions here + // Iconv's substr/strlen are 100x slower (#1489113) - // detect available functions - $strlen_func = function_exists('iconv_strlen') ? 'iconv_strlen' : 'mb_strlen'; - $strpos_func = function_exists('iconv_strpos') ? 'iconv_strpos' : 'mb_strpos'; - $strrpos_func = function_exists('iconv_strrpos') ? 'iconv_strrpos' : 'mb_strrpos'; - $substr_func = function_exists('iconv_substr') ? 'iconv_substr' : 'mb_substr'; + if ($charset && $charset != RCUBE_CHARSET && function_exists('mb_internal_encoding')) { + mb_internal_encoding($charset); + } // Convert \r\n to \n, this is our line-separator $string = str_replace("\r\n", "\n", $string); $separator = "\n"; // must be 1 character length $result = array(); - while (($stringLength = $strlen_func($string, $charset)) > 0) { - $breakPos = $strpos_func($string, $separator, 0, $charset); + while (($stringLength = mb_strlen($string)) > 0) { + $breakPos = mb_strpos($string, $separator, 0); // quoted line (do not wrap) if ($wrap_quoted && $string[0] == '>') { @@ -612,7 +609,7 @@ class rcube_mime $cutLength = null; } else { - $subString = $substr_func($string, 0, $breakPos, $charset); + $subString = mb_substr($string, 0, $breakPos); $cutLength = $breakPos + 1; } } @@ -623,39 +620,34 @@ class rcube_mime $cutLength = null; } else { - $subString = $substr_func($string, 0, $breakPos, $charset); + $subString = mb_substr($string, 0, $breakPos); $cutLength = $breakPos + 1; } } else { - $subString = $substr_func($string, 0, $width, $charset); + $subString = mb_substr($string, 0, $width); // last line if ($breakPos === false && $subString === $string) { $cutLength = null; } else { - $nextChar = $substr_func($string, $width, 1, $charset); + $nextChar = mb_substr($string, $width, 1); if ($nextChar === ' ' || $nextChar === $separator) { - $afterNextChar = $substr_func($string, $width + 1, 1, $charset); + $afterNextChar = mb_substr($string, $width + 1, 1); if ($afterNextChar === false) { $subString .= $nextChar; } - $cutLength = $strlen_func($subString, $charset) + 1; + $cutLength = mb_strlen($subString) + 1; } else { - if ($strrpos_func[0] == 'm') { - $spacePos = $strrpos_func($subString, ' ', 0, $charset); - } - else { - $spacePos = $strrpos_func($subString, ' ', $charset); - } + $spacePos = mb_strrpos($subString, ' ', 0); if ($spacePos !== false) { - $subString = $substr_func($subString, 0, $spacePos, $charset); + $subString = mb_substr($subString, 0, $spacePos); $cutLength = $spacePos + 1; } else if ($cut === false && $breakPos === false) { @@ -663,19 +655,19 @@ class rcube_mime $cutLength = null; } else if ($cut === false) { - $spacePos = $strpos_func($string, ' ', 0, $charset); + $spacePos = mb_strpos($string, ' ', 0); if ($spacePos !== false && $spacePos < $breakPos) { - $subString = $substr_func($string, 0, $spacePos, $charset); + $subString = mb_substr($string, 0, $spacePos); $cutLength = $spacePos + 1; } else { - $subString = $substr_func($string, 0, $breakPos, $charset); + $subString = mb_substr($string, 0, $breakPos); $cutLength = $breakPos + 1; } } else { - $subString = $substr_func($subString, 0, $width, $charset); + $subString = mb_substr($subString, 0, $width); $cutLength = $width; } } @@ -685,13 +677,17 @@ class rcube_mime $result[] = $subString; if ($cutLength !== null) { - $string = $substr_func($string, $cutLength, ($stringLength - $cutLength), $charset); + $string = mb_substr($string, $cutLength, ($stringLength - $cutLength)); } else { break; } } + if ($charset && $charset != RCUBE_CHARSET && function_exists('mb_internal_encoding')) { + mb_internal_encoding(RCUBE_CHARSET); + } + return implode($break, $result); } diff --git a/program/lib/Roundcube/rcube_session.php b/program/lib/Roundcube/rcube_session.php index dedde2284..4e0682749 100644 --- a/program/lib/Roundcube/rcube_session.php +++ b/program/lib/Roundcube/rcube_session.php @@ -42,6 +42,7 @@ class rcube_session private $secret = ''; private $ip_check = false; private $logging = false; + private $storage; private $memcache; @@ -59,11 +60,14 @@ class rcube_session $this->set_lifetime($lifetime); // use memcache backend - if ($config->get('session_storage', 'db') == 'memcache') { + $this->storage = $config->get('session_storage', 'db'); + if ($this->storage == 'memcache') { $this->memcache = rcube::get_instance()->get_memcache(); // set custom functions for PHP session management if memcache is available if ($this->memcache) { + ini_set('session.serialize_handler', 'php'); + session_set_save_handler( array($this, 'open'), array($this, 'close'), @@ -79,7 +83,9 @@ class rcube_session true, true); } } - else { + else if ($this->storage != 'php') { + ini_set('session.serialize_handler', 'php'); + // set custom functions for PHP session management session_set_save_handler( array($this, 'open'), @@ -92,6 +98,22 @@ class rcube_session } + /** + * Wrapper for session_start() + */ + public function start() + { + session_start(); + + // copy some session properties to object vars + if ($this->storage == 'php') { + $this->key = session_id(); + $this->ip = $_SESSION['__IP']; + $this->changed = $_SESSION['__MTIME']; + } + } + + public function open($save_path, $session_name) { return true; @@ -116,6 +138,20 @@ class rcube_session /** + * Wrapper for session_write_close() + */ + public function write_close() + { + if ($this->storage == 'php') { + $_SESSION['__IP'] = $this->ip; + $_SESSION['__MTIME'] = time(); + } + + session_write_close(); + } + + + /** * Read session data from database * * @param string Session ID diff --git a/program/lib/Roundcube/rcube_storage.php b/program/lib/Roundcube/rcube_storage.php index 700d12ffb..b17291bdf 100644 --- a/program/lib/Roundcube/rcube_storage.php +++ b/program/lib/Roundcube/rcube_storage.php @@ -986,6 +986,6 @@ abstract class rcube_storage /** * Delete outdated cache entries */ - abstract function expunge_cache(); + abstract function cache_gc(); } // end class rcube_storage diff --git a/program/lib/Roundcube/rcube_user.php b/program/lib/Roundcube/rcube_user.php index 505b190d1..5e9c9af80 100644 --- a/program/lib/Roundcube/rcube_user.php +++ b/program/lib/Roundcube/rcube_user.php @@ -495,9 +495,9 @@ class rcube_user "INSERT INTO ".$dbh->table_name('users'). " (created, last_login, username, mail_host, language)". " VALUES (".$dbh->now().", ".$dbh->now().", ?, ?, ?)", - strip_newlines($data['user']), - strip_newlines($data['host']), - strip_newlines($data['language'])); + $data['user'], + $data['host'], + $data['language']); if ($user_id = $dbh->insert_id('users')) { // create rcube_user instance to make plugin hooks work @@ -517,7 +517,7 @@ class rcube_user if (empty($user_email)) { $user_email = strpos($data['user'], '@') ? $user : sprintf('%s@%s', $data['user'], $mail_domain); } - $email_list[] = strip_newlines($user_email); + $email_list[] = $user_email; } // identities_level check else if (count($email_list) > 1 && $rcube->config->get('identities_level', 0) > 1) { @@ -547,7 +547,6 @@ class rcube_user $record['name'] = $user_name != $record['email'] ? $user_name : ''; } - $record['name'] = strip_newlines($record['name']); $record['user_id'] = $user_id; $record['standard'] = $standard; diff --git a/program/lib/Roundcube/rcube_utils.php b/program/lib/Roundcube/rcube_utils.php index 8467107fe..29baa82f3 100644 --- a/program/lib/Roundcube/rcube_utils.php +++ b/program/lib/Roundcube/rcube_utils.php @@ -510,17 +510,24 @@ class rcube_utils */ public static function file2class($mimetype, $filename) { + $mimetype = strtolower($mimetype); + $filename = strtolower($filename); + list($primary, $secondary) = explode('/', $mimetype); $classes = array($primary ? $primary : 'unknown'); + if ($secondary) { $classes[] = $secondary; } - if (preg_match('/\.([a-z0-9]+)$/i', $filename, $m)) { - $classes[] = $m[1]; + + if (preg_match('/\.([a-z0-9]+)$/', $filename, $m)) { + if (!in_array($m[1], $classes)) { + $classes[] = $m[1]; + } } - return strtolower(join(" ", $classes)); + return join(" ", $classes); } @@ -726,7 +733,7 @@ class rcube_utils return mktime(0,0,0, intval($matches[2]), intval($matches[3]), intval($matches[1])); } else if (is_numeric($date)) { - return $date; + return (int) $date; } // Clean malformed data @@ -755,7 +762,7 @@ class rcube_utils $date = implode(' ', $d); } - return $ts; + return (int) $ts; } diff --git a/program/lib/Roundcube/rcube_washtml.php b/program/lib/Roundcube/rcube_washtml.php index a11371c61..6b2efcc78 100644 --- a/program/lib/Roundcube/rcube_washtml.php +++ b/program/lib/Roundcube/rcube_washtml.php @@ -113,10 +113,9 @@ class rcube_washtml 'type', 'rows', 'cols', 'disabled', 'readonly', 'checked', 'multiple', 'value' ); - /* Block elements which could be empty but cannot be returned in short form (<tag />) */ - static $block_elements = array('div', 'p', 'pre', 'blockquote', 'a', 'font', 'center', - 'table', 'ul', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'ol', 'dl', 'strong', - 'i', 'b', 'u', 'span', + /* Elements which could be empty and be returned in short form (<tag />) */ + static $void_elements = array('area', 'base', 'br', 'col', 'command', 'embed', 'hr', + 'img', 'input', 'keygen', 'link', 'meta', 'param', 'source', 'track', 'wbr' ); /* State for linked objects in HTML */ @@ -134,8 +133,8 @@ class rcube_washtml /* Ignore these HTML tags but process their content */ private $_ignore_elements = array(); - /* Block elements which could be empty but cannot be returned in short form (<tag />) */ - private $_block_elements = array(); + /* Elements which could be empty and be returned in short form (<tag />) */ + private $_void_elements = array(); /* Allowed HTML attributes */ private $_html_attribs = array(); @@ -152,9 +151,9 @@ class rcube_washtml $this->_html_elements = array_flip((array)$p['html_elements']) + array_flip(self::$html_elements) ; $this->_html_attribs = array_flip((array)$p['html_attribs']) + array_flip(self::$html_attribs); $this->_ignore_elements = array_flip((array)$p['ignore_elements']) + array_flip(self::$ignore_elements); - $this->_block_elements = array_flip((array)$p['block_elements']) + array_flip(self::$block_elements); + $this->_void_elements = array_flip((array)$p['void_elements']) + array_flip(self::$void_elements); - unset($p['html_elements'], $p['html_attribs'], $p['ignore_elements'], $p['block_elements']); + unset($p['html_elements'], $p['html_attribs'], $p['ignore_elements'], $p['void_elements']); $this->config = $p + array('show_washed' => true, 'allow_remote' => false, 'cid_map' => array()); } @@ -321,7 +320,7 @@ class rcube_washtml else if (isset($this->_html_elements[$tagName])) { $content = $this->dumpHtml($node, $level); $dump .= '<' . $tagName . $this->wash_attribs($node) . - ($content != '' || isset($this->_block_elements[$tagName]) ? ">$content</$tagName>" : ' />'); + ($content === '' && isset($this->_void_elements[$tagName]) ? ' />' : ">$content</$tagName>"); } else if (isset($this->_ignore_elements[$tagName])) { $dump .= '<!-- ' . htmlspecialchars($tagName, ENT_QUOTES) . ' not allowed -->'; diff --git a/program/localization/en_US/labels.inc b/program/localization/en_US/labels.inc index ab57007dd..8a8fa2c07 100644 --- a/program/localization/en_US/labels.inc +++ b/program/localization/en_US/labels.inc @@ -472,6 +472,7 @@ $labels['spellcheckignorenums'] = 'Ignore words with numbers'; $labels['spellcheckignorecaps'] = 'Ignore words with all letters capitalized'; $labels['addtodict'] = 'Add to dictionary'; $labels['mailtoprotohandler'] = 'Register protocol handler for mailto: links'; +$labels['standardwindows'] = 'Handle popups as standard windows'; $labels['forwardmode'] = 'Messages forwarding'; $labels['inline'] = 'inline'; $labels['asattachment'] = 'as attachment'; diff --git a/program/steps/mail/compose.inc b/program/steps/mail/compose.inc index 9ee57d31e..d3bc7fe72 100644 --- a/program/steps/mail/compose.inc +++ b/program/steps/mail/compose.inc @@ -327,7 +327,8 @@ foreach ($parts as $header) { $fvalue .= $v; if ($v = $MESSAGE->headers->cc) $fvalue .= (!empty($fvalue) ? $separator : '') . $v; - if ($v = $MESSAGE->headers->get('Sender', false)) + // Use Sender header (#1489011) + if (($v = $MESSAGE->headers->get('Sender', false)) && strpos($v, '-bounces@') === false) $fvalue .= (!empty($fvalue) ? $separator : '') . $v; // When To: and Reply-To: are the same we add From: address to the list (#1489037) @@ -474,6 +475,7 @@ function rcmail_compose_header_from($attrib) if (count($MESSAGE->identities)) { $a_signatures = array(); + $identities = array(); $separator = intval($RCMAIL->config->get('reply_mode')) > 0 && ($compose_mode == RCUBE_COMPOSE_REPLY || $compose_mode == RCUBE_COMPOSE_FORWARD) ? '---' : '-- '; @@ -511,12 +513,21 @@ function rcmail_compose_header_from($attrib) $a_signatures[$identity_id]['text'] = $text; $a_signatures[$identity_id]['html'] = $html; } + + // add bcc and reply-to + if (!empty($sql_arr['reply-to'])) { + $identities[$identity_id]['replyto'] = $sql_arr['reply-to']; + } + if (!empty($sql_arr['bcc'])) { + $identities[$identity_id]['bcc'] = $sql_arr['bcc']; + } } $out = $select_from->show($MESSAGE->compose['from']); // add signatures to client $OUTPUT->set_env('signatures', $a_signatures); + $OUTPUT->set_env('identities', $identities); } // no identities, display text input field else { @@ -1022,8 +1033,8 @@ function rcmail_write_compose_attachments(&$message, $bodyIsHtml) if ($part->ctype_primary == 'message' && $compose_mode == RCUBE_COMPOSE_REPLY) { continue; } - // skip inline images when forwarding in plain text - if ($part->content_id && !$bodyIsHtml && $compose_mode == RCUBE_COMPOSE_FORWARD) { + // skip inline images when forwarding + if ($part->content_id && $part->disposition == 'inline' && $compose_mode == RCUBE_COMPOSE_FORWARD) { continue; } diff --git a/program/steps/mail/func.inc b/program/steps/mail/func.inc index ab4b41155..1a687f508 100644 --- a/program/steps/mail/func.inc +++ b/program/steps/mail/func.inc @@ -1786,9 +1786,12 @@ function rcmail_identity_select($MESSAGE, $identities = null, $compose_mode = 'r // Try Return-Path if ($from_idx === null && ($return_path = $MESSAGE->headers->others['return-path'])) { foreach ($identities as $idx => $ident) { - if (strpos($return_path, str_replace('@', '=', $ident['email_ascii']).'@') !== false) { - $from_idx = $idx; - break; + $ident = str_replace('@', '=', $ident['email_ascii']) . '@'; + foreach ((array)$return_path as $path) { + if (strpos($path, $ident) !== false) { + $from_idx = $idx; + break 2; + } } } } diff --git a/program/steps/mail/sendmail.inc b/program/steps/mail/sendmail.inc index cb3a40524..cf22a2af9 100644 --- a/program/steps/mail/sendmail.inc +++ b/program/steps/mail/sendmail.inc @@ -391,10 +391,6 @@ if (!empty($mailcc)) { if (!empty($mailbcc)) { $headers['Bcc'] = $mailbcc; } -if (!empty($identity_arr['bcc']) && stripos($headers['Bcc'], $identity_arr['bcc']) === false) { - $headers['Bcc'] = ($headers['Bcc'] ? $headers['Bcc'].', ' : '') . $identity_arr['bcc']; - $RECIPIENT_COUNT ++; -} if (($max_recipients = (int) $RCMAIL->config->get('max_recipients')) > 0) { if ($RECIPIENT_COUNT > $max_recipients) { @@ -412,9 +408,6 @@ if (!empty($identity_arr['organization'])) { if (!empty($_POST['_replyto'])) { $headers['Reply-To'] = rcmail_email_input_format(get_input_value('_replyto', RCUBE_INPUT_POST, TRUE, $message_charset)); } -else if (!empty($identity_arr['reply-to'])) { - $headers['Reply-To'] = rcmail_email_input_format($identity_arr['reply-to'], false, true); -} if (!empty($headers['Reply-To'])) { $headers['Mail-Reply-To'] = $headers['Reply-To']; } diff --git a/program/steps/settings/func.inc b/program/steps/settings/func.inc index 2cbfed16c..cbe3bc954 100644 --- a/program/steps/settings/func.inc +++ b/program/steps/settings/func.inc @@ -95,13 +95,13 @@ function get_form_tags($attrib, $action, $id = null, $hidden = null) if (empty($EDIT_FORM)) { $request_key = $action . (isset($id) ? '.'.$id : ''); $form_start = $RCMAIL->output->request_form(array( - 'name' => 'form', - 'method' => 'post', - 'task' => $RCMAIL->task, - 'action' => $action, - 'request' => $request_key, - 'noclose' => true - ) + $attrib); + 'name' => 'form', + 'method' => 'post', + 'task' => $RCMAIL->task, + 'action' => $action, + 'request' => $request_key, + 'noclose' => true + ) + $attrib); if (is_array($hidden)) { $hiddenfields = new html_hiddenfield($hidden); @@ -152,6 +152,8 @@ function rcmail_user_prefs($current=null) $blocks = array( 'main' => array('name' => Q(rcube_label('mainoptions'))), + 'skin' => array('name' => Q(rcube_label('skin'))), + 'browser' => array('name' => Q(rcube_label('browseroptions'))), ); // language selection @@ -263,8 +265,6 @@ function rcmail_user_prefs($current=null) $field_id = 'rcmfd_skin'; $input_skin = new html_radiobutton(array('name'=>'_skin')); - $blocks['skin'] = array('name' => Q(rcube_label('skin')),); - foreach($skins as $skin) { $thumbnail = "./skins/$skin/thumbnail.png"; if (!is_file($thumbnail)) @@ -290,17 +290,27 @@ function rcmail_user_prefs($current=null) } } + // standard_windows option decides if new windows should be + // opened as popups or standard windows (which can be handled by browsers as tabs) + if (!isset($no_override['standard_windows'])) { + $field_id = 'rcmfd_standard_windows'; + $checkbox = new html_checkbox(array('name' => '_standard_windows', 'id' => $field_id, 'value' => 1)); + + $blocks['browser']['options']['standard_windows'] = array( + 'title' => html::label($field_id, Q(rcube_label('standardwindows'))), + 'content' => $checkbox->show($config['standard_windows']?1:0), + ); + } + + $product_name = $RCMAIL->config->get('product_name', 'Roundcube Webmail'); $RCMAIL->output->add_script(sprintf("%s.check_protocol_handler('%s', '#mailtoprotohandler');", JS_OBJECT_NAME, JQ($product_name)), 'foot'); - $blocks['browser'] = array( - 'name' => Q(rcube_label('browseroptions')), - 'options' => array('mailtoprotohandler' => array( - 'content' => html::a(array( - 'href' => '#', - 'id' => 'mailtoprotohandler'), Q(rcube_label('mailtoprotohandler'))), - )), + $blocks['browser']['options']['mailtoprotohandler'] = array( + 'content' => html::a(array( + 'href' => '#', + 'id' => 'mailtoprotohandler'), Q(rcube_label('mailtoprotohandler'))), ); break; @@ -810,13 +820,13 @@ function rcmail_user_prefs($current=null) $blocks['main']['options']['sent_mbox'] = array( 'title' => Q(rcube_label('sent')), 'content' => $select->show($config['sent_mbox'], array('name' => "_sent_mbox")), - ); + ); if (!isset($no_override['junk_mbox'])) $blocks['main']['options']['junk_mbox'] = array( 'title' => Q(rcube_label('junk')), 'content' => $select->show($config['junk_mbox'], array('name' => "_junk_mbox")), - ); + ); if (!isset($no_override['trash_mbox'])) $blocks['main']['options']['trash_mbox'] = array( @@ -918,7 +928,7 @@ function rcmail_user_prefs($current=null) foreach ($data['blocks'] as $block) { if (!empty($block['content']) || !empty($block['options'])) { $found = true; - break; + break; } } @@ -940,7 +950,7 @@ function rcmail_get_skins() $dir = opendir($path); if (!$dir) - return false; + return false; while (($file = readdir($dir)) !== false) { diff --git a/program/steps/settings/save_prefs.inc b/program/steps/settings/save_prefs.inc index caaf6466c..19edb41d4 100644 --- a/program/steps/settings/save_prefs.inc +++ b/program/steps/settings/save_prefs.inc @@ -34,6 +34,7 @@ switch ($CURR_SECTION) 'time_format' => isset($_POST['_time_format']) ? get_input_value('_time_format', RCUBE_INPUT_POST) : ($CONFIG['time_format'] ? $CONFIG['time_format'] : 'H:i'), 'prettydate' => isset($_POST['_pretty_date']) ? TRUE : FALSE, 'refresh_interval' => isset($_POST['_refresh_interval']) ? intval($_POST['_refresh_interval'])*60 : $CONFIG['refresh_interval'], + 'standard_windows' => isset($_POST['_standard_windows']) ? TRUE : FALSE, 'skin' => isset($_POST['_skin']) ? get_input_value('_skin', RCUBE_INPUT_POST) : $CONFIG['skin'], ); diff --git a/skins/classic/functions.js b/skins/classic/functions.js index d980627aa..5dd332ab7 100644 --- a/skins/classic/functions.js +++ b/skins/classic/functions.js @@ -492,14 +492,18 @@ switch_preview_pane: function(elem) /* Message composing */ init_compose_form: function() { - var f, field, fields = ['cc', 'bcc', 'replyto', 'followupto'], + var f, v, field, fields = ['cc', 'bcc', 'replyto', 'followupto'], div = document.getElementById('compose-div'), headers_div = document.getElementById('compose-headers-div'); // Show input elements with non-empty value for (f=0; f<fields.length; f++) { - if ((field = $('#_'+fields[f])) && field.length && field.val() != '') - rcmail_ui.show_header_form(fields[f]); + v = fields[f]; field = $('#_'+v); + if (field.length) { + field.on('change', {v:v}, function(e) { if (this.value) rcmail_ui.show_header_form(e.data.v); }); + if (field.val() != '') + rcmail_ui.show_header_form(v); + } } // prevent from form data loss when pressing ESC key in IE @@ -630,9 +634,117 @@ enable_command: function(p) }; /** - * Scroller + * Roundcube generic layer (floating box) class + * + * @constructor */ +function rcube_layer(id, attributes) +{ + this.name = id; + + // create a new layer in the current document + this.create = function(arg) + { + var l = (arg.x) ? arg.x : 0, + t = (arg.y) ? arg.y : 0, + w = arg.width, + h = arg.height, + z = arg.zindex, + vis = arg.vis, + parent = arg.parent, + obj = document.createElement('DIV'); + + obj.id = this.name; + obj.style.position = 'absolute'; + obj.style.visibility = (vis) ? (vis==2) ? 'inherit' : 'visible' : 'hidden'; + obj.style.left = l+'px'; + obj.style.top = t+'px'; + if (w) + obj.style.width = w.toString().match(/\%$/) ? w : w+'px'; + if (h) + obj.style.height = h.toString().match(/\%$/) ? h : h+'px'; + if (z) + obj.style.zIndex = z; + + if (parent) + parent.appendChild(obj); + else + document.body.appendChild(obj); + + this.elm = obj; + }; + + // create new layer + if (attributes != null) { + this.create(attributes); + this.name = this.elm.id; + } + else // just refer to the object + this.elm = document.getElementById(id); + + if (!this.elm) + return false; + + + // ********* layer object properties ********* + + this.css = this.elm.style; + this.event = this.elm; + this.width = this.elm.offsetWidth; + this.height = this.elm.offsetHeight; + this.x = parseInt(this.elm.offsetLeft); + this.y = parseInt(this.elm.offsetTop); + this.visible = (this.css.visibility=='visible' || this.css.visibility=='show' || this.css.visibility=='inherit') ? true : false; + + + // ********* layer object methods ********* + + // move the layer to a specific position + this.move = function(x, y) + { + this.x = x; + this.y = y; + this.css.left = Math.round(this.x)+'px'; + this.css.top = Math.round(this.y)+'px'; + }; + // change the layers width and height + this.resize = function(w,h) + { + this.css.width = w+'px'; + this.css.height = h+'px'; + this.width = w; + this.height = h; + }; + + // show or hide the layer + this.show = function(a) + { + if(a == 1) { + this.css.visibility = 'visible'; + this.visible = true; + } + else if(a == 2) { + this.css.visibility = 'inherit'; + this.visible = true; + } + else { + this.css.visibility = 'hidden'; + this.visible = false; + } + }; + + // write new content into a Layer + this.write = function(cont) + { + this.elm.innerHTML = cont; + }; + +}; + +/** + * Scroller + */ function rcmail_scroller(list, top, bottom) { var ref = this; diff --git a/skins/larry/mail.css b/skins/larry/mail.css index b2f1d786a..348a0e894 100644 --- a/skins/larry/mail.css +++ b/skins/larry/mail.css @@ -54,6 +54,10 @@ border-top: none; } +#composeview-right #mailview-bottom { + border-radius: 0 0 4px 4px; +} + #folderlist-header { width: 100%; height: 12px; diff --git a/skins/larry/ui.js b/skins/larry/ui.js index 693448097..6f9d30daa 100644 --- a/skins/larry/ui.js +++ b/skins/larry/ui.js @@ -43,6 +43,7 @@ function rcube_mail_ui() this.show_uploadform = show_uploadform; this.show_header_row = show_header_row; this.hide_header_row = hide_header_row; + this.update_quota = update_quota; // set minimal mode on small screens (don't wait for document.ready) @@ -111,10 +112,14 @@ function rcube_mail_ui() layout_composeview(); // Show input elements with non-empty value - var field, fields = ['cc', 'bcc', 'replyto', 'followupto']; - for (var f=0; f < fields.length; f++) { - if ((field = $('#_'+fields[f])) && field.length && field.val() != '') - show_header_row(fields[f], true); + var f, v, field, fields = ['cc', 'bcc', 'replyto', 'followupto']; + for (f=0; f < fields.length; f++) { + v = fields[f]; field = $('#_'+v); + if (field.length) { + field.on('change', {v: v}, function(e) { if (this.value) show_header_row(e.data.v, true); }); + if (field.val() != '') + show_header_row(v, true); + } } $('#composeoptionstoggle').click(function(){ @@ -237,7 +242,8 @@ function rcube_mail_ui() parent.css('position', 'relative'); // re-set original select width to fix click action and options width in some browsers - select.width(overlay.width()); + if (!bw.mz) + select.width(overlay.width()); }); $(document.body) diff --git a/tests/Framework/Utils.php b/tests/Framework/Utils.php index 7c1e92ac8..abfb7cb65 100644 --- a/tests/Framework/Utils.php +++ b/tests/Framework/Utils.php @@ -195,6 +195,23 @@ class Framework_Utils extends PHPUnit_Framework_TestCase } /** + * Check rcube_utils::explode_quoted_string() + */ + function test_explode_quoted_string() + { + $data = array( + '"a,b"' => array('"a,b"'), + '"a,b","c,d"' => array('"a,b"','"c,d"'), + '"a,\\"b",d' => array('"a,\\"b"', 'd'), + ); + + foreach ($data as $text => $res) { + $result = rcube_utils::explode_quoted_string(',', $text); + $this->assertSame($res, $result); + } + } + + /** * Check rcube_utils::explode_quoted_string() compat. with explode() */ function test_explode_quoted_string_compat() @@ -229,4 +246,52 @@ class Framework_Utils extends PHPUnit_Framework_TestCase } } + /** + * rcube:utils::file2class() + */ + function test_file2class() + { + $test = array( + array('', '', 'unknown'), + array('text', 'text', 'text'), + array('image/png', 'image.png', 'image png'), + ); + + foreach ($test as $v) { + $result = rcube_utils::file2class($v[0], $v[1]); + $this->assertSame($v[2], $result); + } + } + + /** + * rcube:utils::strtotime() + */ + function test_strtotime() + { + $test = array( + '1' => 1, + '' => 0, + ); + + foreach ($test as $datetime => $ts) { + $result = rcube_utils::strtotime($datetime); + $this->assertSame($ts, $result); + } + } + + /** + * rcube:utils::normalize _string() + */ + function test_normalize_string() + { + $test = array( + '' => '', + 'abc def' => 'abc def', + ); + + foreach ($test as $input => $output) { + $result = rcube_utils::normalize_string($input); + $this->assertSame($output, $result); + } + } } diff --git a/tests/Framework/Washtml.php b/tests/Framework/Washtml.php index 526b93301..cb7234314 100644 --- a/tests/Framework/Washtml.php +++ b/tests/Framework/Washtml.php @@ -55,4 +55,17 @@ class Framework_Washtml extends PHPUnit_Framework_TestCase $this->assertEquals('<!-- html ignored --><!-- body ignored --><p>test</p>', $washed, "HTML invalid comments (#1487759)"); } + /** + * Test fixing of invalid self-closing elements (#1489137) + */ + function test_self_closing() + { + $html = "<textarea>test"; + + $washer = new rcube_washtml; + $washed = $washer->wash($html); + + $this->assertRegExp('|<textarea>test</textarea>|', $washed, "Self-closing textarea (#1489137)"); + } + } |