summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG17
-rw-r--r--SQL/mssql.initial.sql47
-rw-r--r--SQL/mssql/2013061000.sql44
-rw-r--r--SQL/mysql.initial.sql24
-rw-r--r--SQL/mysql/2013061000.sql24
-rw-r--r--SQL/postgres.initial.sql30
-rw-r--r--SQL/postgres/2013061000.sql24
-rw-r--r--SQL/sqlite.initial.sql20
-rw-r--r--SQL/sqlite/2013061000.sql48
-rwxr-xr-xbin/gc.sh27
-rw-r--r--config/main.inc.php.dist30
-rw-r--r--plugins/acl/localization/en_US.inc2
-rw-r--r--plugins/managesieve/localization/tr_TR.inc4
-rw-r--r--plugins/virtuser_file/virtuser_file.php88
-rw-r--r--plugins/zipdownload/zipdownload.php6
-rw-r--r--program/include/bc.php12
-rw-r--r--program/include/rcmail.php11
-rw-r--r--program/include/rcmail_output_html.php3
-rw-r--r--program/js/app.js106
-rw-r--r--program/js/common.js115
-rw-r--r--program/lib/Roundcube/bootstrap.php26
-rw-r--r--program/lib/Roundcube/rcube.php53
-rw-r--r--program/lib/Roundcube/rcube_cache.php76
-rw-r--r--program/lib/Roundcube/rcube_cache_shared.php57
-rw-r--r--program/lib/Roundcube/rcube_config.php23
-rw-r--r--program/lib/Roundcube/rcube_db.php118
-rw-r--r--program/lib/Roundcube/rcube_db_mssql.php32
-rw-r--r--program/lib/Roundcube/rcube_db_mysql.php21
-rw-r--r--program/lib/Roundcube/rcube_db_pgsql.php66
-rw-r--r--program/lib/Roundcube/rcube_db_sqlite.php47
-rw-r--r--program/lib/Roundcube/rcube_db_sqlsrv.php29
-rw-r--r--program/lib/Roundcube/rcube_image.php8
-rw-r--r--program/lib/Roundcube/rcube_imap.php54
-rw-r--r--program/lib/Roundcube/rcube_imap_cache.php156
-rw-r--r--program/lib/Roundcube/rcube_ldap.php8
-rw-r--r--program/lib/Roundcube/rcube_mime.php52
-rw-r--r--program/lib/Roundcube/rcube_session.php40
-rw-r--r--program/lib/Roundcube/rcube_storage.php2
-rw-r--r--program/lib/Roundcube/rcube_user.php9
-rw-r--r--program/lib/Roundcube/rcube_utils.php17
-rw-r--r--program/lib/Roundcube/rcube_washtml.php17
-rw-r--r--program/localization/en_US/labels.inc1
-rw-r--r--program/steps/mail/compose.inc17
-rw-r--r--program/steps/mail/func.inc9
-rw-r--r--program/steps/mail/sendmail.inc7
-rw-r--r--program/steps/settings/func.inc50
-rw-r--r--program/steps/settings/save_prefs.inc1
-rw-r--r--skins/classic/functions.js120
-rw-r--r--skins/larry/mail.css4
-rw-r--r--skins/larry/ui.js16
-rw-r--r--tests/Framework/Utils.php65
-rw-r--r--tests/Framework/Washtml.php13
52 files changed, 1291 insertions, 605 deletions
diff --git a/CHANGELOG b/CHANGELOG
index 18f26ba56..bcc6e20f8 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -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)");
+ }
+
}