Changeset 5190 in subversion
- Timestamp:
- Sep 7, 2011 7:07:03 AM (21 months ago)
- Location:
- trunk/roundcubemail
- Files:
-
- 17 edited
- 1 copied
-
. (modified) (1 prop)
-
CHANGELOG (modified) (1 diff)
-
SQL/mssql.initial.sql (modified) (7 diffs)
-
SQL/mssql.upgrade.sql (modified) (1 diff)
-
SQL/mysql.initial.sql (modified) (2 diffs)
-
SQL/mysql.update.sql (modified) (1 diff)
-
SQL/postgres.initial.sql (modified) (3 diffs)
-
SQL/postgres.update.sql (modified) (1 diff)
-
SQL/sqlite.initial.sql (modified) (4 diffs)
-
SQL/sqlite.update.sql (modified) (2 diffs)
-
program/include/main.inc (modified) (1 diff)
-
program/include/rcube_imap.php (modified) (73 diffs)
-
program/include/rcube_imap_cache.php (copied) (copied from branches/devel-mcache/roundcubemail/program/include/rcube_imap_cache.php)
-
program/include/rcube_imap_generic.php (modified) (63 diffs)
-
program/include/rcube_message.php (modified) (2 diffs)
-
program/include/rcube_mime_struct.php (modified) (5 diffs)
-
program/steps/mail/func.inc (modified) (1 diff)
-
program/steps/mail/show.inc (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
-
trunk/roundcubemail
- Property svn:mergeinfo changed
/branches/devel-mcache/roundcubemail (added) merged: 5028-5031,5042-5044,5047-5048,5052-5054,5056,5080-5083,5105,5114,5131
- Property svn:mergeinfo changed
-
trunk/roundcubemail/CHANGELOG
r5185 r5190 2 2 =========================== 3 3 4 - Rewritten messages caching: 5 Indexes are stored in a separate table, so there's no need to store all messages in a folder 6 Added threads data caching 7 Flags are stored separately, so flag change doesn't cause DELETE+INSERT, just UPDATE 8 - Partial QRESYNC support 9 - Improved FETCH response handling 10 - Improvements in response tokenization method 4 11 - Use 'From' and 'To' labels instead of 'Sender' and 'Recipient' 5 12 - Fix username case-insensitivity issue in MySQL (#1488021) -
trunk/roundcubemail/SQL/mssql.initial.sql
r5182 r5190 5 5 [created] [datetime] NOT NULL , 6 6 [data] [text] COLLATE Latin1_General_CI_AI NOT NULL 7 ) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY] 8 GO 9 10 CREATE TABLE [dbo].[cache_index] ( 11 [user_id] [int] NOT NULL , 12 [mailbox] [varchar] (128) COLLATE Latin1_General_CI_AI NOT NULL , 13 [changed] [datetime] NOT NULL , 14 [data] [text] COLLATE Latin1_General_CI_AI NOT NULL 15 ) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY] 16 GO 17 18 CREATE TABLE [dbo].[cache_thread] ( 19 [user_id] [int] NOT NULL , 20 [mailbox] [varchar] (128) COLLATE Latin1_General_CI_AI NOT NULL , 21 [changed] [datetime] NOT NULL , 22 [data] [text] COLLATE Latin1_General_CI_AI NOT NULL 23 ) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY] 24 GO 25 26 CREATE TABLE [dbo].[cache_messages] ( 27 [user_id] [int] NOT NULL , 28 [mailbox] [varchar] (128) COLLATE Latin1_General_CI_AI NOT NULL , 29 [uid] [int] NOT NULL , 30 [changed] [datetime] NOT NULL , 31 [data] [text] COLLATE Latin1_General_CI_AI NOT NULL 32 [seen] [char](1) NOT NULL , 33 [deleted] [char](1) NOT NULL , 34 [answered] [char](1) NOT NULL , 35 [forwarded] [char](1) NOT NULL , 36 [flagged] [char](1) NOT NULL , 37 [mdnsent] [char](1) NOT NULL , 7 38 ) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY] 8 39 GO … … 54 85 GO 55 86 56 CREATE TABLE [dbo].[messages] (57 [message_id] [int] IDENTITY (1, 1) NOT NULL ,58 [user_id] [int] NOT NULL ,59 [del] [tinyint] NOT NULL ,60 [cache_key] [varchar] (128) COLLATE Latin1_General_CI_AI NOT NULL ,61 [created] [datetime] NOT NULL ,62 [idx] [int] NOT NULL ,63 [uid] [int] NOT NULL ,64 [subject] [varchar] (255) COLLATE Latin1_General_CI_AI NOT NULL ,65 [from] [varchar] (255) COLLATE Latin1_General_CI_AI NOT NULL ,66 [to] [varchar] (255) COLLATE Latin1_General_CI_AI NOT NULL ,67 [cc] [varchar] (255) COLLATE Latin1_General_CI_AI NOT NULL ,68 [date] [datetime] NOT NULL ,69 [size] [int] NOT NULL ,70 [headers] [text] COLLATE Latin1_General_CI_AI NOT NULL ,71 [structure] [text] COLLATE Latin1_General_CI_AI NULL72 ) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]73 GO74 75 87 CREATE TABLE [dbo].[session] ( 76 88 [sess_id] [varchar] (32) COLLATE Latin1_General_CI_AI NOT NULL , … … 117 129 GO 118 130 131 ALTER TABLE [dbo].[cache_index] WITH NOCHECK ADD 132 PRIMARY KEY CLUSTERED 133 ( 134 [user_id],[mailbox] 135 ) ON [PRIMARY] 136 GO 137 138 ALTER TABLE [dbo].[cache_thread] WITH NOCHECK ADD 139 PRIMARY KEY CLUSTERED 140 ( 141 [user_id],[mailbox] 142 ) ON [PRIMARY] 143 GO 144 145 ALTER TABLE [dbo].[cache_messages] WITH NOCHECK ADD 146 PRIMARY KEY CLUSTERED 147 ( 148 [user_id],[mailbox],[uid] 149 ) ON [PRIMARY] 150 GO 151 119 152 ALTER TABLE [dbo].[contacts] WITH NOCHECK ADD 120 153 CONSTRAINT [PK_contacts_contact_id] PRIMARY KEY CLUSTERED … … 142 175 ( 143 176 [identity_id] 144 ) ON [PRIMARY]145 GO146 147 ALTER TABLE [dbo].[messages] WITH NOCHECK ADD148 PRIMARY KEY CLUSTERED149 (150 [message_id]151 177 ) ON [PRIMARY] 152 178 GO … … 186 212 187 213 CREATE INDEX [IX_cache_created] ON [dbo].[cache]([created]) ON [PRIMARY] 214 GO 215 216 ALTER TABLE [dbo].[cache_index] ADD 217 CONSTRAINT [DF_cache_index_changed] DEFAULT (getdate()) FOR [changed] 218 GO 219 220 CREATE INDEX [IX_cache_index_user_id] ON [dbo].[cache_index]([user_id]) ON [PRIMARY] 221 GO 222 223 ALTER TABLE [dbo].[cache_thread] ADD 224 CONSTRAINT [DF_cache_thread_changed] DEFAULT (getdate()) FOR [changed] 225 GO 226 227 CREATE INDEX [IX_cache_thread_user_id] ON [dbo].[cache_thread]([user_id]) ON [PRIMARY] 228 GO 229 230 ALTER TABLE [dbo].[cache_messages] ADD 231 CONSTRAINT [DF_cache_messages_changed] DEFAULT (getdate()) FOR [changed] 232 CONSTRAINT [DF_cache_messages_seen] DEFAULT (0) FOR [seen], 233 CONSTRAINT [DF_cache_messages_deleted] DEFAULT (0) FOR [deleted], 234 CONSTRAINT [DF_cache_messages_answered] DEFAULT (0) FOR [answered], 235 CONSTRAINT [DF_cache_messages_forwarded] DEFAULT (0) FOR [forwarded], 236 CONSTRAINT [DF_cache_messages_flagged] DEFAULT (0) FOR [flagged], 237 CONSTRAINT [DF_cache_messages_mdnsent] DEFAULT (0) FOR [mdnsent], 238 GO 239 240 CREATE INDEX [IX_cache_messages_user_id] ON [dbo].[cache_messages]([user_id]) ON [PRIMARY] 188 241 GO 189 242 … … 239 292 GO 240 293 241 ALTER TABLE [dbo].[messages] ADD242 CONSTRAINT [DF_messages_user_id] DEFAULT (0) FOR [user_id],243 CONSTRAINT [DF_messages_del] DEFAULT (0) FOR [del],244 CONSTRAINT [DF_messages_cache_key] DEFAULT ('') FOR [cache_key],245 CONSTRAINT [DF_messages_created] DEFAULT (getdate()) FOR [created],246 CONSTRAINT [DF_messages_idx] DEFAULT (0) FOR [idx],247 CONSTRAINT [DF_messages_uid] DEFAULT (0) FOR [uid],248 CONSTRAINT [DF_messages_subject] DEFAULT ('') FOR [subject],249 CONSTRAINT [DF_messages_from] DEFAULT ('') FOR [from],250 CONSTRAINT [DF_messages_to] DEFAULT ('') FOR [to],251 CONSTRAINT [DF_messages_cc] DEFAULT ('') FOR [cc],252 CONSTRAINT [DF_messages_date] DEFAULT (getdate()) FOR [date],253 CONSTRAINT [DF_messages_size] DEFAULT (0) FOR [size]254 GO255 256 CREATE INDEX [IX_messages_user_id] ON [dbo].[messages]([user_id]) ON [PRIMARY]257 GO258 259 CREATE INDEX [IX_messages_cache_key] ON [dbo].[messages]([cache_key]) ON [PRIMARY]260 GO261 262 CREATE INDEX [IX_messages_uid] ON [dbo].[messages]([uid]) ON [PRIMARY]263 GO264 265 CREATE INDEX [IX_messages_created] ON [dbo].[messages]([created]) ON [PRIMARY]266 GO267 268 294 ALTER TABLE [dbo].[session] ADD 269 295 CONSTRAINT [DF_session_sess_id] DEFAULT ('') FOR [sess_id], … … 319 345 GO 320 346 321 ALTER TABLE [dbo].[messages] ADD CONSTRAINT [FK_messages_user_id] 347 ALTER TABLE [dbo].[cache_index] ADD CONSTRAINT [FK_cache_index_user_id] 348 FOREIGN KEY ([user_id]) REFERENCES [dbo].[users] ([user_id]) 349 ON DELETE CASCADE ON UPDATE CASCADE 350 GO 351 352 ALTER TABLE [dbo].[cache_thread] ADD CONSTRAINT [FK_cache_thread_user_id] 353 FOREIGN KEY ([user_id]) REFERENCES [dbo].[users] ([user_id]) 354 ON DELETE CASCADE ON UPDATE CASCADE 355 GO 356 357 ALTER TABLE [dbo].[cache_messages] ADD CONSTRAINT [FK_cache_messages_user_id] 322 358 FOREIGN KEY ([user_id]) REFERENCES [dbo].[users] ([user_id]) 323 359 ON DELETE CASCADE ON UPDATE CASCADE -
trunk/roundcubemail/SQL/mssql.upgrade.sql
r5182 r5190 152 152 GO 153 153 154 DROP TABLE [dbo].[messages] 155 GO 156 CREATE TABLE [dbo].[cache_index] ( 157 [user_id] [int] NOT NULL , 158 [mailbox] [varchar] (128) COLLATE Latin1_General_CI_AI NOT NULL , 159 [changed] [datetime] NOT NULL , 160 [data] [text] COLLATE Latin1_General_CI_AI NOT NULL 161 ) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY] 162 GO 163 164 CREATE TABLE [dbo].[cache_thread] ( 165 [user_id] [int] NOT NULL , 166 [mailbox] [varchar] (128) COLLATE Latin1_General_CI_AI NOT NULL , 167 [changed] [datetime] NOT NULL , 168 [data] [text] COLLATE Latin1_General_CI_AI NOT NULL 169 ) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY] 170 GO 171 172 CREATE TABLE [dbo].[cache_messages] ( 173 [user_id] [int] NOT NULL , 174 [mailbox] [varchar] (128) COLLATE Latin1_General_CI_AI NOT NULL , 175 [uid] [int] NOT NULL , 176 [changed] [datetime] NOT NULL , 177 [data] [text] COLLATE Latin1_General_CI_AI NOT NULL 178 [seen] [char](1) NOT NULL , 179 [deleted] [char](1) NOT NULL , 180 [answered] [char](1) NOT NULL , 181 [forwarded] [char](1) NOT NULL , 182 [flagged] [char](1) NOT NULL , 183 [mdnsent] [char](1) NOT NULL , 184 ) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY] 185 GO 186 187 ALTER TABLE [dbo].[cache_index] WITH NOCHECK ADD 188 PRIMARY KEY CLUSTERED 189 ( 190 [user_id],[mailbox] 191 ) ON [PRIMARY] 192 GO 193 194 ALTER TABLE [dbo].[cache_thread] WITH NOCHECK ADD 195 PRIMARY KEY CLUSTERED 196 ( 197 [user_id],[mailbox] 198 ) ON [PRIMARY] 199 GO 200 201 ALTER TABLE [dbo].[cache_messages] WITH NOCHECK ADD 202 PRIMARY KEY CLUSTERED 203 ( 204 [user_id],[mailbox],[uid] 205 ) ON [PRIMARY] 206 GO 207 208 ALTER TABLE [dbo].[cache_index] ADD 209 CONSTRAINT [DF_cache_index_changed] DEFAULT (getdate()) FOR [changed] 210 GO 211 212 CREATE INDEX [IX_cache_index_user_id] ON [dbo].[cache_index]([user_id]) ON [PRIMARY] 213 GO 214 215 ALTER TABLE [dbo].[cache_thread] ADD 216 CONSTRAINT [DF_cache_thread_changed] DEFAULT (getdate()) FOR [changed] 217 GO 218 219 CREATE INDEX [IX_cache_thread_user_id] ON [dbo].[cache_thread]([user_id]) ON [PRIMARY] 220 GO 221 222 ALTER TABLE [dbo].[cache_messages] ADD 223 CONSTRAINT [DF_cache_messages_changed] DEFAULT (getdate()) FOR [changed] 224 CONSTRAINT [DF_cache_messages_seen] DEFAULT (0) FOR [seen], 225 CONSTRAINT [DF_cache_messages_deleted] DEFAULT (0) FOR [deleted], 226 CONSTRAINT [DF_cache_messages_answered] DEFAULT (0) FOR [answered], 227 CONSTRAINT [DF_cache_messages_forwarded] DEFAULT (0) FOR [forwarded], 228 CONSTRAINT [DF_cache_messages_flagged] DEFAULT (0) FOR [flagged], 229 CONSTRAINT [DF_cache_messages_mdnsent] DEFAULT (0) FOR [mdnsent], 230 GO 231 232 CREATE INDEX [IX_cache_messages_user_id] ON [dbo].[cache_messages]([user_id]) ON [PRIMARY] 233 GO 234 235 ALTER TABLE [dbo].[cache_index] ADD CONSTRAINT [FK_cache_index_user_id] 236 FOREIGN KEY ([user_id]) REFERENCES [dbo].[users] ([user_id]) 237 ON DELETE CASCADE ON UPDATE CASCADE 238 GO 239 240 ALTER TABLE [dbo].[cache_thread] ADD CONSTRAINT [FK_cache_thread_user_id] 241 FOREIGN KEY ([user_id]) REFERENCES [dbo].[users] ([user_id]) 242 ON DELETE CASCADE ON UPDATE CASCADE 243 GO 244 245 ALTER TABLE [dbo].[cache_messages] ADD CONSTRAINT [FK_cache_messages_user_id] 246 FOREIGN KEY ([user_id]) REFERENCES [dbo].[users] ([user_id]) 247 ON DELETE CASCADE ON UPDATE CASCADE 248 GO 249 -
trunk/roundcubemail/SQL/mysql.initial.sql
r5183 r5190 34 34 35 35 36 -- Table structure for table `messages`37 38 CREATE TABLE `messages` (39 `message_id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,40 `user_id` int(10) UNSIGNED NOT NULL DEFAULT '0',41 `del` tinyint(1) NOT NULL DEFAULT '0',42 `cache_key` varchar(128) /*!40101 CHARACTER SET ascii COLLATE ascii_general_ci */ NOT NULL,43 `created` datetime NOT NULL DEFAULT '1000-01-01 00:00:00',44 `idx` int(11) UNSIGNED NOT NULL DEFAULT '0',45 `uid` int(11) UNSIGNED NOT NULL DEFAULT '0',46 `subject` varchar(255) NOT NULL,47 `from` varchar(255) NOT NULL,48 `to` varchar(255) NOT NULL,49 `cc` varchar(255) NOT NULL,50 `date` datetime NOT NULL DEFAULT '1000-01-01 00:00:00',51 `size` int(11) UNSIGNED NOT NULL DEFAULT '0',52 `headers` text NOT NULL,53 `structure` text,54 PRIMARY KEY(`message_id`),55 CONSTRAINT `user_id_fk_messages` FOREIGN KEY (`user_id`)56 REFERENCES `users`(`user_id`) ON DELETE CASCADE ON UPDATE CASCADE,57 INDEX `created_index` (`created`),58 INDEX `index_index` (`user_id`, `cache_key`, `idx`),59 UNIQUE `uniqueness` (`user_id`, `cache_key`, `uid`)60 ) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;61 62 63 36 -- Table structure for table `cache` 64 37 … … 74 47 INDEX `created_index` (`created`), 75 48 INDEX `user_cache_index` (`user_id`,`cache_key`) 49 ) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */; 50 51 52 -- Table structure for table `cache_index` 53 54 CREATE TABLE `cache_index` ( 55 `user_id` int(10) UNSIGNED NOT NULL DEFAULT '0', 56 `mailbox` varchar(255) BINARY NOT NULL, 57 `changed` datetime NOT NULL DEFAULT '1000-01-01 00:00:00', 58 `data` longtext NOT NULL, 59 CONSTRAINT `user_id_fk_cache_index` FOREIGN KEY (`user_id`) 60 REFERENCES `users`(`user_id`) ON DELETE CASCADE ON UPDATE CASCADE, 61 INDEX `changed_index` (`changed`), 62 PRIMARY KEY (`user_id`, `mailbox`) 63 ) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */; 64 65 66 -- Table structure for table `cache_thread` 67 68 CREATE TABLE `cache_thread` ( 69 `user_id` int(10) UNSIGNED NOT NULL DEFAULT '0', 70 `mailbox` varchar(255) BINARY NOT NULL, 71 `changed` datetime NOT NULL DEFAULT '1000-01-01 00:00:00', 72 `data` longtext NOT NULL, 73 CONSTRAINT `user_id_fk_cache_thread` FOREIGN KEY (`user_id`) 74 REFERENCES `users`(`user_id`) ON DELETE CASCADE ON UPDATE CASCADE, 75 INDEX `changed_index` (`changed`), 76 PRIMARY KEY (`user_id`, `mailbox`) 77 ) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */; 78 79 80 -- Table structure for table `cache_messages` 81 82 CREATE TABLE `cache_messages` ( 83 `user_id` int(10) UNSIGNED NOT NULL DEFAULT '0', 84 `mailbox` varchar(255) BINARY NOT NULL, 85 `uid` int(11) UNSIGNED NOT NULL DEFAULT '0', 86 `changed` datetime NOT NULL DEFAULT '1000-01-01 00:00:00', 87 `data` longtext NOT NULL, 88 `seen` tinyint(1) NOT NULL DEFAULT '0', 89 `deleted` tinyint(1) NOT NULL DEFAULT '0', 90 `answered` tinyint(1) NOT NULL DEFAULT '0', 91 `forwarded` tinyint(1) NOT NULL DEFAULT '0', 92 `flagged` tinyint(1) NOT NULL DEFAULT '0', 93 `mdnsent` tinyint(1) NOT NULL DEFAULT '0', 94 CONSTRAINT `user_id_fk_cache_messages` FOREIGN KEY (`user_id`) 95 REFERENCES `users`(`user_id`) ON DELETE CASCADE ON UPDATE CASCADE, 96 INDEX `changed_index` (`changed`), 97 PRIMARY KEY (`user_id`, `mailbox`, `uid`) 76 98 ) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */; 77 99 -
trunk/roundcubemail/SQL/mysql.update.sql
r5183 r5190 171 171 UNIQUE `uniqueness` (`user_id`, `type`, `name`) 172 172 ) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */; 173 174 DROP TABLE `messages`; 175 176 CREATE TABLE `cache_index` ( 177 `user_id` int(10) UNSIGNED NOT NULL DEFAULT '0', 178 `mailbox` varchar(255) BINARY NOT NULL, 179 `changed` datetime NOT NULL DEFAULT '1000-01-01 00:00:00', 180 `data` longtext NOT NULL, 181 CONSTRAINT `user_id_fk_cache_index` FOREIGN KEY (`user_id`) 182 REFERENCES `users`(`user_id`) ON DELETE CASCADE ON UPDATE CASCADE, 183 INDEX `changed_index` (`changed`), 184 PRIMARY KEY (`user_id`, `mailbox`) 185 ) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */; 186 187 CREATE TABLE `cache_thread` ( 188 `user_id` int(10) UNSIGNED NOT NULL DEFAULT '0', 189 `mailbox` varchar(255) BINARY NOT NULL, 190 `changed` datetime NOT NULL DEFAULT '1000-01-01 00:00:00', 191 `data` longtext NOT NULL, 192 CONSTRAINT `user_id_fk_cache_thread` FOREIGN KEY (`user_id`) 193 REFERENCES `users`(`user_id`) ON DELETE CASCADE ON UPDATE CASCADE, 194 INDEX `changed_index` (`changed`), 195 PRIMARY KEY (`user_id`, `mailbox`) 196 ) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */; 197 198 CREATE TABLE `cache_messages` ( 199 `user_id` int(10) UNSIGNED NOT NULL DEFAULT '0', 200 `mailbox` varchar(255) BINARY NOT NULL, 201 `uid` int(11) UNSIGNED NOT NULL DEFAULT '0', 202 `changed` datetime NOT NULL DEFAULT '1000-01-01 00:00:00', 203 `data` longtext NOT NULL, 204 `seen` tinyint(1) NOT NULL DEFAULT '0', 205 `deleted` tinyint(1) NOT NULL DEFAULT '0', 206 `answered` tinyint(1) NOT NULL DEFAULT '0', 207 `forwarded` tinyint(1) NOT NULL DEFAULT '0', 208 `flagged` tinyint(1) NOT NULL DEFAULT '0', 209 `mdnsent` tinyint(1) NOT NULL DEFAULT '0', 210 CONSTRAINT `user_id_fk_cache_messages` FOREIGN KEY (`user_id`) 211 REFERENCES `users`(`user_id`) ON DELETE CASCADE ON UPDATE CASCADE, 212 INDEX `changed_index` (`changed`), 213 PRIMARY KEY (`user_id`, `mailbox`, `uid`) 214 ) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */; -
trunk/roundcubemail/SQL/postgres.initial.sql
r5182 r5190 68 68 identity_id integer DEFAULT nextval('identity_ids'::text) PRIMARY KEY, 69 69 user_id integer NOT NULL 70 REFERENCES users (user_id) ON DELETE CASCADE ON UPDATE CASCADE,70 REFERENCES users (user_id) ON DELETE CASCADE ON UPDATE CASCADE, 71 71 changed timestamp with time zone DEFAULT now() NOT NULL, 72 72 del smallint DEFAULT 0 NOT NULL, … … 179 179 cache_id integer DEFAULT nextval('cache_ids'::text) PRIMARY KEY, 180 180 user_id integer NOT NULL 181 REFERENCES users (user_id) ON DELETE CASCADE ON UPDATE CASCADE,181 REFERENCES users (user_id) ON DELETE CASCADE ON UPDATE CASCADE, 182 182 cache_key varchar(128) DEFAULT '' NOT NULL, 183 183 created timestamp with time zone DEFAULT now() NOT NULL, … … 189 189 190 190 -- 191 -- Sequence "message_ids" 192 -- Name: message_ids; Type: SEQUENCE; Schema: public; Owner: postgres 193 -- 194 195 CREATE SEQUENCE message_ids 196 INCREMENT BY 1 197 NO MAXVALUE 198 NO MINVALUE 199 CACHE 1; 200 201 -- 202 -- Table "messages" 203 -- Name: messages; Type: TABLE; Schema: public; Owner: postgres 204 -- 205 206 CREATE TABLE messages ( 207 message_id integer DEFAULT nextval('message_ids'::text) PRIMARY KEY, 208 user_id integer NOT NULL 209 REFERENCES users (user_id) ON DELETE CASCADE ON UPDATE CASCADE, 210 del smallint DEFAULT 0 NOT NULL, 211 cache_key varchar(128) DEFAULT '' NOT NULL, 212 created timestamp with time zone DEFAULT now() NOT NULL, 213 idx integer DEFAULT 0 NOT NULL, 214 uid integer DEFAULT 0 NOT NULL, 215 subject varchar(128) DEFAULT '' NOT NULL, 216 "from" varchar(128) DEFAULT '' NOT NULL, 217 "to" varchar(128) DEFAULT '' NOT NULL, 218 cc varchar(128) DEFAULT '' NOT NULL, 219 date timestamp with time zone NOT NULL, 220 size integer DEFAULT 0 NOT NULL, 221 headers text NOT NULL, 222 structure text, 223 CONSTRAINT messages_user_id_key UNIQUE (user_id, cache_key, uid) 224 ); 225 226 CREATE INDEX messages_index_idx ON messages (user_id, cache_key, idx); 227 CREATE INDEX messages_created_idx ON messages (created); 191 -- Table "cache_index" 192 -- Name: cache_index; Type: TABLE; Schema: public; Owner: postgres 193 -- 194 195 CREATE TABLE cache_index ( 196 user_id integer NOT NULL 197 REFERENCES users (user_id) ON DELETE CASCADE ON UPDATE CASCADE, 198 mailbox varchar(255) NOT NULL, 199 changed timestamp with time zone DEFAULT now() NOT NULL, 200 data text NOT NULL, 201 PRIMARY KEY (user_id, mailbox) 202 ); 203 204 CREATE INDEX cache_index_changed_idx ON cache_index (changed); 205 206 -- 207 -- Table "cache_thread" 208 -- Name: cache_thread; Type: TABLE; Schema: public; Owner: postgres 209 -- 210 211 CREATE TABLE cache_thread ( 212 user_id integer NOT NULL 213 REFERENCES users (user_id) ON DELETE CASCADE ON UPDATE CASCADE, 214 mailbox varchar(255) NOT NULL, 215 changed timestamp with time zone DEFAULT now() NOT NULL, 216 data text NOT NULL, 217 PRIMARY KEY (user_id, mailbox) 218 ); 219 220 CREATE INDEX cache_thread_changed_idx ON cache_thread (changed); 221 222 -- 223 -- Table "cache_messages" 224 -- Name: cache_messages; Type: TABLE; Schema: public; Owner: postgres 225 -- 226 227 CREATE TABLE cache_messages ( 228 user_id integer NOT NULL 229 REFERENCES users (user_id) ON DELETE CASCADE ON UPDATE CASCADE, 230 mailbox varchar(255) NOT NULL, 231 uid integer NOT NULL, 232 changed timestamp with time zone DEFAULT now() NOT NULL, 233 data text NOT NULL, 234 seen smallint NOT NULL DEFAULT 0, 235 deleted smallint NOT NULL DEFAULT 0, 236 answered smallint NOT NULL DEFAULT 0, 237 forwarded smallint NOT NULL DEFAULT 0, 238 flagged smallint NOT NULL DEFAULT 0, 239 mdnsent smallint NOT NULL DEFAULT 0, 240 PRIMARY KEY (user_id, mailbox, uid) 241 ); 242 243 CREATE INDEX cache_messages_changed_idx ON cache_messages (changed); 228 244 229 245 -- -
trunk/roundcubemail/SQL/postgres.update.sql
r5182 r5190 127 127 CONSTRAINT searches_user_id_key UNIQUE (user_id, "type", name) 128 128 ); 129 130 DROP SEQUENCE messages_ids; 131 DROP TABLE messages; 132 133 CREATE TABLE cache_index ( 134 user_id integer NOT NULL 135 REFERENCES users (user_id) ON DELETE CASCADE ON UPDATE CASCADE, 136 mailbox varchar(255) NOT NULL, 137 changed timestamp with time zone DEFAULT now() NOT NULL, 138 data text NOT NULL, 139 PRIMARY KEY (user_id, mailbox) 140 ); 141 142 CREATE INDEX cache_index_changed_idx ON cache_index (changed); 143 144 CREATE TABLE cache_thread ( 145 user_id integer NOT NULL 146 REFERENCES users (user_id) ON DELETE CASCADE ON UPDATE CASCADE, 147 mailbox varchar(255) NOT NULL, 148 changed timestamp with time zone DEFAULT now() NOT NULL, 149 data text NOT NULL, 150 PRIMARY KEY (user_id, mailbox) 151 ); 152 153 CREATE INDEX cache_thread_changed_idx ON cache_thread (changed); 154 155 CREATE TABLE cache_messages ( 156 user_id integer NOT NULL 157 REFERENCES users (user_id) ON DELETE CASCADE ON UPDATE CASCADE, 158 mailbox varchar(255) NOT NULL, 159 uid integer NOT NULL, 160 changed timestamp with time zone DEFAULT now() NOT NULL, 161 data text NOT NULL, 162 seen smallint NOT NULL DEFAULT 0, 163 deleted smallint NOT NULL DEFAULT 0, 164 answered smallint NOT NULL DEFAULT 0, 165 forwarded smallint NOT NULL DEFAULT 0, 166 flagged smallint NOT NULL DEFAULT 0, 167 mdnsent smallint NOT NULL DEFAULT 0, 168 PRIMARY KEY (user_id, mailbox, uid) 169 ); 170 171 CREATE INDEX cache_messages_changed_idx ON cache_messages (changed); -
trunk/roundcubemail/SQL/sqlite.initial.sql
r5182 r5190 2 2 3 3 -- 4 -- Table structure for table `cache`4 -- Table structure for table cache 5 5 -- 6 6 … … 10 10 cache_key varchar(128) NOT NULL default '', 11 11 created datetime NOT NULL default '0000-00-00 00:00:00', 12 data longtext NOT NULL12 data text NOT NULL 13 13 ); 14 14 … … 122 122 -- -------------------------------------------------------- 123 123 124 --125 -- Table structure for table messages126 --127 128 CREATE TABLE messages (129 message_id integer NOT NULL PRIMARY KEY,130 user_id integer NOT NULL default '0',131 del tinyint NOT NULL default '0',132 cache_key varchar(128) NOT NULL default '',133 created datetime NOT NULL default '0000-00-00 00:00:00',134 idx integer NOT NULL default '0',135 uid integer NOT NULL default '0',136 subject varchar(255) NOT NULL default '',137 "from" varchar(255) NOT NULL default '',138 "to" varchar(255) NOT NULL default '',139 "cc" varchar(255) NOT NULL default '',140 "date" datetime NOT NULL default '0000-00-00 00:00:00',141 size integer NOT NULL default '0',142 headers text NOT NULL,143 structure text144 );145 146 CREATE UNIQUE INDEX ix_messages_user_cache_uid ON messages (user_id,cache_key,uid);147 CREATE INDEX ix_messages_index ON messages (user_id,cache_key,idx);148 CREATE INDEX ix_messages_created ON messages (created);149 150 -- --------------------------------------------------------151 152 124 -- 153 125 -- Table structure for table dictionary … … 177 149 178 150 CREATE UNIQUE INDEX ix_searches_user_type_name (user_id, type, name); 151 152 -- -------------------------------------------------------- 153 154 -- 155 -- Table structure for table cache_index 156 -- 157 158 CREATE TABLE cache_index ( 159 user_id integer NOT NULL, 160 mailbox varchar(255) NOT NULL, 161 changed datetime NOT NULL default '0000-00-00 00:00:00', 162 data text NOT NULL, 163 PRIMARY KEY (user_id, mailbox) 164 ); 165 166 CREATE INDEX ix_cache_index_changed ON cache_index (changed); 167 168 -- -------------------------------------------------------- 169 170 -- 171 -- Table structure for table cache_thread 172 -- 173 174 CREATE TABLE cache_thread ( 175 user_id integer NOT NULL, 176 mailbox varchar(255) NOT NULL, 177 changed datetime NOT NULL default '0000-00-00 00:00:00', 178 data text NOT NULL, 179 PRIMARY KEY (user_id, mailbox) 180 ); 181 182 CREATE INDEX ix_cache_thread_changed ON cache_thread (changed); 183 184 -- -------------------------------------------------------- 185 186 -- 187 -- Table structure for table cache_messages 188 -- 189 190 CREATE TABLE cache_messages ( 191 user_id integer NOT NULL, 192 mailbox varchar(255) NOT NULL, 193 uid integer NOT NULL, 194 changed datetime NOT NULL default '0000-00-00 00:00:00', 195 data text NOT NULL, 196 seen smallint NOT NULL DEFAULT '0', 197 deleted smallint NOT NULL DEFAULT '0', 198 answered smallint NOT NULL DEFAULT '0', 199 forwarded smallint NOT NULL DEFAULT '0', 200 flagged smallint NOT NULL DEFAULT '0', 201 mdnsent smallint NOT NULL DEFAULT '0', 202 PRIMARY KEY (user_id, mailbox, uid) 203 ); 204 205 CREATE INDEX ix_cache_messages_changed ON cache_messages (changed); -
trunk/roundcubemail/SQL/sqlite.update.sql
r5182 r5190 224 224 DROP TABLE contacts_tmp; 225 225 226 226 227 DELETE FROM messages; 227 228 DELETE FROM cache; 228 229 CREATE INDEX ix_contactgroupmembers_contact_id ON contactgroupmembers (contact_id); 229 230 230 231 231 -- Updates from version 0.6-stable … … 248 248 249 249 CREATE UNIQUE INDEX ix_searches_user_type_name (user_id, type, name); 250 251 DROP TABLE messages; 252 253 CREATE TABLE cache_index ( 254 user_id integer NOT NULL, 255 mailbox varchar(255) NOT NULL, 256 changed datetime NOT NULL default '0000-00-00 00:00:00', 257 data text NOT NULL, 258 PRIMARY KEY (user_id, mailbox) 259 ); 260 261 CREATE INDEX ix_cache_index_changed ON cache_index (changed); 262 263 CREATE TABLE cache_thread ( 264 user_id integer NOT NULL, 265 mailbox varchar(255) NOT NULL, 266 changed datetime NOT NULL default '0000-00-00 00:00:00', 267 data text NOT NULL, 268 PRIMARY KEY (user_id, mailbox) 269 ); 270 271 CREATE INDEX ix_cache_thread_changed ON cache_thread (changed); 272 273 CREATE TABLE cache_messages ( 274 user_id integer NOT NULL, 275 mailbox varchar(255) NOT NULL, 276 uid integer NOT NULL, 277 changed datetime NOT NULL default '0000-00-00 00:00:00', 278 data text NOT NULL, 279 seen smallint NOT NULL DEFAULT '0', 280 deleted smallint NOT NULL DEFAULT '0', 281 answered smallint NOT NULL DEFAULT '0', 282 forwarded smallint NOT NULL DEFAULT '0', 283 flagged smallint NOT NULL DEFAULT '0', 284 mdnsent smallint NOT NULL DEFAULT '0', 285 PRIMARY KEY (user_id, mailbox, uid) 286 ); 287 288 CREATE INDEX ix_cache_messages_changed ON cache_messages (changed); -
trunk/roundcubemail/program/include/main.inc
r5181 r5190 170 170 $ts = get_offset_time($rcmail->config->get('message_cache_lifetime', '30d'), -1); 171 171 172 $db->query("DELETE FROM ".get_table_name('messages')." 173 WHERE created < " . $db->fromunixtime($ts)); 174 175 $db->query("DELETE FROM ".get_table_name('cache')." 176 WHERE created < " . $db->fromunixtime($ts)); 172 $db->query("DELETE FROM ".get_table_name('cache_messages') 173 ." WHERE changed < " . $db->fromunixtime($ts)); 174 175 $db->query("DELETE FROM ".get_table_name('cache_index') 176 ." WHERE changed < " . $db->fromunixtime($ts)); 177 178 $db->query("DELETE FROM ".get_table_name('cache_thread') 179 ." WHERE changed < " . $db->fromunixtime($ts)); 180 181 $db->query("DELETE FROM ".get_table_name('cache') 182 ." WHERE created < " . $db->fromunixtime($ts)); 177 183 } 178 184 -
trunk/roundcubemail/program/include/rcube_imap.php
r5150 r5190 49 49 50 50 /** 51 * Instance of rcube_ mdb252 * 53 * @var rcube_ mdb254 */ 55 private $ db;51 * Instance of rcube_imap_cache 52 * 53 * @var rcube_imap_cache 54 */ 55 private $mcache; 56 56 57 57 /** … … 61 61 */ 62 62 private $cache; 63 64 /** 65 * Internal (in-memory) cache 66 * 67 * @var array 68 */ 69 private $icache = array(); 70 63 71 private $mailbox = 'INBOX'; 64 72 private $delimiter = NULL; … … 69 77 private $struct_charset = NULL; 70 78 private $default_folders = array('INBOX'); 71 private $messages_caching = false;72 private $icache = array();73 79 private $uid_id_map = array(); 74 80 private $msg_headers = array(); … … 79 85 private $search_threads = false; 80 86 private $search_sorted = false; 81 private $db_header_fields = array('idx', 'uid', 'subject', 'from', 'to', 'cc', 'date', 'size');82 87 private $options = array('auth_method' => 'check'); 83 88 private $host, $user, $pass, $port, $ssl; 84 89 private $caching = false; 90 private $messages_caching = false; 85 91 86 92 /** … … 215 221 { 216 222 $this->conn->closeConnection(); 223 if ($this->mcache) 224 $this->mcache->close(); 217 225 } 218 226 … … 690 698 if ($status) { 691 699 $this->set_folder_stats($mailbox, 'cnt', $res['msgcount']); 692 $this->set_folder_stats($mailbox, 'maxuid', $res['maxuid'] ? $this-> _id2uid($res['maxuid'], $mailbox) : 0);700 $this->set_folder_stats($mailbox, 'maxuid', $res['maxuid'] ? $this->id2uid($res['maxuid'], $mailbox) : 0); 693 701 } 694 702 } … … 723 731 724 732 if ($mode == 'ALL') { 725 if ($ need_uid && $this->messages_caching) {726 // Save messages index for check_cache_status()727 $this->icache[' all_undeleted_idx'] = $index['ALL'];733 if ($this->messages_caching) { 734 // Save additional info required by cache status check 735 $this->icache['undeleted_idx'] = array($mailbox, $index['ALL'], $index['COUNT']); 728 736 } 729 737 if ($status) { … … 740 748 if ($status) { 741 749 $this->set_folder_stats($mailbox,'cnt', $count); 742 $this->set_folder_stats($mailbox, 'maxuid', $count ? $this-> _id2uid($count, $mailbox) : 0);750 $this->set_folder_stats($mailbox, 'maxuid', $count ? $this->id2uid($count, $mailbox) : 0); 743 751 } 744 752 } … … 775 783 ); 776 784 } 777 else if (is_array($result = $this-> _fetch_threads($mailbox))) {785 else if (is_array($result = $this->fetch_threads($mailbox))) { 778 786 $dcount = count($result[1]); 779 787 $result = array( … … 818 826 * @param string $sort_order Sort order [ASC|DESC] 819 827 * @param int $slice Number of slice items to extract from result array 828 * 820 829 * @return array Indexed array with message header objects 821 * @access private822 830 * @see rcube_imap::list_headers 823 831 */ 824 private function _list_headers($mailbox='', $page=NULL, $sort_field=NULL, $sort_order=NULL, $ recursive=false, $slice=0)832 private function _list_headers($mailbox='', $page=NULL, $sort_field=NULL, $sort_order=NULL, $slice=0) 825 833 { 826 834 if (!strlen($mailbox)) … … 832 840 833 841 if ($this->threading) 834 return $this->_list_thread_headers($mailbox, $page, $sort_field, $sort_order, $ recursive, $slice);842 return $this->_list_thread_headers($mailbox, $page, $sort_field, $sort_order, $slice); 835 843 836 844 $this->_set_sort_order($sort_field, $sort_order); 837 845 838 $page = $page ? $page : $this->list_page; 839 $cache_key = $mailbox.'.msg'; 840 841 if ($this->messages_caching) { 842 // cache is OK, we can get messages from local cache 843 // (assume cache is in sync when in recursive mode) 844 if ($recursive || $this->check_cache_status($mailbox, $cache_key)>0) { 845 $start_msg = ($page-1) * $this->page_size; 846 $a_msg_headers = $this->get_message_cache($cache_key, $start_msg, 847 $start_msg+$this->page_size, $this->sort_field, $this->sort_order); 848 $result = array_values($a_msg_headers); 849 if ($slice) 850 $result = array_slice($result, -$slice, $slice); 851 return $result; 852 } 853 // cache is incomplete, sync it (all messages in the folder) 854 else if (!$recursive) { 855 $this->sync_header_index($mailbox); 856 return $this->_list_headers($mailbox, $page, $this->sort_field, $this->sort_order, true, $slice); 857 } 858 } 859 846 $page = $page ? $page : $this->list_page; 847 848 // Use messages cache 849 if ($mcache = $this->get_mcache_engine()) { 850 $msg_index = $mcache->get_index($mailbox, $this->sort_field, $this->sort_order); 851 852 if (empty($msg_index)) 853 return array(); 854 855 $from = ($page-1) * $this->page_size; 856 $to = $from + $this->page_size; 857 $msg_index = array_values($msg_index); // UIDs 858 $is_uid = true; 859 $sorted = true; 860 861 if ($from || $to) 862 $msg_index = array_slice($msg_index, $from, $to - $from); 863 864 if ($slice) 865 $msg_index = array_slice($msg_index, -$slice, $slice); 866 867 $a_msg_headers = $mcache->get_messages($mailbox, $msg_index); 868 } 860 869 // retrieve headers from IMAP 861 $a_msg_headers = array();862 863 870 // use message index sort as default sorting (for better performance) 864 if (!$this->sort_field) {871 else if (!$this->sort_field) { 865 872 if ($this->skip_deleted) { 866 873 // @TODO: this could be cached 867 874 if ($msg_index = $this->_search_index($mailbox, 'ALL UNDELETED')) { 868 $max = max($msg_index);869 875 list($begin, $end) = $this->_get_message_range(count($msg_index), $page); 870 876 $msg_index = array_slice($msg_index, $begin, $end-$begin); … … 883 889 // fetch reqested headers from server 884 890 if ($msg_index) 885 $ this->_fetch_headers($mailbox, join(",", $msg_index), $a_msg_headers, $cache_key);891 $a_msg_headers = $this->fetch_headers($mailbox, $msg_index); 886 892 } 887 893 // use SORT command 888 894 else if ($this->get_capability('SORT') && 889 895 // Courier-IMAP provides SORT capability but allows to disable it by admin (#1486959) 890 ($msg_index = $this->conn->sort($mailbox, $this->sort_field, $this->skip_deleted ? 'UNDELETED' : '')) !== false 896 ($msg_index = $this->conn->sort($mailbox, $this->sort_field, 897 $this->skip_deleted ? 'UNDELETED' : '', true)) !== false 891 898 ) { 892 899 if (!empty($msg_index)) { 893 900 list($begin, $end) = $this->_get_message_range(count($msg_index), $page); 894 $max = max($msg_index);895 901 $msg_index = array_slice($msg_index, $begin, $end-$begin); 902 $is_uid = true; 896 903 897 904 if ($slice) … … 899 906 900 907 // fetch reqested headers from server 901 $ this->_fetch_headers($mailbox, join(',', $msg_index), $a_msg_headers, $cache_key);908 $a_msg_headers = $this->fetch_headers($mailbox, $msg_index, true); 902 909 } 903 910 } 904 911 // fetch specified header for all messages and sort 905 else if ($a_index = $this->conn->fetchHeaderIndex($mailbox, "1:*", $this->sort_field, $this->skip_deleted)) { 906 asort($a_index); // ASC 907 $msg_index = array_keys($a_index); 908 $max = max($msg_index); 912 else if ($msg_index = $this->conn->fetchHeaderIndex($mailbox, "1:*", 913 $this->sort_field, $this->skip_deleted, true) 914 ) { 915 asort($msg_index); // ASC 916 $msg_index = array_keys($msg_index); 909 917 list($begin, $end) = $this->_get_message_range(count($msg_index), $page); 910 918 $msg_index = array_slice($msg_index, $begin, $end-$begin); 919 $is_uid = true; 911 920 912 921 if ($slice) … … 914 923 915 924 // fetch reqested headers from server 916 $this->_fetch_headers($mailbox, join(",", $msg_index), $a_msg_headers, $cache_key); 917 } 918 919 // delete cached messages with a higher index than $max+1 920 // Changed $max to $max+1 to fix this bug : #1484295 921 $this->clear_message_cache($cache_key, $max + 1); 922 923 // kick child process to sync cache 924 // ... 925 $a_msg_headers = $this->fetch_headers($mailbox, $msg_index, true); 926 } 925 927 926 928 // return empty array if no messages found … … 930 932 // use this class for message sorting 931 933 $sorter = new rcube_header_sorter(); 932 $sorter->set_ sequence_numbers($msg_index);934 $sorter->set_index($msg_index, $is_uid); 933 935 $sorter->sort_headers($a_msg_headers); 934 936 935 if ($this->sort_order == 'DESC' )937 if ($this->sort_order == 'DESC' && !$sorted) 936 938 $a_msg_headers = array_reverse($a_msg_headers); 937 939 … … 947 949 * @param string $sort_field Header field to sort by 948 950 * @param string $sort_order Sort order [ASC|DESC] 949 * @param boolean $recursive True if called recursively950 951 * @param int $slice Number of slice items to extract from result array 952 * 951 953 * @return array Indexed array with message header objects 952 * @access private953 954 * @see rcube_imap::list_headers 954 955 */ 955 private function _list_thread_headers($mailbox, $page=NULL, $sort_field=NULL, $sort_order=NULL, $ recursive=false, $slice=0)956 private function _list_thread_headers($mailbox, $page=NULL, $sort_field=NULL, $sort_order=NULL, $slice=0) 956 957 { 957 958 $this->_set_sort_order($sort_field, $sort_order); 958 959 959 $page = $page ? $page : $this->list_page; 960 // $cache_key = $mailbox.'.msg'; 961 // $cache_status = $this->check_cache_status($mailbox, $cache_key); 962 963 // get all threads (default sort order) 964 list ($thread_tree, $msg_depth, $has_children) = $this->_fetch_threads($mailbox); 960 $page = $page ? $page : $this->list_page; 961 $mcache = $this->get_mcache_engine(); 962 963 // get all threads (not sorted) 964 if ($mcache) 965 list ($thread_tree, $msg_depth, $has_children) = $mcache->get_thread($mailbox); 966 else 967 list ($thread_tree, $msg_depth, $has_children) = $this->fetch_threads($mailbox); 965 968 966 969 if (empty($thread_tree)) 967 970 return array(); 968 971 969 $msg_index = $this-> _sort_threads($mailbox, $thread_tree);972 $msg_index = $this->sort_threads($mailbox, $thread_tree); 970 973 971 974 return $this->_fetch_thread_headers($mailbox, … … 975 978 976 979 /** 977 * Private method for fetching threads data 978 * 979 * @param string $mailbox Mailbox/folder name 980 * Method for fetching threads data 981 * 982 * @param string $mailbox Folder name 983 * @param bool $force Use IMAP server, no cache 984 * 980 985 * @return array Array with thread data 981 * @access private 982 */ 983 private function _fetch_threads($mailbox) 984 { 986 */ 987 function fetch_threads($mailbox, $force = false) 988 { 989 if (!$force && ($mcache = $this->get_mcache_engine())) { 990 // don't store in self's internal cache, cache has it's own internal cache 991 return $mcache->get_thread($mailbox); 992 } 993 985 994 if (empty($this->icache['threads'])) { 986 995 // get all threads … … 1013 1022 * @param int $page List page number 1014 1023 * @param int $slice Number of threads to slice 1024 * 1015 1025 * @return array Messages headers 1016 1026 * @access private … … 1018 1028 private function _fetch_thread_headers($mailbox, $thread_tree, $msg_depth, $has_children, $msg_index, $page, $slice=0) 1019 1029 { 1020 $cache_key = $mailbox.'.msg';1021 1030 // now get IDs for current page 1022 1031 list($begin, $end) = $this->_get_message_range(count($msg_index), $page); … … 1039 1048 1040 1049 // fetch reqested headers from server 1041 $ this->_fetch_headers($mailbox, $all_ids, $a_msg_headers, $cache_key);1050 $a_msg_headers = $this->fetch_headers($mailbox, $all_ids); 1042 1051 1043 1052 // return empty array if no messages found … … 1047 1056 // use this class for message sorting 1048 1057 $sorter = new rcube_header_sorter(); 1049 $sorter->set_ sequence_numbers($all_ids);1058 $sorter->set_index($all_ids); 1050 1059 $sorter->sort_headers($a_msg_headers); 1051 1060 … … 1136 1145 1137 1146 // fetch headers 1138 $ this->_fetch_headers($mailbox, join(',',$msgs), $a_msg_headers, NULL);1147 $a_msg_headers = $this->fetch_headers($mailbox, $msgs); 1139 1148 1140 1149 // I didn't found in RFC that FETCH always returns messages sorted by index 1141 1150 $sorter = new rcube_header_sorter(); 1142 $sorter->set_ sequence_numbers($msgs);1151 $sorter->set_index($msgs); 1143 1152 $sorter->sort_headers($a_msg_headers); 1144 1153 … … 1166 1175 1167 1176 // fetch headers 1168 $ this->_fetch_headers($mailbox, join(',',$msgs), $a_msg_headers, NULL);1177 $a_msg_headers = $this->fetch_headers($mailbox, $msgs); 1169 1178 1170 1179 $sorter = new rcube_header_sorter(); 1171 $sorter->set_ sequence_numbers($msgs);1180 $sorter->set_index($msgs); 1172 1181 $sorter->sort_headers($a_msg_headers); 1173 1182 … … 1185 1194 $msgs = array_slice($msgs, -$slice, $slice); 1186 1195 // ...and fetch headers 1187 $this->_fetch_headers($mailbox, join(',', $msgs), $a_msg_headers, NULL); 1196 $a_msg_headers = $this->fetch_headers($mailbox, $msgs); 1197 1188 1198 1189 1199 // return empty array if no messages found … … 1192 1202 1193 1203 $sorter = new rcube_header_sorter(); 1194 $sorter->set_ sequence_numbers($msgs);1204 $sorter->set_index($msgs); 1195 1205 $sorter->sort_headers($a_msg_headers); 1196 1206 … … 1199 1209 else { 1200 1210 // for small result set we can fetch all messages headers 1201 $ this->_fetch_headers($mailbox, join(',', $msgs), $a_msg_headers, NULL);1211 $a_msg_headers = $this->fetch_headers($mailbox, $msgs); 1202 1212 1203 1213 // return empty array if no messages found … … 1257 1267 $this->_set_sort_order($sort_field, $sort_order); 1258 1268 1259 $msg_index = $this-> _sort_threads($mailbox, $thread_tree, array_keys($msg_depth));1269 $msg_index = $this->sort_threads($mailbox, $thread_tree, array_keys($msg_depth)); 1260 1270 1261 1271 return $this->_fetch_thread_headers($mailbox, … … 1298 1308 1299 1309 /** 1300 * Fetches message headers (used for loop) 1301 * 1302 * @param string $mailbox Mailbox name 1303 * @param string $msgs Message index to fetch 1304 * @param array $a_msg_headers Reference to message headers array 1305 * @param string $cache_key Cache index key 1306 * @return int Messages count 1310 * Fetches messages headers 1311 * 1312 * @param string $mailbox Mailbox name 1313 * @param array $msgs Messages sequence numbers 1314 * @param bool $is_uid Enable if $msgs numbers are UIDs 1315 * @param bool $force Disables cache use 1316 * 1317 * @return array Messages headers indexed by UID 1307 1318 * @access private 1308 1319 */ 1309 private function _fetch_headers($mailbox, $msgs, &$a_msg_headers, $cache_key) 1310 { 1320 function fetch_headers($mailbox, $msgs, $is_uid = false, $force = false) 1321 { 1322 if (empty($msgs)) 1323 return array(); 1324 1325 if (!$force && ($mcache = $this->get_mcache_engine())) { 1326 return $mcache->get_messages($mailbox, $msgs, $is_uid); 1327 } 1328 1311 1329 // fetch reqested headers from server 1312 $ a_header_index = $this->conn->fetchHeaders(1313 $mailbox, $msgs, false, false, $this->get_fetch_headers());1314 1315 if (empty($ a_header_index))1316 return 0;1317 1318 foreach ($ a_header_index as $i =>$headers) {1330 $index = $this->conn->fetchHeaders( 1331 $mailbox, $msgs, $is_uid, false, $this->get_fetch_headers()); 1332 1333 if (empty($index)) 1334 return array(); 1335 1336 foreach ($index as $headers) { 1319 1337 $a_msg_headers[$headers->uid] = $headers; 1320 1338 } 1321 1339 1322 // Update cache 1323 if ($this->messages_caching && $cache_key) { 1324 // cache is incomplete? 1325 $cache_index = $this->get_message_cache_index($cache_key); 1326 1327 foreach ($a_header_index as $headers) { 1328 // message in cache 1329 if ($cache_index[$headers->id] == $headers->uid) { 1330 unset($cache_index[$headers->id]); 1331 continue; 1332 } 1333 // wrong UID at this position 1334 if ($cache_index[$headers->id]) { 1335 $for_remove[] = $cache_index[$headers->id]; 1336 unset($cache_index[$headers->id]); 1337 } 1338 // message UID in cache but at wrong position 1339 if (is_int($key = array_search($headers->uid, $cache_index))) { 1340 $for_remove[] = $cache_index[$key]; 1341 unset($cache_index[$key]); 1342 } 1343 1344 $for_create[] = $headers->uid; 1345 } 1346 1347 if ($for_remove) 1348 $this->remove_message_cache($cache_key, $for_remove); 1349 1350 // add messages to cache 1351 foreach ((array)$for_create as $uid) { 1352 $headers = $a_msg_headers[$uid]; 1353 $this->add_message_cache($cache_key, $headers->id, $headers, NULL, true); 1354 } 1355 } 1356 1357 return count($a_msg_headers); 1340 return $a_msg_headers; 1358 1341 } 1359 1342 … … 1472 1455 else { 1473 1456 $a_index = $this->conn->fetchHeaderIndex($mailbox, 1474 join(',', $this->search_set), $this->sort_field, $this->skip_deleted);1457 join(',', $this->search_set), $this->sort_field, $this->skip_deleted); 1475 1458 1476 1459 if (is_array($a_index)) { … … 1493 1476 1494 1477 // check local cache 1495 $cache_key = $mailbox.'.msg'; 1496 $cache_status = $this->check_cache_status($mailbox, $cache_key); 1497 1498 // cache is OK 1499 if ($cache_status>0) { 1500 $a_index = $this->get_message_cache_index($cache_key, 1501 $this->sort_field, $this->sort_order); 1502 return array_keys($a_index); 1503 } 1504 1478 if ($mcache = $this->get_mcache_engine()) { 1479 $a_index = $mcache->get_index($mailbox, $this->sort_field, $this->sort_order); 1480 $this->icache[$key] = array_keys($a_index); 1481 } 1482 // fetch from IMAP server 1483 else { 1484 $this->icache[$key] = $this->message_index_direct( 1485 $mailbox, $this->sort_field, $this->sort_order); 1486 } 1487 1488 return $this->icache[$key]; 1489 } 1490 1491 1492 /** 1493 * Return sorted array of message IDs (not UIDs) directly from IMAP server. 1494 * Doesn't use cache and ignores current search settings. 1495 * 1496 * @param string $mailbox Mailbox to get index from 1497 * @param string $sort_field Sort column 1498 * @param string $sort_order Sort order [ASC, DESC] 1499 * 1500 * @return array Indexed array with message IDs 1501 */ 1502 function message_index_direct($mailbox, $sort_field = null, $sort_order = null) 1503 { 1505 1504 // use message index sort as default sorting 1506 if (!$ this->sort_field) {1505 if (!$sort_field) { 1507 1506 if ($this->skip_deleted) { 1508 1507 $a_index = $this->_search_index($mailbox, 'ALL'); … … 1511 1510 } 1512 1511 1513 if ($a_index !== false && $ this->sort_order == 'DESC')1512 if ($a_index !== false && $sort_order == 'DESC') 1514 1513 $a_index = array_reverse($a_index); 1515 1516 $this->icache[$key] = $a_index;1517 1514 } 1518 1515 // fetch complete message index 1519 1516 else if ($this->get_capability('SORT') && 1520 1517 ($a_index = $this->conn->sort($mailbox, 1521 $ this->sort_field, $this->skip_deleted ? 'UNDELETED' : '')) !== false1518 $sort_field, $this->skip_deleted ? 'UNDELETED' : '')) !== false 1522 1519 ) { 1523 if ($ this->sort_order == 'DESC')1520 if ($sort_order == 'DESC') 1524 1521 $a_index = array_reverse($a_index); 1525 1526 $this->icache[$key] = $a_index;1527 1522 } 1528 1523 else if ($a_index = $this->conn->fetchHeaderIndex( 1529 $mailbox, "1:*", $ this->sort_field, $this->skip_deleted)) {1530 if ($ this->sort_order=="ASC")1524 $mailbox, "1:*", $sort_field, $skip_deleted)) { 1525 if ($sort_order=="ASC") 1531 1526 asort($a_index); 1532 else if ($ this->sort_order=="DESC")1527 else if ($sort_order=="DESC") 1533 1528 arsort($a_index); 1534 1529 1535 $ this->icache[$key]= array_keys($a_index);1536 } 1537 1538 return $ this->icache[$key] !== false ? $this->icache[$key]: array();1530 $a_index = array_keys($a_index); 1531 } 1532 1533 return $a_index !== false ? $a_index : array(); 1539 1534 } 1540 1535 … … 1568 1563 if (isset($this->icache[$key])) 1569 1564 return $this->icache[$key]; 1570 /* 1571 // check local cache 1572 $cache_key = $mailbox.'.msg'; 1573 $cache_status = $this->check_cache_status($mailbox, $cache_key); 1574 1575 // cache is OK 1576 if ($cache_status>0) { 1577 $a_index = $this->get_message_cache_index($cache_key, $this->sort_field, $this->sort_order); 1578 return array_keys($a_index); 1579 } 1580 */ 1565 1581 1566 // get all threads (default sort order) 1582 list ($thread_tree) = $this-> _fetch_threads($mailbox);1567 list ($thread_tree) = $this->fetch_threads($mailbox); 1583 1568 1584 1569 $this->icache[$key] = $this->_flatten_threads($mailbox, $thread_tree); … … 1592 1577 * 1593 1578 * @param string $mailbox Mailbox to get index from 1594 * @param array $thread_tree Threaded messages array (see _fetch_threads())1579 * @param array $thread_tree Threaded messages array (see fetch_threads()) 1595 1580 * @param array $ids Message IDs if we know what we need (e.g. search result) 1596 1581 * for better performance … … 1604 1589 return array(); 1605 1590 1606 $msg_index = $this-> _sort_threads($mailbox, $thread_tree, $ids);1591 $msg_index = $this->sort_threads($mailbox, $thread_tree, $ids); 1607 1592 1608 1593 if ($this->sort_order == 'DESC') … … 1624 1609 1625 1610 /** 1626 * @param string $mailbox Mailbox name1627 * @access private1628 */1629 private function sync_header_index($mailbox)1630 {1631 $cache_key = $mailbox.'.msg';1632 $cache_index = $this->get_message_cache_index($cache_key);1633 $chunk_size = 1000;1634 1635 // cache is empty, get all messages1636 if (is_array($cache_index) && empty($cache_index)) {1637 $max = $this->_messagecount($mailbox);1638 // syncing a big folder maybe slow1639 @set_time_limit(0);1640 $start = 1;1641 $end = min($chunk_size, $max);1642 while (true) {1643 // do this in loop to save memory (1000 msgs ~= 10 MB)1644 if ($headers = $this->conn->fetchHeaders($mailbox,1645 "$start:$end", false, false, $this->get_fetch_headers())1646 ) {1647 foreach ($headers as $header) {1648 $this->add_message_cache($cache_key, $header->id, $header, NULL, true);1649 }1650 }1651 if ($end - $start < $chunk_size - 1)1652 break;1653 1654 $end = min($end+$chunk_size, $max);1655 $start += $chunk_size;1656 }1657 return;1658 }1659 1660 // fetch complete message index1661 if (isset($this->icache['folder_index']))1662 $a_message_index = &$this->icache['folder_index'];1663 else1664 $a_message_index = $this->conn->fetchHeaderIndex($mailbox, "1:*", 'UID', $this->skip_deleted);1665 1666 if ($a_message_index === false || $cache_index === null)1667 return;1668 1669 // compare cache index with real index1670 foreach ($a_message_index as $id => $uid) {1671 // message in cache at correct position1672 if ($cache_index[$id] == $uid) {1673 unset($cache_index[$id]);1674 continue;1675 }1676 1677 // other message at this position1678 if (isset($cache_index[$id])) {1679 $for_remove[] = $cache_index[$id];1680 unset($cache_index[$id]);1681 }1682 1683 // message in cache but at wrong position1684 if (is_int($key = array_search($uid, $cache_index))) {1685 $for_remove[] = $uid;1686 unset($cache_index[$key]);1687 }1688 1689 $for_update[] = $id;1690 }1691 1692 // remove messages at wrong positions and those deleted that are still in cache_index1693 if (!empty($for_remove))1694 $cache_index = array_merge($cache_index, $for_remove);1695 1696 if (!empty($cache_index))1697 $this->remove_message_cache($cache_key, $cache_index);1698 1699 // fetch complete headers and add to cache1700 if (!empty($for_update)) {1701 // syncing a big folder maybe slow1702 @set_time_limit(0);1703 // To save memory do this in chunks1704 $for_update = array_chunk($for_update, $chunk_size);1705 foreach ($for_update as $uids) {1706 if ($headers = $this->conn->fetchHeaders($mailbox,1707 $uids, false, false, $this->get_fetch_headers())1708 ) {1709 foreach ($headers as $header) {1710 $this->add_message_cache($cache_key, $header->id, $header, NULL, true);1711 }1712 }1713 }1714 }1715 }1716 1717 1718 /**1719 1611 * Invoke search request to IMAP server 1720 1612 * … … 1751 1643 * @param string $charset Charset 1752 1644 * @param string $sort_field Sorting field 1645 * 1753 1646 * @return array search results as list of message ids 1754 * @access private1755 1647 * @see rcube_imap::search() 1756 1648 */ … … 1774 1666 list ($thread_tree, $msg_depth, $has_children) = $a_messages; 1775 1667 $a_messages = array( 1776 'tree' => $thread_tree,1777 'depth'=> $msg_depth,1778 'children' => $has_children1668 'tree' => $thread_tree, 1669 'depth'=> $msg_depth, 1670 'children' => $has_children 1779 1671 ); 1780 1672 } … … 1788 1680 1789 1681 // Error, try with US-ASCII (RFC5256: SORT/THREAD must support US-ASCII and UTF-8, 1790 // but I've seen that Courier doesn't support UTF-8)1682 // but I've seen Courier with disabled UTF-8 support) 1791 1683 if ($a_messages === false && $charset && $charset != 'US-ASCII') 1792 1684 $a_messages = $this->conn->sort($mailbox, $sort_field, … … 1830 1722 * @param string $str Search string 1831 1723 * @param boolean $ret_uid True if UIDs should be returned 1724 * 1832 1725 * @return array Search results as list of message IDs or UIDs 1833 * @access public1834 1726 */ 1835 1727 function search_once($mailbox='', $str=NULL, $ret_uid=false) … … 1885 1777 * @param array $thread_tree Unsorted thread tree (rcube_imap_generic::thread() result) 1886 1778 * @param array $ids Message IDs if we know what we need (e.g. search result) 1779 * 1887 1780 * @return array Sorted roots IDs 1888 * @access private1889 */1890 private function _sort_threads($mailbox, $thread_tree, $ids=NULL)1891 {1892 // THREAD= ORDEREDSUBJECT:sorting by sent date of root message1893 // THREAD=REF ERENCES: sorting by sent date of root message1894 // THREAD=REFS: sorting by the most recent date in each thread 1781 */ 1782 function sort_threads($mailbox, $thread_tree, $ids = null) 1783 { 1784 // THREAD=ORDEREDSUBJECT: sorting by sent date of root message 1785 // THREAD=REFERENCES: sorting by sent date of root message 1786 // THREAD=REFS: sorting by the most recent date in each thread 1787 1895 1788 // default sorting 1896 1789 if (!$this->sort_field || ($this->sort_field == 'date' && $this->threading == 'REFS')) { 1897 1790 return array_keys((array)$thread_tree); 1898 } 1899 // here we'll implement REFS sorting, for performance reason 1900 else { // ($sort_field == 'date' && $this->threading != 'REFS') 1791 } 1792 // here we'll implement REFS sorting 1793 else { 1794 if ($mcache = $this->get_mcache_engine()) { 1795 $a_index = $mcache->get_index($mailbox, $this->sort_field, 'ASC'); 1796 if (is_array($a_index)) { 1797 $a_index = array_keys($a_index); 1798 // now we must remove IDs that doesn't exist in $ids 1799 if (!empty($ids)) 1800 $a_index = array_intersect($a_index, $ids); 1801 } 1802 } 1901 1803 // use SORT command 1902 if ($this->get_capability('SORT') &&1804 else if ($this->get_capability('SORT') && 1903 1805 ($a_index = $this->conn->sort($mailbox, $this->sort_field, 1904 !empty($ids) ? $ids : ($this->skip_deleted ? 'UNDELETED' : ''))) !== false1806 !empty($ids) ? $ids : ($this->skip_deleted ? 'UNDELETED' : ''))) !== false 1905 1807 ) { 1906 // return unsorted tree if we've got no index data 1907 if (!$a_index) 1908 return array_keys((array)$thread_tree); 1808 // do nothing 1909 1809 } 1910 1810 else { 1911 1811 // fetch specified headers for all messages and sort them 1912 1812 $a_index = $this->conn->fetchHeaderIndex($mailbox, !empty($ids) ? $ids : "1:*", 1913 $this->sort_field, $this->skip_deleted); 1914 1915 // return unsorted tree if we've got no index data 1916 if (!$a_index) 1917 return array_keys((array)$thread_tree); 1918 1919 asort($a_index); // ASC 1920 $a_index = array_values($a_index); 1921 } 1922 1923 return $this->_sort_thread_refs($thread_tree, $a_index); 1813 $this->sort_field, $this->skip_deleted); 1814 1815 // return unsorted tree if we've got no index data 1816 if (!empty($a_index)) { 1817 asort($a_index); // ASC 1818 $a_index = array_values($a_index); 1819 } 1820 } 1821 1822 if (empty($a_index)) 1823 return array_keys((array)$thread_tree); 1824 1825 return $this->_sort_thread_refs($thread_tree, $a_index); 1924 1826 } 1925 1827 } … … 1929 1831 * THREAD=REFS sorting implementation 1930 1832 * 1931 * @param array $tree Thread tree array (message identifiers as keys) 1932 * @param array $index Array of sorted message identifiers 1833 * @param array $tree Thread tree array (message identifiers as keys) 1834 * @param array $index Array of sorted message identifiers 1835 * 1933 1836 * @return array Array of sorted roots messages 1934 * @access private1935 1837 */ 1936 1838 private function _sort_thread_refs($tree, $index) … … 1981 1883 if (!empty($this->search_string)) 1982 1884 $this->search_set = $this->search('', $this->search_string, $this->search_charset, 1983 $this->search_sort_field, $this->search_threads, $this->search_sorted);1885 $this->search_sort_field, $this->search_threads, $this->search_sorted); 1984 1886 1985 1887 return $this->get_search_set(); … … 2009 1911 * Return message headers object of a specific message 2010 1912 * 2011 * @param int $id Message ID1913 * @param int $id Message sequence ID or UID 2012 1914 * @param string $mailbox Mailbox to read from 2013 * @param bool ean $is_uid True if $id is the message UID2014 * @param boolean $bodystr True if we need also BODYSTRUCTURE in headers2015 * @return object Message headers representation2016 */ 2017 function get_headers($ id, $mailbox=null, $is_uid=true, $bodystr=false)1915 * @param bool $force True to skip cache 1916 * 1917 * @return rcube_mail_header Message headers 1918 */ 1919 function get_headers($uid, $mailbox = null, $force = false) 2018 1920 { 2019 1921 if (!strlen($mailbox)) { 2020 1922 $mailbox = $this->mailbox; 2021 1923 } 2022 $uid = $is_uid ? $id : $this->_id2uid($id, $mailbox);2023 1924 2024 1925 // get cached headers 2025 if ($uid && ($headers = &$this->get_cached_message($mailbox.'.msg', $uid))) 1926 if (!$force && $uid && ($mcache = $this->get_mcache_engine())) { 1927 $headers = $mcache->get_message($mailbox, $uid); 1928 } 1929 else { 1930 $headers = $this->conn->fetchHeader( 1931 $mailbox, $uid, true, true, $this->get_fetch_headers()); 1932 } 1933 1934 return $headers; 1935 } 1936 1937 1938 /** 1939 * Fetch message headers and body structure from the IMAP server and build 1940 * an object structure similar to the one generated by PEAR::Mail_mimeDecode 1941 * 1942 * @param int $uid Message UID to fetch 1943 * @param string $mailbox Mailbox to read from 1944 * 1945 * @return object rcube_mail_header Message data 1946 */ 1947 function get_message($uid, $mailbox = null) 1948 { 1949 if (!strlen($mailbox)) { 1950 $mailbox = $this->mailbox; 1951 } 1952 1953 // Check internal cache 1954 if (!empty($this->icache['message'])) { 1955 if (($headers = $this->icache['message']) && $headers->uid == $uid) { 1956 return $headers; 1957 } 1958 } 1959 1960 $headers = $this->get_headers($uid, $mailbox); 1961 1962 // structure might be cached 1963 if (!empty($headers->structure)) 2026 1964 return $headers; 2027 1965 2028 $headers = $this->conn->fetchHeader( 2029 $mailbox, $id, $is_uid, $bodystr, $this->get_fetch_headers()); 2030 2031 // write headers cache 2032 if ($headers) { 2033 if ($headers->uid && $headers->id) 2034 $this->uid_id_map[$mailbox][$headers->uid] = $headers->id; 2035 2036 $this->add_message_cache($mailbox.'.msg', $headers->id, $headers, NULL, false, true); 2037 } 2038 2039 return $headers; 2040 } 2041 2042 2043 /** 2044 * Fetch body structure from the IMAP server and build 2045 * an object structure similar to the one generated by PEAR::Mail_mimeDecode 2046 * 2047 * @param int $uid Message UID to fetch 2048 * @param string $structure_str Message BODYSTRUCTURE string (optional) 2049 * @return object rcube_message_part Message part tree or False on failure 2050 */ 2051 function &get_structure($uid, $structure_str='') 2052 { 2053 $cache_key = $this->mailbox.'.msg'; 2054 $headers = &$this->get_cached_message($cache_key, $uid); 2055 2056 // return cached message structure 2057 if (is_object($headers) && is_object($headers->structure)) { 2058 return $headers->structure; 2059 } 2060 2061 if (!$structure_str) { 2062 $structure_str = $this->conn->fetchStructureString($this->mailbox, $uid, true); 2063 } 2064 $structure = rcube_mime_struct::parseStructure($structure_str); 2065 $struct = false; 2066 2067 // parse structure and add headers 2068 if (!empty($structure)) { 2069 $headers = $this->get_headers($uid); 2070 $this->_msg_id = $headers->id; 1966 $this->_msg_uid = $uid; 1967 1968 if (empty($headers->bodystructure)) { 1969 $headers->bodystructure = $this->conn->getStructure($mailbox, $uid, true); 1970 } 1971 1972 $structure = $headers->bodystructure; 1973 1974 if (empty($structure)) 1975 return $headers; 2071 1976 2072 1977 // set message charset from message headers … … 2091 1996 } 2092 1997 else 2093 return false;1998 return $headers; 2094 1999 } 2095 2000 … … 2104 2009 } 2105 2010 2106 // write structure to cache 2107 if ($this->messages_caching) 2108 $this->add_message_cache($cache_key, $this->_msg_id, $headers, $struct, 2109 $this->icache['message.id'][$uid], true); 2110 } 2111 2112 return $struct; 2011 $headers->structure = $struct; 2012 2013 return $this->icache['message'] = $headers; 2113 2014 } 2114 2015 … … 2175 2076 if ($mime_part_headers) { 2176 2077 $mime_part_headers = $this->conn->fetchMIMEHeaders($this->mailbox, 2177 $this->_msg_ id, $mime_part_headers);2078 $this->_msg_uid, $mime_part_headers); 2178 2079 } 2179 2080 … … 2277 2178 if (empty($mime_headers)) { 2278 2179 $mime_headers = $this->conn->fetchPartHeader( 2279 $this->mailbox, $this->_msg_ id, false, $struct->mime_id);2180 $this->mailbox, $this->_msg_uid, true, $struct->mime_id); 2280 2181 } 2281 2182 … … 2340 2241 if (!$headers) { 2341 2242 $headers = $this->conn->fetchPartHeader( 2342 $this->mailbox, $this->_msg_ id, false, $part->mime_id);2243 $this->mailbox, $this->_msg_uid, true, $part->mime_id); 2343 2244 } 2344 2245 $filename_mime = ''; … … 2359 2260 if (!$headers) { 2360 2261 $headers = $this->conn->fetchPartHeader( 2361 $this->mailbox, $this->_msg_ id, false, $part->mime_id);2262 $this->mailbox, $this->_msg_uid, true, $part->mime_id); 2362 2263 } 2363 2264 $filename_encoded = ''; … … 2378 2279 if (!$headers) { 2379 2280 $headers = $this->conn->fetchPartHeader( 2380 $this->mailbox, $this->_msg_ id, false, $part->mime_id);2281 $this->mailbox, $this->_msg_uid, true, $part->mime_id); 2381 2282 } 2382 2283 $filename_mime = ''; … … 2397 2298 if (!$headers) { 2398 2299 $headers = $this->conn->fetchPartHeader( 2399 $this->mailbox, $this->_msg_ id, false, $part->mime_id);2300 $this->mailbox, $this->_msg_uid, true, $part->mime_id); 2400 2301 } 2401 2302 $filename_encoded = ''; … … 2466 2367 // get part encoding if not provided 2467 2368 if (!is_object($o_part)) { 2468 $structure_str = $this->conn->fetchStructureString($this->mailbox, $uid, true); 2469 $structure = new rcube_mime_struct(); 2470 // error or message not found 2471 if (!$structure->loadStructure($structure_str)) { 2472 return false; 2473 } 2369 $structure = $this->conn->getStructure($this->mailbox, $uid, true); 2474 2370 2475 2371 $o_part = new rcube_message_part; 2476 $o_part->ctype_primary = strtolower( $structure->getPartType($part));2477 $o_part->encoding = strtolower( $structure->getPartEncoding($part));2478 $o_part->charset = $structure->getPartCharset($part);2372 $o_part->ctype_primary = strtolower(rcube_imap_generic::getStructurePartType($structure, $part)); 2373 $o_part->encoding = strtolower(rcube_imap_generic::getStructurePartEncoding($structure, $part)); 2374 $o_part->charset = rcube_imap_generic::getStructurePartCharset($structure, $part); 2479 2375 } 2480 2376 … … 2585 2481 if ($result) { 2586 2482 // reload message headers if cached 2587 if ($this->messages_caching && !$skip_cache) {2588 $cache_key = $mailbox.'.msg';2589 if ($all_mode)2590 $this->clear_message_cache($cache_key);2591 else2592 $ this->remove_message_cache($cache_key, explode(',', $uids));2483 // @TODO: update flags instead removing from cache 2484 if (!$skip_cache && ($mcache = $this->get_mcache_engine())) { 2485 $status = strpos($flag, 'UN') !== 0; 2486 $mflag = preg_replace('/^UN/', '', $flag); 2487 $mcache->change_flag($mailbox, $all_mode ? null : explode(',', $uids), 2488 $mflag, $status); 2593 2489 } 2594 2490 … … 2722 2618 $this->refresh_search(); 2723 2619 else { 2724 $ uids = explode(',', $uids);2725 foreach ($ uids as $uid)2726 $a_mids[] = $this-> _uid2id($uid, $from_mbox);2620 $a_uids = explode(',', $uids); 2621 foreach ($a_uids as $uid) 2622 $a_mids[] = $this->uid2id($uid, $from_mbox); 2727 2623 $this->search_set = array_diff($this->search_set, $a_mids); 2728 2624 } 2729 } 2730 2731 // update cached message headers 2732 $cache_key = $from_mbox.'.msg'; 2733 if ($all_mode || ($start_index = $this->get_message_cache_index_min($cache_key, $uids))) { 2734 // clear cache from the lowest index on 2735 $this->clear_message_cache($cache_key, $all_mode ? 1 : $start_index); 2736 } 2625 unset($a_mids); 2626 unset($a_uids); 2627 } 2628 2629 // remove cached messages 2630 // @TODO: do cache update instead of clearing it 2631 $this->clear_message_cache($from_mbox, $all_mode ? null : explode(',', $uids)); 2737 2632 } 2738 2633 … … 2819 2714 $this->refresh_search(); 2820 2715 else { 2821 $ uids = explode(',', $uids);2822 foreach ($ uids as $uid)2823 $a_mids[] = $this-> _uid2id($uid, $mailbox);2716 $a_uids = explode(',', $uids); 2717 foreach ($a_uids as $uid) 2718 $a_mids[] = $this->uid2id($uid, $mailbox); 2824 2719 $this->search_set = array_diff($this->search_set, $a_mids); 2720 unset($a_uids); 2721 unset($a_mids); 2825 2722 } 2826 2723 } 2827 2724 2828 // remove deleted messages from cache 2829 $cache_key = $mailbox.'.msg'; 2830 if ($all_mode || ($start_index = $this->get_message_cache_index_min($cache_key, $uids))) { 2831 // clear cache from the lowest index on 2832 $this->clear_message_cache($cache_key, $all_mode ? 1 : $start_index); 2833 } 2725 // remove cached messages 2726 $this->clear_message_cache($mailbox, $all_mode ? null : explode(',', $uids)); 2834 2727 } 2835 2728 … … 2856 2749 } 2857 2750 2858 // make sure the message countcache is cleared as well2751 // make sure the cache is cleared as well 2859 2752 if ($cleared) { 2860 $this->clear_message_cache($mailbox .'.msg');2753 $this->clear_message_cache($mailbox); 2861 2754 $a_mailbox_cache = $this->get_cache('messagecount'); 2862 2755 unset($a_mailbox_cache[$mailbox]); … … 2899 2792 { 2900 2793 if ($uids && $this->get_capability('UIDPLUS')) 2901 $a_uids = is_array($uids) ? join(',', $uids) : $uids;2794 list($uids, $all_mode) = $this->_parse_uids($uids, $mailbox); 2902 2795 else 2903 $ a_uids = NULL;2796 $uids = null; 2904 2797 2905 2798 // force mailbox selection and check if mailbox is writeable … … 2916 2809 2917 2810 // CLOSE(+SELECT) should be faster than EXPUNGE 2918 if (empty($ a_uids) || $a_uids == '1:*')2811 if (empty($uids) || $all_mode) 2919 2812 $result = $this->conn->close(); 2920 2813 else 2921 $result = $this->conn->expunge($mailbox, $ a_uids);2814 $result = $this->conn->expunge($mailbox, $uids); 2922 2815 2923 2816 if ($result && $clear_cache) { 2924 $this->clear_message_cache($mailbox .'.msg');2817 $this->clear_message_cache($mailbox, $all_mode ? null : explode(',', $uids)); 2925 2818 $this->_clear_messagecount($mailbox); 2926 2819 } … … 2987 2880 } 2988 2881 2989 return $this-> _uid2id($uid, $mailbox);2882 return $this->uid2id($uid, $mailbox); 2990 2883 } 2991 2884 … … 3005 2898 } 3006 2899 3007 return $this-> _id2uid($id, $mailbox);2900 return $this->id2uid($id, $mailbox); 3008 2901 } 3009 2902 … … 3303 3196 $this->conn->subscribe(preg_replace('/^'.preg_quote($mailbox, '/').'/', 3304 3197 $new_name, $c_subscribed)); 3198 3199 // clear cache 3200 $this->clear_message_cache($c_subscribed); 3305 3201 } 3306 3202 } 3307 3203 3308 3204 // clear cache 3309 $this->clear_message_cache($mailbox .'.msg');3205 $this->clear_message_cache($mailbox); 3310 3206 $this->clear_cache('mailboxes', true); 3311 3207 } … … 3343 3239 $this->conn->unsubscribe($c_mbox); 3344 3240 if ($this->conn->deleteFolder($c_mbox)) { 3345 $this->clear_message_cache($c_mbox .'.msg');3241 $this->clear_message_cache($c_mbox); 3346 3242 } 3347 3243 } … … 3349 3245 3350 3246 // clear mailbox-related cache 3351 $this->clear_message_cache($mailbox .'.msg');3247 $this->clear_message_cache($mailbox); 3352 3248 $this->clear_cache('mailboxes', true); 3353 3249 } … … 3505 3401 3506 3402 return is_array($opts) ? $opts : array(); 3403 } 3404 3405 3406 /** 3407 * Gets connection (and current mailbox) data: UIDVALIDITY, EXISTS, RECENT, 3408 * PERMANENTFLAGS, UIDNEXT, UNSEEN 3409 * 3410 * @param string $mailbox Folder name 3411 * 3412 * @return array Data 3413 */ 3414 function mailbox_data($mailbox) 3415 { 3416 if (!strlen($mailbox)) 3417 $mailbox = $this->mailbox !== null ? $this->mailbox : 'INBOX'; 3418 3419 if ($this->conn->selected != $mailbox) { 3420 if ($this->conn->select($mailbox)) 3421 $this->mailbox = $mailbox; 3422 } 3423 3424 $data = $this->conn->data; 3425 3426 // add (E)SEARCH result for ALL UNDELETED query 3427 if (!empty($this->icache['undeleted_idx']) && $this->icache['undeleted_idx'][0] == $mailbox) { 3428 $data['ALL_UNDELETED'] = $this->icache['undeleted_idx'][1]; 3429 $data['COUNT_UNDELETED'] = $this->icache['undeleted_idx'][2]; 3430 } 3431 3432 return $data; 3507 3433 } 3508 3434 … … 3849 3775 if ($this->cache) 3850 3776 $this->cache->close(); 3851 $this->cache = null;3777 $this->cache = null; 3852 3778 $this->caching = false; 3853 3779 } … … 3919 3845 * 3920 3846 * @param boolean $set Flag 3921 * @access public3922 3847 */ 3923 3848 function set_messages_caching($set) 3924 3849 { 3925 $rcmail = rcmail::get_instance(); 3926 3927 if ($set && ($dbh = $rcmail->get_dbh())) { 3928 $this->db = $dbh; 3850 if ($set) { 3929 3851 $this->messages_caching = true; 3930 3852 } 3931 3853 else { 3854 if ($this->mcache) 3855 $this->mcache->close(); 3856 $this->mcache = null; 3932 3857 $this->messages_caching = false; 3933 3858 } … … 3935 3860 3936 3861 /** 3937 * Checks if the cache is up-to-date 3938 * 3939 * @param string $mailbox Mailbox name 3940 * @param string $cache_key Internal cache key 3941 * @return int Cache status: -3 = off, -2 = incomplete, -1 = dirty, 1 = OK 3942 */ 3943 private function check_cache_status($mailbox, $cache_key) 3944 { 3945 if (!$this->messages_caching) 3946 return -3; 3947 3948 $cache_index = $this->get_message_cache_index($cache_key); 3949 $msg_count = $this->_messagecount($mailbox); 3950 $cache_count = count($cache_index); 3951 3952 // empty mailbox 3953 if (!$msg_count) { 3954 return $cache_count ? -2 : 1; 3955 } 3956 3957 if ($cache_count == $msg_count) { 3958 if ($this->skip_deleted) { 3959 if (!empty($this->icache['all_undeleted_idx'])) { 3960 $uids = rcube_imap_generic::uncompressMessageSet($this->icache['all_undeleted_idx']); 3961 $uids = array_flip($uids); 3962 foreach ($cache_index as $uid) { 3963 unset($uids[$uid]); 3964 } 3965 } 3966 else { 3967 // get all undeleted messages excluding cached UIDs 3968 $uids = $this->search_once($mailbox, 'ALL UNDELETED NOT UID '. 3969 rcube_imap_generic::compressMessageSet($cache_index)); 3970 } 3971 if (empty($uids)) { 3972 return 1; 3973 } 3974 } else { 3975 // get UID of the message with highest index 3976 $uid = $this->_id2uid($msg_count, $mailbox); 3977 $cache_uid = array_pop($cache_index); 3978 3979 // uids of highest message matches -> cache seems OK 3980 if ($cache_uid == $uid) { 3981 return 1; 3982 } 3983 } 3984 // cache is dirty 3985 return -1; 3986 } 3987 3988 // if cache count differs less than 10% report as dirty 3989 return (abs($msg_count - $cache_count) < $msg_count/10) ? -1 : -2; 3990 } 3991 3992 3993 /** 3994 * @param string $key Cache key 3995 * @param string $from 3996 * @param string $to 3997 * @param string $sort_field 3998 * @param string $sort_order 3999 * @access private 4000 */ 4001 private function get_message_cache($key, $from, $to, $sort_field, $sort_order) 4002 { 4003 if (!$this->messages_caching) 4004 return NULL; 4005 4006 // use idx sort as default sorting 4007 if (!$sort_field || !in_array($sort_field, $this->db_header_fields)) { 4008 $sort_field = 'idx'; 4009 } 4010 4011 $result = array(); 4012 4013 $sql_result = $this->db->limitquery( 4014 "SELECT idx, uid, headers". 4015 " FROM ".get_table_name('messages'). 4016 " WHERE user_id=?". 4017 " AND cache_key=?". 4018 " ORDER BY ".$this->db->quoteIdentifier($sort_field)." ".strtoupper($sort_order), 4019 $from, 4020 $to - $from, 4021 $_SESSION['user_id'], 4022 $key); 4023 4024 while ($sql_arr = $this->db->fetch_assoc($sql_result)) { 4025 $uid = intval($sql_arr['uid']); 4026 $result[$uid] = $this->db->decode(unserialize($sql_arr['headers'])); 4027 4028 // featch headers if unserialize failed 4029 if (empty($result[$uid])) 4030 $result[$uid] = $this->conn->fetchHeader( 4031 preg_replace('/.msg$/', '', $key), $uid, true, false, $this->get_fetch_headers()); 4032 } 4033 4034 return $result; 4035 } 4036 4037 4038 /** 4039 * @param string $key Cache key 4040 * @param int $uid Message UID 4041 * @return mixed 4042 * @access private 4043 */ 4044 private function &get_cached_message($key, $uid) 4045 { 4046 $internal_key = 'message'; 4047 4048 if ($this->messages_caching && !isset($this->icache[$internal_key][$uid])) { 4049 $sql_result = $this->db->query( 4050 "SELECT idx, headers, structure, message_id". 4051 " FROM ".get_table_name('messages'). 4052 " WHERE user_id=?". 4053 " AND cache_key=?". 4054 " AND uid=?", 4055 $_SESSION['user_id'], 4056 $key, 4057 $uid); 4058 4059 if ($sql_arr = $this->db->fetch_assoc($sql_result)) { 4060 $this->icache['message.id'][$uid] = intval($sql_arr['message_id']); 4061 $this->uid_id_map[preg_replace('/\.msg$/', '', $key)][$uid] = intval($sql_arr['idx']); 4062 $this->icache[$internal_key][$uid] = $this->db->decode(unserialize($sql_arr['headers'])); 4063 4064 if (is_object($this->icache[$internal_key][$uid]) && !empty($sql_arr['structure'])) 4065 $this->icache[$internal_key][$uid]->structure = $this->db->decode(unserialize($sql_arr['structure'])); 4066 } 4067 } 4068 4069 return $this->icache[$internal_key][$uid]; 4070 } 4071 4072 4073 /** 4074 * @param string $key Cache key 4075 * @param string $sort_field Sorting column 4076 * @param string $sort_order Sorting order 4077 * @return array Messages index 4078 * @access private 4079 */ 4080 private function get_message_cache_index($key, $sort_field='idx', $sort_order='ASC') 4081 { 4082 if (!$this->messages_caching || empty($key)) 4083 return NULL; 4084 4085 // use idx sort as default 4086 if (!$sort_field || !in_array($sort_field, $this->db_header_fields)) 4087 $sort_field = 'idx'; 4088 4089 if (array_key_exists('index', $this->icache) 4090 && $this->icache['index']['key'] == $key 4091 && $this->icache['index']['sort_field'] == $sort_field 4092 ) { 4093 if ($this->icache['index']['sort_order'] == $sort_order) 4094 return $this->icache['index']['result']; 4095 else 4096 return array_reverse($this->icache['index']['result'], true); 4097 } 4098 4099 $this->icache['index'] = array( 4100 'result' => array(), 4101 'key' => $key, 4102 'sort_field' => $sort_field, 4103 'sort_order' => $sort_order, 4104 ); 4105 4106 $sql_result = $this->db->query( 4107 "SELECT idx, uid". 4108 " FROM ".get_table_name('messages'). 4109 " WHERE user_id=?". 4110 " AND cache_key=?". 4111 " ORDER BY ".$this->db->quote_identifier($sort_field)." ".$sort_order, 4112 $_SESSION['user_id'], 4113 $key); 4114 4115 while ($sql_arr = $this->db->fetch_assoc($sql_result)) 4116 $this->icache['index']['result'][$sql_arr['idx']] = intval($sql_arr['uid']); 4117 4118 return $this->icache['index']['result']; 4119 } 4120 4121 4122 /** 4123 * @access private 4124 */ 4125 private function add_message_cache($key, $index, $headers, $struct=null, $force=false, $internal_cache=false) 4126 { 4127 if (empty($key) || !is_object($headers) || empty($headers->uid)) 4128 return; 4129 4130 // add to internal (fast) cache 4131 if ($internal_cache) { 4132 $this->icache['message'][$headers->uid] = clone $headers; 4133 $this->icache['message'][$headers->uid]->structure = $struct; 4134 } 4135 4136 // no further caching 4137 if (!$this->messages_caching) 4138 return; 4139 4140 // known message id 4141 if (is_int($force) && $force > 0) { 4142 $message_id = $force; 4143 } 4144 // check for an existing record (probably headers are cached but structure not) 4145 else if (!$force) { 4146 $sql_result = $this->db->query( 4147 "SELECT message_id". 4148 " FROM ".get_table_name('messages'). 4149 " WHERE user_id=?". 4150 " AND cache_key=?". 4151 " AND uid=?", 4152 $_SESSION['user_id'], 4153 $key, 4154 $headers->uid); 4155 4156 if ($sql_arr = $this->db->fetch_assoc($sql_result)) 4157 $message_id = $sql_arr['message_id']; 4158 } 4159 4160 // update cache record 4161 if ($message_id) { 4162 $this->db->query( 4163 "UPDATE ".get_table_name('messages'). 4164 " SET idx=?, headers=?, structure=?". 4165 " WHERE message_id=?", 4166 $index, 4167 serialize($this->db->encode(clone $headers)), 4168 is_object($struct) ? serialize($this->db->encode(clone $struct)) : NULL, 4169 $message_id 4170 ); 4171 } 4172 else { // insert new record 4173 $this->db->query( 4174 "INSERT INTO ".get_table_name('messages'). 4175 " (user_id, del, cache_key, created, idx, uid, subject, ". 4176 $this->db->quoteIdentifier('from').", ". 4177 $this->db->quoteIdentifier('to').", ". 4178 "cc, date, size, headers, structure)". 4179 " VALUES (?, 0, ?, ".$this->db->now().", ?, ?, ?, ?, ?, ?, ". 4180 $this->db->fromunixtime($headers->timestamp).", ?, ?, ?)", 4181 $_SESSION['user_id'], 4182 $key, 4183 $index, 4184 $headers->uid, 4185 (string)mb_substr($this->db->encode($this->decode_header($headers->subject, true)), 0, 128), 4186 (string)mb_substr($this->db->encode($this->decode_header($headers->from, true)), 0, 128), 4187 (string)mb_substr($this->db->encode($this->decode_header($headers->to, true)), 0, 128), 4188 (string)mb_substr($this->db->encode($this->decode_header($headers->cc, true)), 0, 128), 4189 (int)$headers->size, 4190 serialize($this->db->encode(clone $headers)), 4191 is_object($struct) ? serialize($this->db->encode(clone $struct)) : NULL 4192 ); 4193 } 4194 4195 unset($this->icache['index']); 4196 } 4197 4198 4199 /** 4200 * @access private 4201 */ 4202 private function remove_message_cache($key, $ids, $idx=false) 4203 { 4204 if (!$this->messages_caching) 4205 return; 4206 4207 $this->db->query( 4208 "DELETE FROM ".get_table_name('messages'). 4209 " WHERE user_id=?". 4210 " AND cache_key=?". 4211 " AND ".($idx ? "idx" : "uid")." IN (".$this->db->array2list($ids, 'integer').")", 4212 $_SESSION['user_id'], 4213 $key); 4214 4215 unset($this->icache['index']); 4216 } 4217 4218 4219 /** 4220 * @param string $key Cache key 4221 * @param int $start_index Start index 4222 * @access private 4223 */ 4224 private function clear_message_cache($key, $start_index=1) 4225 { 4226 if (!$this->messages_caching) 4227 return; 4228 4229 $this->db->query( 4230 "DELETE FROM ".get_table_name('messages'). 4231 " WHERE user_id=?". 4232 " AND cache_key=?". 4233 " AND idx>=?", 4234 $_SESSION['user_id'], $key, $start_index); 4235 4236 unset($this->icache['index']); 4237 } 4238 4239 4240 /** 4241 * @access private 4242 */ 4243 private function get_message_cache_index_min($key, $uids=NULL) 4244 { 4245 if (!$this->messages_caching) 4246 return; 4247 4248 if (!empty($uids) && !is_array($uids)) { 4249 if ($uids == '*' || $uids == '1:*') 4250 $uids = NULL; 4251 else 4252 $uids = explode(',', $uids); 4253 } 4254 4255 $sql_result = $this->db->query( 4256 "SELECT MIN(idx) AS minidx". 4257 " FROM ".get_table_name('messages'). 4258 " WHERE user_id=?". 4259 " AND cache_key=?" 4260 .(!empty($uids) ? " AND uid IN (".$this->db->array2list($uids, 'integer').")" : ''), 4261 $_SESSION['user_id'], 4262 $key); 4263 4264 if ($sql_arr = $this->db->fetch_assoc($sql_result)) 4265 return $sql_arr['minidx']; 4266 else 4267 return 0; 4268 } 4269 4270 4271 /** 4272 * @param string $key Cache key 4273 * @param int $id Message (sequence) ID 4274 * @return int Message UID 4275 * @access private 4276 */ 4277 private function get_cache_id2uid($key, $id) 4278 { 4279 if (!$this->messages_caching) 4280 return null; 4281 4282 if (array_key_exists('index', $this->icache) 4283 && $this->icache['index']['key'] == $key 4284 ) { 4285 return $this->icache['index']['result'][$id]; 4286 } 4287 4288 $sql_result = $this->db->query( 4289 "SELECT uid". 4290 " FROM ".get_table_name('messages'). 4291 " WHERE user_id=?". 4292 " AND cache_key=?". 4293 " AND idx=?", 4294 $_SESSION['user_id'], $key, $id); 4295 4296 if ($sql_arr = $this->db->fetch_assoc($sql_result)) 4297 return intval($sql_arr['uid']); 4298 4299 return null; 4300 } 4301 4302 4303 /** 4304 * @param string $key Cache key 4305 * @param int $uid Message UID 4306 * @return int Message (sequence) ID 4307 * @access private 4308 */ 4309 private function get_cache_uid2id($key, $uid) 4310 { 4311 if (!$this->messages_caching) 4312 return null; 4313 4314 if (array_key_exists('index', $this->icache) 4315 && $this->icache['index']['key'] == $key 4316 ) { 4317 return array_search($uid, $this->icache['index']['result']); 4318 } 4319 4320 $sql_result = $this->db->query( 4321 "SELECT idx". 4322 " FROM ".get_table_name('messages'). 4323 " WHERE user_id=?". 4324 " AND cache_key=?". 4325 " AND uid=?", 4326 $_SESSION['user_id'], $key, $uid); 4327 4328 if ($sql_arr = $this->db->fetch_assoc($sql_result)) 4329 return intval($sql_arr['idx']); 4330 4331 return null; 4332 } 3862 * Getter for messages cache object 3863 */ 3864 private function get_mcache_engine() 3865 { 3866 if ($this->messages_caching && !$this->mcache) { 3867 $rcmail = rcmail::get_instance(); 3868 if ($dbh = $rcmail->get_dbh()) { 3869 $this->mcache = new rcube_imap_cache( 3870 $dbh, $this, $rcmail->user->ID, $this->skip_deleted); 3871 } 3872 } 3873 3874 return $this->mcache; 3875 } 3876 3877 /** 3878 * Clears the messages cache. 3879 * 3880 * @param string $mailbox Folder name 3881 * @param array $uids Optional message UIDs to remove from cache 3882 */ 3883 function clear_message_cache($mailbox = null, $uids = null) 3884 { 3885 if ($mcache = $this->get_mcache_engine()) { 3886 $mcache->clear($mailbox, $uids); 3887 } 3888 } 3889 4333 3890 4334 3891 … … 4628 4185 4629 4186 /** 4630 * @param int $uid Message UID 4631 * @param string $mailbox Mailbox name 4187 * Finds message sequence ID for specified UID 4188 * 4189 * @param int $uid Message UID 4190 * @param string $mailbox Mailbox name 4191 * @param bool $force True to skip cache 4192 * 4632 4193 * @return int Message (sequence) ID 4633 * @access private 4634 */ 4635 private function _uid2id($uid, $mailbox=NULL) 4194 */ 4195 function uid2id($uid, $mailbox = null, $force = false) 4636 4196 { 4637 4197 if (!strlen($mailbox)) { … … 4639 4199 } 4640 4200 4641 if (!isset($this->uid_id_map[$mailbox][$uid])) { 4642 if (!($id = $this->get_cache_uid2id($mailbox.'.msg', $uid))) 4643 $id = $this->conn->UID2ID($mailbox, $uid); 4644 4645 $this->uid_id_map[$mailbox][$uid] = $id; 4646 } 4647 4648 return $this->uid_id_map[$mailbox][$uid]; 4649 } 4650 4651 4652 /** 4653 * @param int $id Message (sequence) ID 4654 * @param string $mailbox Mailbox name 4201 if (!empty($this->uid_id_map[$mailbox][$uid])) { 4202 return $this->uid_id_map[$mailbox][$uid]; 4203 } 4204 4205 if (!$force && ($mcache = $this->get_mcache_engine())) 4206 $id = $mcache->uid2id($mailbox, $uid); 4207 4208 if (empty($id)) 4209 $id = $this->conn->UID2ID($mailbox, $uid); 4210 4211 $this->uid_id_map[$mailbox][$uid] = $id; 4212 4213 return $id; 4214 } 4215 4216 4217 /** 4218 * Find UID of the specified message sequence ID 4219 * 4220 * @param int $id Message (sequence) ID 4221 * @param string $mailbox Mailbox name 4222 * @param bool $force True to skip cache 4655 4223 * 4656 4224 * @return int Message UID 4657 * @access private 4658 */ 4659 private function _id2uid($id, $mailbox=null) 4225 */ 4226 function id2uid($id, $mailbox = null, $force = false) 4660 4227 { 4661 4228 if (!strlen($mailbox)) { … … 4667 4234 } 4668 4235 4669 if (!($uid = $this->get_cache_id2uid($mailbox.'.msg', $id))) { 4236 if (!$force && ($mcache = $this->get_mcache_engine())) 4237 $uid = $mcache->id2uid($mailbox, $id); 4238 4239 if (empty($uid)) 4670 4240 $uid = $this->conn->ID2UID($mailbox, $id); 4671 }4672 4241 4673 4242 $this->uid_id_map[$mailbox][$uid] = $id; … … 4955 4524 class rcube_header_sorter 4956 4525 { 4957 var $sequence_numbers = array(); 4526 private $seqs = array(); 4527 private $uids = array(); 4528 4958 4529 4959 4530 /** 4960 4531 * Set the predetermined sort order. 4961 4532 * 4962 * @param array $seqnums Numerically indexed array of IMAP message sequence numbers 4963 */ 4964 function set_sequence_numbers($seqnums) 4965 { 4966 $this->sequence_numbers = array_flip($seqnums); 4533 * @param array $index Numerically indexed array of IMAP ID or UIDs 4534 * @param bool $is_uid Set to true if $index contains UIDs 4535 */ 4536 function set_index($index, $is_uid = false) 4537 { 4538 $index = array_flip($index); 4539 4540 if ($is_uid) 4541 $this->uids = $index; 4542 else 4543 $this->seqs = $index; 4967 4544 } 4968 4545 … … 4974 4551 function sort_headers(&$headers) 4975 4552 { 4976 /* 4977 * uksort would work if the keys were the sequence number, but unfortunately 4978 * the keys are the UIDs. We'll use uasort instead and dereference the value 4979 * to get the sequence number (in the "id" field). 4980 * 4981 * uksort($headers, array($this, "compare_seqnums")); 4982 */ 4983 uasort($headers, array($this, "compare_seqnums")); 4553 if (!empty($this->uids)) 4554 uksort($headers, array($this, "compare_uids")); 4555 else 4556 uasort($headers, array($this, "compare_seqnums")); 4984 4557 } 4985 4558 … … 4997 4570 4998 4571 // then find each sequence number in my ordered list 4999 $posa = isset($this->seq uence_numbers[$seqa]) ? intval($this->sequence_numbers[$seqa]) : -1;5000 $posb = isset($this->seq uence_numbers[$seqb]) ? intval($this->sequence_numbers[$seqb]) : -1;4572 $posa = isset($this->seqs[$seqa]) ? intval($this->seqs[$seqa]) : -1; 4573 $posb = isset($this->seqs[$seqb]) ? intval($this->seqs[$seqb]) : -1; 5001 4574 5002 4575 // return the relative position as the comparison value 5003 4576 return $posa - $posb; 5004 4577 } 4578 4579 /** 4580 * Sort method called by uksort() 4581 * 4582 * @param int $a Array key (UID) 4583 * @param int $b Array key (UID) 4584 */ 4585 function compare_uids($a, $b) 4586 { 4587 // then find each sequence number in my ordered list 4588 $posa = isset($this->uids[$a]) ? intval($this->uids[$a]) : -1; 4589 $posb = isset($this->uids[$b]) ? intval($this->uids[$b]) : -1; 4590 4591 // return the relative position as the comparison value 4592 return $posa - $posb; 4593 } 5005 4594 } -
trunk/roundcubemail/program/include/rcube_imap_generic.php
r5116 r5190 49 49 public $charset; 50 50 public $ctype; 51 public $flags;52 51 public $timestamp; 53 public $body _structure;52 public $bodystructure; 54 53 public $internaldate; 55 54 public $references; 56 55 public $priority; 57 56 public $mdn_to; 58 public $mdn_sent = false; 57 58 public $flags; 59 public $mdnsent = false; 59 60 public $seen = false; 60 61 public $deleted = false; … … 62 63 public $forwarded = false; 63 64 public $flagged = false; 64 public $has_children = false;65 public $depth = 0;66 public $unread_children = 0;67 65 public $others = array(); 68 66 } … … 85 83 public $result; 86 84 public $resultcode; 85 public $selected; 87 86 public $data = array(); 88 87 public $flags = array( … … 97 96 ); 98 97 99 private $selected;100 98 private $fp; 101 99 private $host; … … 236 234 } 237 235 238 function multLine($line, $escape =false)236 function multLine($line, $escape = false) 239 237 { 240 238 $line = rtrim($line); 241 if (preg_match('/\{ [0-9]+\}$/', $line)) {242 $out = '';243 244 preg_match_all('/(.*)\{([0-9]+)\}$/', $line, $a);245 $bytes = $a[2][0]; 239 if (preg_match('/\{([0-9]+)\}$/', $line, $m)) { 240 $out = ''; 241 $str = substr($line, 0, -strlen($m[0])); 242 $bytes = $m[1]; 243 246 244 while (strlen($out) < $bytes) { 247 245 $line = $this->readBytes($bytes); … … 251 249 } 252 250 253 $line = $ a[1][0]. ($escape ? $this->escape($out) : $out);251 $line = $str . ($escape ? $this->escape($out) : $out); 254 252 } 255 253 … … 878 876 * Executes SELECT command (if mailbox is already not in selected state) 879 877 * 880 * @param string $mailbox Mailbox name 878 * @param string $mailbox Mailbox name 879 * @param array $qresync_data QRESYNC data (RFC5162) 881 880 * 882 881 * @return boolean True on success, false on error 883 * @access public 884 */ 885 function select($mailbox) 882 */ 883 function select($mailbox, $qresync_data = null) 886 884 { 887 885 if (!strlen($mailbox)) { … … 902 900 } 903 901 */ 904 list($code, $response) = $this->execute('SELECT', array($this->escape($mailbox))); 902 $params = array($this->escape($mailbox)); 903 904 // QRESYNC data items 905 // 0. the last known UIDVALIDITY, 906 // 1. the last known modification sequence, 907 // 2. the optional set of known UIDs, and 908 // 3. an optional parenthesized list of known sequence ranges and their 909 // corresponding UIDs. 910 if (!empty($qresync_data)) { 911 if (!empty($qresync_data[2])) 912 $qresync_data[2] = self::compressMessageSet($qresync_data[2]); 913 $params[] = array('QRESYNC', $qresync_data); 914 } 915 916 list($code, $response) = $this->execute('SELECT', $params); 905 917 906 918 if ($code == self::ERROR_OK) { … … 910 922 $this->data[strtoupper($m[2])] = (int) $m[1]; 911 923 } 912 else if (preg_match('/^\* OK \[(UIDNEXT|UIDVALIDITY|UNSEEN) ([0-9]+)\]/i', $line, $match)) { 913 $this->data[strtoupper($match[1])] = (int) $match[2]; 914 } 915 else if (preg_match('/^\* OK \[PERMANENTFLAGS \(([^\)]+)\)\]/iU', $line, $match)) { 916 $this->data['PERMANENTFLAGS'] = explode(' ', $match[1]); 924 else if (preg_match('/^\* OK \[/i', $line, $match)) { 925 $line = substr($line, 6); 926 if (preg_match('/^(UIDNEXT|UIDVALIDITY|UNSEEN) ([0-9]+)/i', $line, $match)) { 927 $this->data[strtoupper($match[1])] = (int) $match[2]; 928 } 929 else if (preg_match('/^(HIGHESTMODSEQ) ([0-9]+)/i', $line, $match)) { 930 $this->data[strtoupper($match[1])] = (string) $match[2]; 931 } 932 else if (preg_match('/^(NOMODSEQ)/i', $line, $match)) { 933 $this->data[strtoupper($match[1])] = true; 934 } 935 else if (preg_match('/^PERMANENTFLAGS \(([^\)]+)\)/iU', $line, $match)) { 936 $this->data['PERMANENTFLAGS'] = explode(' ', $match[1]); 937 } 938 } 939 // QRESYNC FETCH response (RFC5162) 940 else if (preg_match('/^\* ([0-9+]) FETCH/i', $line, $match)) { 941 $line = substr($line, strlen($match[0])); 942 $fetch_data = $this->tokenizeResponse($line, 1); 943 $data = array('id' => $match[1]); 944 945 for ($i=0, $size=count($fetch_data); $i<$size; $i+=2) { 946 $data[strtolower($fetch_data[$i])] = $fetch_data[$i+1]; 947 } 948 949 $this->data['QRESYNC'][$data['uid']] = $data; 950 } 951 // QRESYNC VANISHED response (RFC5162) 952 else if (preg_match('/^\* VANISHED [()EARLIER]*/i', $line, $match)) { 953 $line = substr($line, strlen($match[0])); 954 $v_data = $this->tokenizeResponse($line, 1); 955 956 $this->data['VANISHED'] = $v_data; 917 957 } 918 958 } … … 936 976 * 937 977 * @return array Status item-value hash 938 * @access public939 978 * @since 0.5-beta 940 979 */ … … 972 1011 973 1012 for ($i=0, $len=count($items); $i<$len; $i += 2) { 974 $result[$items[$i]] = (int)$items[$i+1];1013 $result[$items[$i]] = $items[$i+1]; 975 1014 } 976 1015 … … 990 1029 * 991 1030 * @return boolean True on success, False on error 992 * @access public993 1031 */ 994 1032 function expunge($mailbox, $messages=NULL) … … 1023 1061 * 1024 1062 * @return boolean True on success, False on error 1025 * @access public1026 1063 * @since 0.5 1027 1064 */ … … 1044 1081 * 1045 1082 * @return boolean True on success, False on error 1046 * @access public1047 1083 */ 1048 1084 function subscribe($mailbox) … … 1060 1096 * 1061 1097 * @return boolean True on success, False on error 1062 * @access public1063 1098 */ 1064 1099 function unsubscribe($mailbox) … … 1076 1111 * 1077 1112 * @return boolean True on success, False on error 1078 * @access public1079 1113 */ 1080 1114 function deleteFolder($mailbox) … … 1092 1126 * 1093 1127 * @return boolean True on success, False on error 1094 * @access public1095 1128 */ 1096 1129 function clearFolder($mailbox) … … 1117 1150 * 1118 1151 * @return int Number of messages, False on error 1119 * @access public1120 1152 */ 1121 1153 function countMessages($mailbox, $refresh = false) … … 1150 1182 * 1151 1183 * @return int Number of messages, False on error 1152 * @access public1153 1184 */ 1154 1185 function countRecent($mailbox) … … 1173 1204 * 1174 1205 * @return int Number of messages, False on error 1175 * @access public1176 1206 */ 1177 1207 function countUnseen($mailbox) … … 1204 1234 * 1205 1235 * @return array Server identification information key/value hash 1206 * @access public1207 1236 * @since 0.6 1208 1237 */ … … 1229 1258 $result[$items[$i]] = $items[$i+1]; 1230 1259 } 1260 1261 return $result; 1262 } 1263 1264 return false; 1265 } 1266 1267 /** 1268 * Executes ENABLE command (RFC5161) 1269 * 1270 * @param mixed $extension Extension name to enable (or array of names) 1271 * 1272 * @return array|bool List of enabled extensions, False on error 1273 * @since 0.6 1274 */ 1275 function enable($extension) 1276 { 1277 if (empty($extension)) 1278 return false; 1279 1280 if (!$this->hasCapability('ENABLE')) 1281 return false; 1282 1283 if (!is_array($extension)) 1284 $extension = array($extension); 1285 1286 list($code, $response) = $this->execute('ENABLE', $extension); 1287 1288 if ($code == self::ERROR_OK && preg_match('/\* ENABLED /i', $response)) { 1289 $response = substr($response, 10); // remove prefix "* ENABLED " 1290 $result = (array) $this->tokenizeResponse($response); 1231 1291 1232 1292 return $result; … … 1473 1533 * 1474 1534 * @return int Message sequence identifier 1475 * @access public1476 1535 */ 1477 1536 function UID2ID($mailbox, $uid) … … 1493 1552 * 1494 1553 * @return int Message unique identifier 1495 * @access public1496 1554 */ 1497 1555 function ID2UID($mailbox, $id) 1498 1556 { 1499 1557 if (empty($id) || $id < 0) { 1500 return null;1558 return null; 1501 1559 } 1502 1560 … … 1516 1574 function fetchUIDs($mailbox, $message_set=null) 1517 1575 { 1518 if (is_array($message_set)) 1519 $message_set = join(',', $message_set); 1520 else if (empty($message_set)) 1576 if (empty($message_set)) 1521 1577 $message_set = '1:*'; 1522 1578 … … 1524 1580 } 1525 1581 1526 function fetchHeaders($mailbox, $message_set, $uidfetch=false, $bodystr=false, $add='') 1527 { 1528 $result = array(); 1529 1582 /** 1583 * FETCH command (RFC3501) 1584 * 1585 * @param string $mailbox Mailbox name 1586 * @param mixed $message_set Message(s) sequence identifier(s) or UID(s) 1587 * @param bool $is_uid True if $message_set contains UIDs 1588 * @param array $query_items FETCH command data items 1589 * @param string $mod_seq Modification sequence for CHANGEDSINCE (RFC4551) query 1590 * @param bool $vanished Enables VANISHED parameter (RFC5162) for CHANGEDSINCE query 1591 * 1592 * @return array List of rcube_mail_header elements, False on error 1593 * @since 0.6 1594 */ 1595 function fetch($mailbox, $message_set, $is_uid = false, $query_items = array(), 1596 $mod_seq = null, $vanished = false) 1597 { 1530 1598 if (!$this->select($mailbox)) { 1531 1599 return false; … … 1533 1601 1534 1602 $message_set = $this->compressMessageSet($message_set); 1535 1536 if ($add) 1537 $add = ' '.trim($add); 1538 1539 /* FETCH uid, size, flags and headers */ 1603 $result = array(); 1604 1540 1605 $key = $this->nextTag(); 1541 $request = $key . ($ uidfetch? ' UID' : '') . " FETCH $message_set ";1542 $request .= "( UID RFC822.SIZE FLAGS INTERNALDATE";1543 if ($bodystr) 1544 $request .= "BODYSTRUCTURE ";1545 $request .= "BODY.PEEK[HEADER.FIELDS (DATE FROM TO SUBJECT CONTENT-TYPE";1546 $request .= "CC REPLY-TO LIST-POST DISPOSITION-NOTIFICATION-TO X-PRIORITY".$add.")])";1606 $request = $key . ($is_uid ? ' UID' : '') . " FETCH $message_set "; 1607 $request .= "(" . implode(' ', $query_items) . ")"; 1608 1609 if ($mod_seq !== null && $this->hasCapability('CONDSTORE')) { 1610 $request .= " (CHANGEDSINCE $mod_seq" . ($vanished ? " VANISHED" : '') .")"; 1611 } 1547 1612 1548 1613 if (!$this->putLine($request)) { … … 1550 1615 return false; 1551 1616 } 1617 1552 1618 do { 1553 1619 $line = $this->readLine(4096); 1554 $line = $this->multLine($line);1555 1620 1556 1621 if (!$line) 1557 1622 break; 1623 1624 // Sample reply line: 1625 // * 321 FETCH (UID 2417 RFC822.SIZE 2730 FLAGS (\Seen) 1626 // INTERNALDATE "16-Nov-2008 21:08:46 +0100" BODYSTRUCTURE (...) 1627 // BODY[HEADER.FIELDS ... 1558 1628 1559 1629 if (preg_match('/^\* ([0-9]+) FETCH/', $line, $m)) { … … 1566 1636 1567 1637 $lines = array(); 1568 $ln = 0; 1569 1570 // Sample reply line: 1571 // * 321 FETCH (UID 2417 RFC822.SIZE 2730 FLAGS (\Seen) 1572 // INTERNALDATE "16-Nov-2008 21:08:46 +0100" BODYSTRUCTURE (...) 1573 // BODY[HEADER.FIELDS ... 1574 1575 if (preg_match('/^\* [0-9]+ FETCH \((.*) BODY/sU', $line, $matches)) { 1576 $str = $matches[1]; 1577 1578 while (list($name, $value) = $this->tokenizeResponse($str, 2)) { 1579 if ($name == 'UID') { 1580 $result[$id]->uid = intval($value); 1581 } 1582 else if ($name == 'RFC822.SIZE') { 1583 $result[$id]->size = intval($value); 1584 } 1585 else if ($name == 'INTERNALDATE') { 1586 $result[$id]->internaldate = $value; 1587 $result[$id]->date = $value; 1588 $result[$id]->timestamp = $this->StrToTime($value); 1589 } 1590 else if ($name == 'FLAGS') { 1591 $flags_a = $value; 1592 } 1593 } 1594 1595 // BODYSTRUCTURE 1596 if ($bodystr) { 1597 while (!preg_match('/ BODYSTRUCTURE (.*) BODY\[HEADER.FIELDS/sU', $line, $m)) { 1598 $line2 = $this->readLine(1024); 1599 $line .= $this->multLine($line2, true); 1600 } 1601 $result[$id]->body_structure = $m[1]; 1602 } 1603 1604 // the rest of the result 1605 if (preg_match('/ BODY\[HEADER.FIELDS \(.*?\)\]\s*(.*)$/s', $line, $m)) { 1606 $reslines = explode("\n", trim($m[1], '"')); 1607 // re-parse (see below) 1608 foreach ($reslines as $resln) { 1609 if (ord($resln[0])<=32) { 1610 $lines[$ln] .= (empty($lines[$ln])?'':"\n").trim($resln); 1611 } else { 1612 $lines[++$ln] = trim($resln); 1638 $line = substr($line, strlen($m[0]) + 2); 1639 $ln = 0; 1640 1641 // get complete entry 1642 while (preg_match('/\{([0-9]+)\}\r\n$/', $line, $m)) { 1643 $bytes = $m[1]; 1644 $out = ''; 1645 1646 while (strlen($out) < $bytes) { 1647 $out = $this->readBytes($bytes); 1648 if ($out === NULL) 1649 break; 1650 $line .= $out; 1651 } 1652 1653 $str = $this->readLine(4096); 1654 if ($str === false) 1655 break; 1656 1657 $line .= $str; 1658 } 1659 1660 // Tokenize response and assign to object properties 1661 while (list($name, $value) = $this->tokenizeResponse($line, 2)) { 1662 if ($name == 'UID') { 1663 $result[$id]->uid = intval($value); 1664 } 1665 else if ($name == 'RFC822.SIZE') { 1666 $result[$id]->size = intval($value); 1667 } 1668 else if ($name == 'RFC822.TEXT') { 1669 $result[$id]->body = $value; 1670 } 1671 else if ($name == 'INTERNALDATE') { 1672 $result[$id]->internaldate = $value; 1673 $result[$id]->date = $value; 1674 $result[$id]->timestamp = $this->StrToTime($value); 1675 } 1676 else if ($name == 'FLAGS') { 1677 if (!empty($value)) { 1678 foreach ((array)$value as $flag) { 1679 $flag = str_replace('\\', '', $flag); 1680 1681 switch (strtoupper($flag)) { 1682 case 'SEEN': 1683 $result[$id]->seen = true; 1684 break; 1685 case 'DELETED': 1686 $result[$id]->deleted = true; 1687 break; 1688 case 'ANSWERED': 1689 $result[$id]->answered = true; 1690 break; 1691 case '$FORWARDED': 1692 $result[$id]->forwarded = true; 1693 break; 1694 case '$MDNSENT': 1695 $result[$id]->mdnsent = true; 1696 break; 1697 case 'FLAGGED': 1698 $result[$id]->flagged = true; 1699 break; 1700 default: 1701 $result[$id]->flags[] = $flag; 1702 break; 1703 } 1613 1704 } 1614 1705 } 1615 1706 } 1616 } 1617 1618 // Start parsing headers. The problem is, some header "lines" take up multiple lines. 1619 // So, we'll read ahead, and if the one we're reading now is a valid header, we'll 1620 // process the previous line. Otherwise, we'll keep adding the strings until we come 1621 // to the next valid header line. 1622 1623 do { 1624 $line = rtrim($this->readLine(300), "\r\n"); 1625 1626 // The preg_match below works around communigate imap, which outputs " UID <number>)". 1627 // Without this, the while statement continues on and gets the "FH0 OK completed" message. 1628 // If this loop gets the ending message, then the outer loop does not receive it from radline on line 1249. 1629 // This in causes the if statement on line 1278 to never be true, which causes the headers to end up missing 1630 // If the if statement was changed to pick up the fh0 from this loop, then it causes the outer loop to spin 1631 // An alternative might be: 1632 // if (!preg_match("/:/",$line) && preg_match("/\)$/",$line)) break; 1633 // however, unsure how well this would work with all imap clients. 1634 if (preg_match("/^\s*UID [0-9]+\)$/", $line)) { 1635 break; 1636 } 1637 1638 // handle FLAGS reply after headers (AOL, Zimbra?) 1639 if (preg_match('/\s+FLAGS \((.*)\)\)$/', $line, $matches)) { 1640 $flags_a = $this->tokenizeResponse($matches[1]); 1641 break; 1642 } 1643 1644 if (ord($line[0])<=32) { 1645 $lines[$ln] .= (empty($lines[$ln])?'':"\n").trim($line); 1646 } else { 1647 $lines[++$ln] = trim($line); 1648 } 1649 // patch from "Maksim Rubis" <siburny@hotmail.com> 1650 } while ($line[0] != ')' && !$this->startsWith($line, $key, true)); 1651 1652 if (strncmp($line, $key, strlen($key))) { 1653 // process header, fill rcube_mail_header obj. 1654 // initialize 1655 if (is_array($headers)) { 1656 reset($headers); 1657 while (list($k, $bar) = each($headers)) { 1658 $headers[$k] = ''; 1707 else if ($name == 'MODSEQ') { 1708 $result[$id]->modseq = $value[0]; 1709 } 1710 else if ($name == 'ENVELOPE') { 1711 $result[$id]->envelope = $value; 1712 } 1713 else if ($name == 'BODYSTRUCTURE' || ($name == 'BODY' && count($value) > 2)) { 1714 if (!is_array($value[0]) && (strtolower($value[0]) == 'message' && strtolower($value[1]) == 'rfc822')) { 1715 $value = array($value); 1659 1716 } 1660 } 1661 1662 // create array with header field:data 1717 $result[$id]->bodystructure = $value; 1718 } 1719 else if ($name == 'RFC822') { 1720 $result[$id]->body = $value; 1721 } 1722 else if ($name == 'BODY') { 1723 $body = $this->tokenizeResponse($line, 1); 1724 if ($value[0] == 'HEADER.FIELDS') 1725 $headers = $body; 1726 else if (!empty($value)) 1727 $result[$id]->bodypart[$value[0]] = $body; 1728 else 1729 $result[$id]->body = $body; 1730 } 1731 } 1732 1733 // create array with header field:data 1734 if (!empty($headers)) { 1735 $headers = explode("\n", trim($headers)); 1736 foreach ($headers as $hid => $resln) { 1737 if (ord($resln[0]) <= 32) { 1738 $lines[$ln] .= (empty($lines[$ln]) ? '' : "\n") . trim($resln); 1739 } else { 1740 $lines[++$ln] = trim($resln); 1741 } 1742 } 1743 1663 1744 while (list($lines_key, $str) = each($lines)) { 1664 1745 list($field, $string) = explode(':', $str, 2); … … 1724 1805 } 1725 1806 break; 1726 } // end switch ()1727 } // end while ()1728 }1729 1730 // process flags1731 if (!empty($flags_a)) {1732 foreach ($flags_a as $flag) {1733 $flag = str_replace('\\', '', $flag);1734 $result[$id]->flags[] = $flag;1735 1736 switch (strtoupper($flag)) {1737 case 'SEEN':1738 $result[$id]->seen = true;1739 break;1740 case 'DELETED':1741 $result[$id]->deleted = true;1742 break;1743 case 'ANSWERED':1744 $result[$id]->answered = true;1745 break;1746 case '$FORWARDED':1747 $result[$id]->forwarded = true;1748 break;1749 case '$MDNSENT':1750 $result[$id]->mdn_sent = true;1751 break;1752 case 'FLAGGED':1753 $result[$id]->flagged = true;1754 break;1755 1807 } 1756 1808 } 1757 1809 } 1758 1810 } 1811 1812 // VANISHED response (QRESYNC RFC5162) 1813 // Sample: * VANISHED (EARLIER) 300:310,405,411 1814 1815 else if (preg_match('/^\* VANISHED [EARLIER]*/i', $line, $match)) { 1816 $line = substr($line, strlen($match[0])); 1817 $v_data = $this->tokenizeResponse($line, 1); 1818 1819 $this->data['VANISHED'] = $v_data; 1820 } 1821 1759 1822 } while (!$this->startsWith($line, $key, true)); 1760 1823 … … 1762 1825 } 1763 1826 1827 function fetchHeaders($mailbox, $message_set, $is_uid = false, $bodystr = false, $add = '') 1828 { 1829 $query_items = array('UID', 'RFC822.SIZE', 'FLAGS', 'INTERNALDATE'); 1830 if ($bodystr) 1831 $query_items[] = 'BODYSTRUCTURE'; 1832 $query_items[] = 'BODY.PEEK[HEADER.FIELDS (' 1833 . 'DATE FROM TO SUBJECT CONTENT-TYPE CC REPLY-TO LIST-POST DISPOSITION-NOTIFICATION-TO X-PRIORITY' 1834 . ($add ? ' ' . trim($add) : '') 1835 . ')]'; 1836 1837 $result = $this->fetch($mailbox, $message_set, $is_uid, $query_items); 1838 1839 return $result; 1840 } 1841 1764 1842 function fetchHeader($mailbox, $id, $uidfetch=false, $bodystr=false, $add='') 1765 1843 { 1766 $a = $this->fetchHeaders($mailbox, $id, $uidfetch, $bodystr, $add);1844 $a = $this->fetchHeaders($mailbox, $id, $uidfetch, $bodystr, $add); 1767 1845 if (is_array($a)) { 1768 1846 return array_shift($a); … … 2044 2122 } 2045 2123 if (!empty($criteria)) { 2124 $modseq = stripos($criteria, 'MODSEQ') !== false; 2046 2125 $params .= ($params ? ' ' : '') . $criteria; 2047 2126 } … … 2055 2134 if ($code == self::ERROR_OK) { 2056 2135 // remove prefix... 2057 $response = substr($response, stripos($response, 2136 $response = substr($response, stripos($response, 2058 2137 $esearch ? '* ESEARCH' : '* SEARCH') + ($esearch ? 10 : 9)); 2059 2138 // ...and unilateral untagged server responses … … 2062 2141 } 2063 2142 2143 // remove MODSEQ response 2144 if ($modseq) { 2145 if (preg_match('/\(MODSEQ ([0-9]+)\)$/', $response, $m)) { 2146 $response = substr($response, 0, -strlen($m[0])); 2147 } 2148 } 2149 2064 2150 if ($esearch) { 2065 2151 // Skip prefix: ... (TAG "A285") UID ... … … 2068 2154 $result = array(); 2069 2155 for ($i=0; $i<count($items); $i++) { 2070 // If the SEARCH re sults inno matches, the server MUST NOT2156 // If the SEARCH returns no matches, the server MUST NOT 2071 2157 // include the item result option in the ESEARCH response 2072 2158 if ($ret = $this->tokenizeResponse($response, 2)) { … … 2117 2203 * @return array List of mailboxes or hash of options if $status_opts argument 2118 2204 * is non-empty. 2119 * @access public2120 2205 */ 2121 2206 function listMailboxes($ref, $mailbox, $status_opts=array(), $select_opts=array()) … … 2133 2218 * @return array List of mailboxes or hash of options if $status_opts argument 2134 2219 * is non-empty. 2135 * @access public2136 2220 */ 2137 2221 function listSubscribed($ref, $mailbox, $status_opts=array()) … … 2153 2237 * @return array List of mailboxes or hash of options if $status_ops argument 2154 2238 * is non-empty. 2155 * @access private2156 2239 */ 2157 2240 private function _listMailboxes($ref, $mailbox, $subscribed=false, … … 2232 2315 } 2233 2316 2234 function fetchMIMEHeaders($mailbox, $ id, $parts, $mime=true)2317 function fetchMIMEHeaders($mailbox, $uid, $parts, $mime=true) 2235 2318 { 2236 2319 if (!$this->select($mailbox)) { … … 2250 2333 } 2251 2334 2252 $request = "$key FETCH $id (" . implode(' ', $peeks) . ')';2335 $request = "$key UID FETCH $uid (" . implode(' ', $peeks) . ')'; 2253 2336 2254 2337 // send request … … 2264 2347 if (preg_match('/BODY\[([0-9\.]+)\.'.$type.'\]/', $line, $matches)) { 2265 2348 $idx = $matches[1]; 2266 $result[$idx] = preg_replace('/^(\* '.$id.'FETCH \()?\s*BODY\['.$idx.'\.'.$type.'\]\s+/', '', $line);2349 $result[$idx] = preg_replace('/^(\* [0-9]+ FETCH \()?\s*BODY\['.$idx.'\.'.$type.'\]\s+/', '', $line); 2267 2350 $result[$idx] = trim($result[$idx], '"'); 2268 2351 $result[$idx] = rtrim($result[$idx], "\t\r\n\0\x0B"); … … 2571 2654 } 2572 2655 2573 function fetchStructureString($mailbox, $id, $is_uid=false)2574 {2575 if (!$this->select($mailbox)) {2576 return false;2577 }2578 2579 $key = $this->nextTag();2580 $result = false;2581 $command = $key . ($is_uid ? ' UID' : '') ." FETCH $id (BODYSTRUCTURE)";2582 2583 if ($this->putLine($command)) {2584 do {2585 $line = $this->readLine(5000);2586 $line = $this->multLine($line, true);2587 if (!preg_match("/^$key /", $line))2588 $result .= $line;2589 } while (!$this->startsWith($line, $key, true, true));2590 2591 $result = trim(substr($result, strpos($result, 'BODYSTRUCTURE')+13, -1));2592 }2593 else {2594 $this->setError(self::ERROR_COMMAND, "Unable to send command: $command");2595 }2596 2597 return $result;2598 }2599 2600 2656 function getQuota() 2601 2657 { … … 2661 2717 * @return boolean True on success, False on failure 2662 2718 * 2663 * @access public2664 2719 * @since 0.5-beta 2665 2720 */ … … 2685 2740 * @return boolean True on success, False on failure 2686 2741 * 2687 * @access public2688 2742 * @since 0.5-beta 2689 2743 */ … … 2703 2757 * 2704 2758 * @return array User-rights array on success, NULL on error 2705 * @access public2706 2759 * @since 0.5-beta 2707 2760 */ … … 2744 2797 * 2745 2798 * @return array List of user rights 2746 * @access public2747 2799 * @since 0.5-beta 2748 2800 */ … … 2776 2828 * 2777 2829 * @return array MYRIGHTS response on success, NULL on error 2778 * @access public2779 2830 * @since 0.5-beta 2780 2831 */ … … 2803 2854 * 2804 2855 * @return boolean True on success, False on failure 2805 * @access public2806 2856 * @since 0.5-beta 2807 2857 */ … … 2833 2883 * @return boolean True on success, False on failure 2834 2884 * 2835 * @access public2836 2885 * @since 0.5-beta 2837 2886 */ … … 2863 2912 * @return array GETMETADATA result on success, NULL on error 2864 2913 * 2865 * @access public2866 2914 * @since 0.5-beta 2867 2915 */ … … 2955 3003 * 2956 3004 * @return boolean True on success, False on failure 2957 * @access public2958 3005 * @since 0.5-beta 2959 3006 */ … … 2987 3034 * @return boolean True on success, False on failure 2988 3035 * 2989 * @access public2990 3036 * @since 0.5-beta 2991 3037 */ … … 3009 3055 * @return array Annotations result on success, NULL on error 3010 3056 * 3011 * @access public3012 3057 * @since 0.5-beta 3013 3058 */ … … 3094 3139 3095 3140 /** 3141 * Returns BODYSTRUCTURE for the specified message. 3142 * 3143 * @param string $mailbox Folder name 3144 * @param int $id Message sequence number or UID 3145 * @param bool $is_uid True if $id is an UID 3146 * 3147 * @return array/bool Body structure array or False on error. 3148 * @since 0.6 3149 */ 3150 function getStructure($mailbox, $id, $is_uid = false) 3151 { 3152 $result = $this->fetch($mailbox, $id, $is_uid, array('BODYSTRUCTURE')); 3153 if (is_array($result)) { 3154 $result = array_shift($result); 3155 return $result->bodystructure; 3156 } 3157 return false; 3158 } 3159 3160 static function getStructurePartType($structure, $part) 3161 { 3162 $part_a = self::getStructurePartArray($structure, $part); 3163 if (!empty($part_a)) { 3164 if (is_array($part_a[0])) 3165 return 'multipart'; 3166 else if ($part_a[0]) 3167 return $part_a[0]; 3168 } 3169 3170 return 'other'; 3171 } 3172 3173 static function getStructurePartEncoding($structure, $part) 3174 { 3175 $part_a = self::getStructurePartArray($structure, $part); 3176 if ($part_a) { 3177 if (!is_array($part_a[0])) 3178 return $part_a[5]; 3179 } 3180 3181 return ''; 3182 } 3183 3184 static function getStructurePartCharset($structure, $part) 3185 { 3186 $part_a = self::getStructurePartArray($structure, $part); 3187 if ($part_a) { 3188 if (is_array($part_a[0])) 3189 return ''; 3190 else { 3191 if (is_array($part_a[2])) { 3192 $name = ''; 3193 while (list($key, $val) = each($part_a[2])) 3194 if (strcasecmp($val, 'charset') == 0) 3195 return $part_a[2][$key+1]; 3196 } 3197 } 3198 } 3199 3200 return ''; 3201 } 3202 3203 static function getStructurePartArray($a, $part) 3204 { 3205 if (!is_array($a)) { 3206 return false; 3207 } 3208 if (strpos($part, '.') > 0) { 3209 $original_part = $part; 3210 $pos = strpos($part, '.'); 3211 $rest = substr($original_part, $pos+1); 3212 $part = substr($original_part, 0, $pos); 3213 if ((strcasecmp($a[0], 'message') == 0) && (strcasecmp($a[1], 'rfc822') == 0)) { 3214 $a = $a[8]; 3215 } 3216 return self::getStructurePartArray($a[$part-1], $rest); 3217 } 3218 else if ($part>0) { 3219 if (!is_array($a[0]) && (strcasecmp($a[0], 'message') == 0) 3220 && (strcasecmp($a[1], 'rfc822') == 0)) { 3221 $a = $a[8]; 3222 } 3223 if (is_array($a[$part-1])) 3224 return $a[$part-1]; 3225 else 3226 return $a; 3227 } 3228 else if (($part == 0) || (empty($part))) { 3229 return $a; 3230 } 3231 } 3232 3233 3234 /** 3096 3235 * Creates next command identifier (tag) 3097 3236 * 3098 3237 * @return string Command identifier 3099 * @access public3100 3238 * @since 0.5-beta 3101 3239 */ … … 3116 3254 * 3117 3255 * @return mixed Response code or list of response code and data 3118 * @access public3119 3256 * @since 0.5-beta 3120 3257 */ … … 3127 3264 3128 3265 if (!empty($arguments)) { 3129 $query .= ' ' . implode(' ', $arguments); 3266 foreach ($arguments as $arg) { 3267 $query .= ' ' . self::r_implode($arg); 3268 } 3130 3269 } 3131 3270 … … 3174 3313 * 3175 3314 * @return mixed Tokens array or string if $num=1 3176 * @access public3177 3315 * @since 0.5-beta 3178 3316 */ … … 3195 3333 // error 3196 3334 } 3197 $result[] = substr($str, $epos + 3, $bytes);3335 $result[] = $bytes ? substr($str, $epos + 3, $bytes) : ''; 3198 3336 // Advance the string 3199 3337 $str = substr($str, $epos + 3 + $bytes); … … 3224 3362 // Parenthesized list 3225 3363 case '(': 3364 case '[': 3226 3365 $str = substr($str, 1); 3227 3366 $result[] = self::tokenizeResponse($str); 3228 3367 break; 3229 3368 case ')': 3369 case ']': 3230 3370 $str = substr($str, 1); 3231 3371 return $result; … … 3244 3384 } 3245 3385 3246 // excluded chars: SP, CTL, ) 3247 if (preg_match('/^([^\x00-\x20\x29\x 7F]+)/', $str, $m)) {3386 // excluded chars: SP, CTL, ), [, ] 3387 if (preg_match('/^([^\x00-\x20\x29\x5B\x5D\x7F]+)/', $str, $m)) { 3248 3388 $result[] = $m[1] == 'NIL' ? NULL : $m[1]; 3249 3389 $str = substr($str, strlen($m[1])); … … 3254 3394 3255 3395 return $num == 1 ? $result[0] : $result; 3396 } 3397 3398 static function r_implode($element) 3399 { 3400 $string = ''; 3401 3402 if (is_array($element)) { 3403 reset($element); 3404 while (list($key, $value) = each($element)) { 3405 $string .= ' ' . self::r_implode($value); 3406 } 3407 } 3408 else { 3409 return $element; 3410 } 3411 3412 return '(' . trim($string) . ')'; 3256 3413 } 3257 3414 … … 3349 3506 * @param boolean $debug New value for the debugging flag. 3350 3507 * 3351 * @access public3352 3508 * @since 0.5-stable 3353 3509 */ … … 3363 3519 * @param string $message Debug mesage text. 3364 3520 * 3365 * @access private3366 3521 * @since 0.5-stable 3367 3522 */ -
trunk/roundcubemail/program/include/rcube_message.php
r5149 r5190 78 78 79 79 $this->uid = $uid; 80 $this->headers = $this->imap->get_ headers($uid, NULL, true, true);80 $this->headers = $this->imap->get_message($uid); 81 81 82 82 if (!$this->headers) … … 95 95 ); 96 96 97 if ( $this->structure = $this->imap->get_structure($uid, $this->headers->body_structure)) {98 $this->get_mime_numbers($this-> structure);99 $this->parse_structure($this-> structure);97 if (!empty($this->headers->structure)) { 98 $this->get_mime_numbers($this->headers->structure); 99 $this->parse_structure($this->headers->structure); 100 100 } 101 101 else { -
trunk/roundcubemail/program/include/rcube_mime_struct.php
r4980 r5190 1 <?php2 1 3 4 /* 5 +-----------------------------------------------------------------------+ 6 | program/include/rcube_mime_struct.php | 7 | | 8 | This file is part of the Roundcube Webmail client | 9 | Copyright (C) 2005-2011, The Roundcube Dev Team | 10 | Licensed under the GNU GPL | 11 | | 12 | PURPOSE: | 13 | Provide functions for handling mime messages structure | 14 | | 15 | Based on Iloha MIME Library. See http://ilohamail.org/ for details | 16 | | 17 +-----------------------------------------------------------------------+ 18 | Author: Aleksander Machniak <alec@alec.pl> | 19 | Author: Ryo Chijiiwa <Ryo@IlohaMail.org> | 20 +-----------------------------------------------------------------------+ 21 22 $Id$ 23 24 */ 25 26 /** 27 * Helper class to process IMAP's BODYSTRUCTURE string 28 * 29 * @package Mail 30 * @author Aleksander Machniak <alec@alec.pl> 31 */ 32 class rcube_mime_struct 33 { 34 private $structure; 35 36 37 function __construct($str=null) 2 function getStructurePartType($structure, $part) 38 3 { 39 if ($str) 40 $this->structure = $this->parseStructure($str); 41 } 42 43 /* 44 * Parses IMAP's BODYSTRUCTURE string into array 45 */ 46 function parseStructure($str) 47 { 48 $line = substr($str, 1, strlen($str) - 2); 49 $line = str_replace(')(', ') (', $line); 50 51 $struct = rcube_imap_generic::tokenizeResponse($line); 52 if (!is_array($struct[0]) && (strcasecmp($struct[0], 'message') == 0) 53 && (strcasecmp($struct[1], 'rfc822') == 0)) { 54 $struct = array($struct); 55 } 56 57 return $struct; 58 } 59 60 /* 61 * Parses IMAP's BODYSTRUCTURE string into array and loads it into class internal variable 62 */ 63 function loadStructure($str) 64 { 65 if (empty($str)) 66 return true; 67 68 $this->structure = $this->parseStructure($str); 69 return (!empty($this->structure)); 70 } 71 72 function getPartType($part) 73 { 74 $part_a = $this->getPartArray($this->structure, $part); 4 $part_a = self::getPartArray($structure, $part); 75 5 if (!empty($part_a)) { 76 6 if (is_array($part_a[0])) … … 83 13 } 84 14 85 function get PartEncoding($part)15 function getStructurePartEncoding($structure, $part) 86 16 { 87 $part_a = $this->getPartArray($this->structure, $part);17 $part_a = self::getPartArray($structure, $part); 88 18 if ($part_a) { 89 19 if (!is_array($part_a[0])) … … 94 24 } 95 25 96 function get PartCharset($part)26 function getStructurePartCharset($structure, $part) 97 27 { 98 $part_a = $this->getPartArray($this->structure, $part);28 $part_a = self::getPartArray($structure, $part); 99 29 if ($part_a) { 100 30 if (is_array($part_a[0])) … … 113 43 } 114 44 115 function get PartArray($a, $part)45 function getStructurePartArray($a, $part) 116 46 { 117 47 if (!is_array($a)) { … … 138 68 return $a; 139 69 } 140 else if (($part ==0) || (empty($part))) {70 else if (($part == 0) || (empty($part))) { 141 71 return $a; 142 72 } 143 73 } 144 145 } -
trunk/roundcubemail/program/steps/mail/func.inc
r5138 r5190 1455 1455 $message = new rcube_message($message); 1456 1456 1457 if ($message->headers->mdn_to && !$message->headers->mdn _sent &&1457 if ($message->headers->mdn_to && !$message->headers->mdnsent && 1458 1458 ($IMAP->check_permflag('MDNSENT') || $IMAP->check_permflag('*'))) 1459 1459 { -
trunk/roundcubemail/program/steps/mail/show.inc
r5144 r5190 78 78 // check for unset disposition notification 79 79 if ($MESSAGE->headers->mdn_to && 80 !$MESSAGE->headers->mdn _sent && !$MESSAGE->headers->seen &&80 !$MESSAGE->headers->mdnsent && !$MESSAGE->headers->seen && 81 81 ($IMAP->check_permflag('MDNSENT') || $IMAP->check_permflag('*')) && 82 82 $mbox_name != $CONFIG['drafts_mbox'] &&
Note: See TracChangeset
for help on using the changeset viewer.
