Changeset 5190 in subversion


Ignore:
Timestamp:
Sep 7, 2011 7:07:03 AM (21 months ago)
Author:
alec
Message:
  • Rewritten messages caching (merged devel-mcache branch): Indexes are stored in a separate table, so there's no need to store all messages in a folder Added threads data caching Flags are stored separately, so flag change doesn't cause DELETE+INSERT, just UPDATE
  • Partial QRESYNC support
  • Improved FETCH response handling
  • Improvements in response tokenization method
Location:
trunk/roundcubemail
Files:
17 edited
1 copied

Legend:

Unmodified
Added
Removed
  • trunk/roundcubemail

  • trunk/roundcubemail/CHANGELOG

    r5185 r5190  
    22=========================== 
    33 
     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 
    411- Use 'From' and 'To' labels instead of 'Sender' and 'Recipient' 
    512- Fix username case-insensitivity issue in MySQL (#1488021) 
  • trunk/roundcubemail/SQL/mssql.initial.sql

    r5182 r5190  
    55        [created] [datetime] NOT NULL , 
    66        [data] [text] COLLATE Latin1_General_CI_AI NOT NULL  
     7) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY] 
     8GO 
     9 
     10CREATE 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] 
     16GO 
     17 
     18CREATE 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] 
     24GO 
     25 
     26CREATE 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 , 
    738) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY] 
    839GO 
     
    5485GO 
    5586 
    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 NULL  
    72 ) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY] 
    73 GO 
    74  
    7587CREATE TABLE [dbo].[session] ( 
    7688        [sess_id] [varchar] (32) COLLATE Latin1_General_CI_AI NOT NULL , 
     
    117129GO 
    118130 
     131ALTER TABLE [dbo].[cache_index] WITH NOCHECK ADD  
     132         PRIMARY KEY CLUSTERED  
     133        ( 
     134                [user_id],[mailbox] 
     135        ) ON [PRIMARY]  
     136GO 
     137 
     138ALTER TABLE [dbo].[cache_thread] WITH NOCHECK ADD  
     139         PRIMARY KEY CLUSTERED  
     140        ( 
     141                [user_id],[mailbox] 
     142        ) ON [PRIMARY]  
     143GO 
     144 
     145ALTER TABLE [dbo].[cache_messages] WITH NOCHECK ADD  
     146         PRIMARY KEY CLUSTERED  
     147        ( 
     148                [user_id],[mailbox],[uid] 
     149        ) ON [PRIMARY]  
     150GO 
     151 
    119152ALTER TABLE [dbo].[contacts] WITH NOCHECK ADD  
    120153        CONSTRAINT [PK_contacts_contact_id] PRIMARY KEY  CLUSTERED  
     
    142175        ( 
    143176                [identity_id] 
    144         )  ON [PRIMARY]  
    145 GO 
    146  
    147 ALTER TABLE [dbo].[messages] WITH NOCHECK ADD  
    148          PRIMARY KEY  CLUSTERED  
    149         ( 
    150                 [message_id] 
    151177        )  ON [PRIMARY]  
    152178GO 
     
    186212 
    187213CREATE  INDEX [IX_cache_created] ON [dbo].[cache]([created]) ON [PRIMARY] 
     214GO 
     215 
     216ALTER TABLE [dbo].[cache_index] ADD  
     217        CONSTRAINT [DF_cache_index_changed] DEFAULT (getdate()) FOR [changed] 
     218GO 
     219 
     220CREATE  INDEX [IX_cache_index_user_id] ON [dbo].[cache_index]([user_id]) ON [PRIMARY] 
     221GO 
     222 
     223ALTER TABLE [dbo].[cache_thread] ADD  
     224        CONSTRAINT [DF_cache_thread_changed] DEFAULT (getdate()) FOR [changed] 
     225GO 
     226 
     227CREATE  INDEX [IX_cache_thread_user_id] ON [dbo].[cache_thread]([user_id]) ON [PRIMARY] 
     228GO 
     229 
     230ALTER 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], 
     238GO 
     239 
     240CREATE  INDEX [IX_cache_messages_user_id] ON [dbo].[cache_messages]([user_id]) ON [PRIMARY] 
    188241GO 
    189242 
     
    239292GO 
    240293 
    241 ALTER TABLE [dbo].[messages] ADD  
    242         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 GO 
    255  
    256 CREATE  INDEX [IX_messages_user_id] ON [dbo].[messages]([user_id]) ON [PRIMARY] 
    257 GO 
    258  
    259 CREATE  INDEX [IX_messages_cache_key] ON [dbo].[messages]([cache_key]) ON [PRIMARY] 
    260 GO 
    261  
    262 CREATE  INDEX [IX_messages_uid] ON [dbo].[messages]([uid]) ON [PRIMARY] 
    263 GO 
    264  
    265 CREATE  INDEX [IX_messages_created] ON [dbo].[messages]([created]) ON [PRIMARY] 
    266 GO 
    267  
    268294ALTER TABLE [dbo].[session] ADD  
    269295        CONSTRAINT [DF_session_sess_id] DEFAULT ('') FOR [sess_id], 
     
    319345GO 
    320346 
    321 ALTER TABLE [dbo].[messages] ADD CONSTRAINT [FK_messages_user_id] 
     347ALTER 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 
     350GO 
     351 
     352ALTER 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 
     355GO 
     356 
     357ALTER TABLE [dbo].[cache_messages] ADD CONSTRAINT [FK_cache_messages_user_id] 
    322358    FOREIGN KEY ([user_id]) REFERENCES [dbo].[users] ([user_id]) 
    323359    ON DELETE CASCADE ON UPDATE CASCADE 
  • trunk/roundcubemail/SQL/mssql.upgrade.sql

    r5182 r5190  
    152152GO 
    153153 
     154DROP TABLE [dbo].[messages] 
     155GO 
     156CREATE 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] 
     162GO 
     163 
     164CREATE 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] 
     170GO 
     171 
     172CREATE 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] 
     185GO 
     186 
     187ALTER TABLE [dbo].[cache_index] WITH NOCHECK ADD  
     188         PRIMARY KEY CLUSTERED  
     189        ( 
     190                [user_id],[mailbox] 
     191        ) ON [PRIMARY]  
     192GO 
     193 
     194ALTER TABLE [dbo].[cache_thread] WITH NOCHECK ADD  
     195         PRIMARY KEY CLUSTERED  
     196        ( 
     197                [user_id],[mailbox] 
     198        ) ON [PRIMARY]  
     199GO 
     200 
     201ALTER TABLE [dbo].[cache_messages] WITH NOCHECK ADD  
     202         PRIMARY KEY CLUSTERED  
     203        ( 
     204                [user_id],[mailbox],[uid] 
     205        ) ON [PRIMARY]  
     206GO 
     207 
     208ALTER TABLE [dbo].[cache_index] ADD  
     209        CONSTRAINT [DF_cache_index_changed] DEFAULT (getdate()) FOR [changed] 
     210GO 
     211 
     212CREATE  INDEX [IX_cache_index_user_id] ON [dbo].[cache_index]([user_id]) ON [PRIMARY] 
     213GO 
     214 
     215ALTER TABLE [dbo].[cache_thread] ADD  
     216        CONSTRAINT [DF_cache_thread_changed] DEFAULT (getdate()) FOR [changed] 
     217GO 
     218 
     219CREATE  INDEX [IX_cache_thread_user_id] ON [dbo].[cache_thread]([user_id]) ON [PRIMARY] 
     220GO 
     221 
     222ALTER 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], 
     230GO 
     231 
     232CREATE  INDEX [IX_cache_messages_user_id] ON [dbo].[cache_messages]([user_id]) ON [PRIMARY] 
     233GO 
     234 
     235ALTER 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 
     238GO 
     239 
     240ALTER 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 
     243GO 
     244 
     245ALTER 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 
     248GO 
     249 
  • trunk/roundcubemail/SQL/mysql.initial.sql

    r5183 r5190  
    3434 
    3535 
    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  
    6336-- Table structure for table `cache` 
    6437 
     
    7447 INDEX `created_index` (`created`), 
    7548 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 
     54CREATE 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 
     68CREATE 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 
     82CREATE 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`) 
    7698) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */; 
    7799 
  • trunk/roundcubemail/SQL/mysql.update.sql

    r5183 r5190  
    171171  UNIQUE `uniqueness` (`user_id`, `type`, `name`) 
    172172) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */; 
     173 
     174DROP TABLE `messages`; 
     175 
     176CREATE 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 
     187CREATE 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 
     198CREATE 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  
    6868    identity_id integer DEFAULT nextval('identity_ids'::text) PRIMARY KEY, 
    6969    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, 
    7171    changed timestamp with time zone DEFAULT now() NOT NULL, 
    7272    del smallint DEFAULT 0 NOT NULL, 
     
    179179    cache_id integer DEFAULT nextval('cache_ids'::text) PRIMARY KEY, 
    180180    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, 
    182182    cache_key varchar(128) DEFAULT '' NOT NULL, 
    183183    created timestamp with time zone DEFAULT now() NOT NULL, 
     
    189189 
    190190-- 
    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 
     195CREATE 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 
     204CREATE 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 
     211CREATE 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 
     220CREATE 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 
     227CREATE 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 
     243CREATE INDEX cache_messages_changed_idx ON cache_messages (changed); 
    228244 
    229245-- 
  • trunk/roundcubemail/SQL/postgres.update.sql

    r5182 r5190  
    127127    CONSTRAINT searches_user_id_key UNIQUE (user_id, "type", name) 
    128128); 
     129 
     130DROP SEQUENCE messages_ids; 
     131DROP TABLE messages; 
     132 
     133CREATE 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 
     142CREATE INDEX cache_index_changed_idx ON cache_index (changed); 
     143 
     144CREATE 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 
     153CREATE INDEX cache_thread_changed_idx ON cache_thread (changed); 
     154 
     155CREATE 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 
     171CREATE INDEX cache_messages_changed_idx ON cache_messages (changed); 
  • trunk/roundcubemail/SQL/sqlite.initial.sql

    r5182 r5190  
    22 
    33--  
    4 -- Table structure for table `cache` 
     4-- Table structure for table cache 
    55--  
    66 
     
    1010  cache_key varchar(128) NOT NULL default '', 
    1111  created datetime NOT NULL default '0000-00-00 00:00:00', 
    12   data longtext NOT NULL 
     12  data text NOT NULL 
    1313); 
    1414 
     
    122122-- -------------------------------------------------------- 
    123123 
    124 --  
    125 -- Table structure for table messages 
    126 --  
    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 text 
    144 ); 
    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  
    152124-- 
    153125-- Table structure for table dictionary 
     
    177149 
    178150CREATE UNIQUE INDEX ix_searches_user_type_name (user_id, type, name); 
     151 
     152-- -------------------------------------------------------- 
     153 
     154-- 
     155-- Table structure for table cache_index 
     156-- 
     157 
     158CREATE 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 
     166CREATE INDEX ix_cache_index_changed ON cache_index (changed); 
     167 
     168-- -------------------------------------------------------- 
     169 
     170-- 
     171-- Table structure for table cache_thread 
     172-- 
     173 
     174CREATE 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 
     182CREATE INDEX ix_cache_thread_changed ON cache_thread (changed); 
     183 
     184-- -------------------------------------------------------- 
     185 
     186-- 
     187-- Table structure for table cache_messages 
     188-- 
     189 
     190CREATE 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 
     205CREATE INDEX ix_cache_messages_changed ON cache_messages (changed); 
  • trunk/roundcubemail/SQL/sqlite.update.sql

    r5182 r5190  
    224224DROP TABLE contacts_tmp; 
    225225 
     226 
    226227DELETE FROM messages; 
    227228DELETE FROM cache; 
    228229CREATE INDEX ix_contactgroupmembers_contact_id ON contactgroupmembers (contact_id); 
    229  
    230230 
    231231-- Updates from version 0.6-stable 
     
    248248 
    249249CREATE UNIQUE INDEX ix_searches_user_type_name (user_id, type, name); 
     250 
     251DROP TABLE messages; 
     252 
     253CREATE 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 
     261CREATE INDEX ix_cache_index_changed ON cache_index (changed); 
     262 
     263CREATE 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 
     271CREATE INDEX ix_cache_thread_changed ON cache_thread (changed); 
     272 
     273CREATE 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 
     288CREATE INDEX ix_cache_messages_changed ON cache_messages (changed); 
  • trunk/roundcubemail/program/include/main.inc

    r5181 r5190  
    170170  $ts = get_offset_time($rcmail->config->get('message_cache_lifetime', '30d'), -1); 
    171171 
    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)); 
    177183} 
    178184 
  • trunk/roundcubemail/program/include/rcube_imap.php

    r5150 r5190  
    4949 
    5050    /** 
    51      * Instance of rcube_mdb2 
    52      * 
    53      * @var rcube_mdb2 
    54      */ 
    55     private $db; 
     51     * Instance of rcube_imap_cache 
     52     * 
     53     * @var rcube_imap_cache 
     54     */ 
     55    private $mcache; 
    5656 
    5757    /** 
     
    6161     */ 
    6262    private $cache; 
     63 
     64    /** 
     65     * Internal (in-memory) cache 
     66     * 
     67     * @var array 
     68     */ 
     69    private $icache = array(); 
     70 
    6371    private $mailbox = 'INBOX'; 
    6472    private $delimiter = NULL; 
     
    6977    private $struct_charset = NULL; 
    7078    private $default_folders = array('INBOX'); 
    71     private $messages_caching = false; 
    72     private $icache = array(); 
    7379    private $uid_id_map = array(); 
    7480    private $msg_headers = array(); 
     
    7985    private $search_threads = false; 
    8086    private $search_sorted = false; 
    81     private $db_header_fields = array('idx', 'uid', 'subject', 'from', 'to', 'cc', 'date', 'size'); 
    8287    private $options = array('auth_method' => 'check'); 
    8388    private $host, $user, $pass, $port, $ssl; 
    8489    private $caching = false; 
     90    private $messages_caching = false; 
    8591 
    8692    /** 
     
    215221    { 
    216222        $this->conn->closeConnection(); 
     223        if ($this->mcache) 
     224            $this->mcache->close(); 
    217225    } 
    218226 
     
    690698            if ($status) { 
    691699                $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); 
    693701            } 
    694702        } 
     
    723731 
    724732            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']); 
    728736                } 
    729737                if ($status) { 
     
    740748                if ($status) { 
    741749                    $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); 
    743751                } 
    744752            } 
     
    775783            ); 
    776784        } 
    777         else if (is_array($result = $this->_fetch_threads($mailbox))) { 
     785        else if (is_array($result = $this->fetch_threads($mailbox))) { 
    778786            $dcount = count($result[1]); 
    779787            $result = array( 
     
    818826     * @param   string   $sort_order Sort order [ASC|DESC] 
    819827     * @param   int      $slice      Number of slice items to extract from result array 
     828     * 
    820829     * @return  array    Indexed array with message header objects 
    821      * @access  private 
    822830     * @see     rcube_imap::list_headers 
    823831     */ 
    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) 
    825833    { 
    826834        if (!strlen($mailbox)) 
     
    832840 
    833841        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); 
    835843 
    836844        $this->_set_sort_order($sort_field, $sort_order); 
    837845 
    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        } 
    860869        // retrieve headers from IMAP 
    861         $a_msg_headers = array(); 
    862  
    863870        // use message index sort as default sorting (for better performance) 
    864         if (!$this->sort_field) { 
     871        else if (!$this->sort_field) { 
    865872            if ($this->skip_deleted) { 
    866873                // @TODO: this could be cached 
    867874                if ($msg_index = $this->_search_index($mailbox, 'ALL UNDELETED')) { 
    868                     $max = max($msg_index); 
    869875                    list($begin, $end) = $this->_get_message_range(count($msg_index), $page); 
    870876                    $msg_index = array_slice($msg_index, $begin, $end-$begin); 
     
    883889            // fetch reqested headers from server 
    884890            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); 
    886892        } 
    887893        // use SORT command 
    888894        else if ($this->get_capability('SORT') && 
    889895            // 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 
    891898        ) { 
    892899            if (!empty($msg_index)) { 
    893900                list($begin, $end) = $this->_get_message_range(count($msg_index), $page); 
    894                 $max = max($msg_index); 
    895901                $msg_index = array_slice($msg_index, $begin, $end-$begin); 
     902                $is_uid    = true; 
    896903 
    897904                if ($slice) 
     
    899906 
    900907                // 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); 
    902909            } 
    903910        } 
    904911        // 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); 
    909917            list($begin, $end) = $this->_get_message_range(count($msg_index), $page); 
    910918            $msg_index = array_slice($msg_index, $begin, $end-$begin); 
     919            $is_uid    = true; 
    911920 
    912921            if ($slice) 
     
    914923 
    915924            // 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        } 
    925927 
    926928        // return empty array if no messages found 
     
    930932        // use this class for message sorting 
    931933        $sorter = new rcube_header_sorter(); 
    932         $sorter->set_sequence_numbers($msg_index); 
     934        $sorter->set_index($msg_index, $is_uid); 
    933935        $sorter->sort_headers($a_msg_headers); 
    934936 
    935         if ($this->sort_order == 'DESC') 
     937        if ($this->sort_order == 'DESC' && !$sorted) 
    936938            $a_msg_headers = array_reverse($a_msg_headers); 
    937939 
     
    947949     * @param   string   $sort_field Header field to sort by 
    948950     * @param   string   $sort_order Sort order [ASC|DESC] 
    949      * @param   boolean  $recursive  True if called recursively 
    950951     * @param   int      $slice      Number of slice items to extract from result array 
     952     * 
    951953     * @return  array    Indexed array with message header objects 
    952      * @access  private 
    953954     * @see     rcube_imap::list_headers 
    954955     */ 
    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) 
    956957    { 
    957958        $this->_set_sort_order($sort_field, $sort_order); 
    958959 
    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); 
    965968 
    966969        if (empty($thread_tree)) 
    967970            return array(); 
    968971 
    969         $msg_index = $this->_sort_threads($mailbox, $thread_tree); 
     972        $msg_index = $this->sort_threads($mailbox, $thread_tree); 
    970973 
    971974        return $this->_fetch_thread_headers($mailbox, 
     
    975978 
    976979    /** 
    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     * 
    980985     * @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 
    985994        if (empty($this->icache['threads'])) { 
    986995            // get all threads 
     
    10131022     * @param int     $page         List page number 
    10141023     * @param int     $slice        Number of threads to slice 
     1024     * 
    10151025     * @return array  Messages headers 
    10161026     * @access  private 
     
    10181028    private function _fetch_thread_headers($mailbox, $thread_tree, $msg_depth, $has_children, $msg_index, $page, $slice=0) 
    10191029    { 
    1020         $cache_key = $mailbox.'.msg'; 
    10211030        // now get IDs for current page 
    10221031        list($begin, $end) = $this->_get_message_range(count($msg_index), $page); 
     
    10391048 
    10401049        // 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); 
    10421051 
    10431052        // return empty array if no messages found 
     
    10471056        // use this class for message sorting 
    10481057        $sorter = new rcube_header_sorter(); 
    1049         $sorter->set_sequence_numbers($all_ids); 
     1058        $sorter->set_index($all_ids); 
    10501059        $sorter->sort_headers($a_msg_headers); 
    10511060 
     
    11361145 
    11371146            // fetch headers 
    1138             $this->_fetch_headers($mailbox, join(',',$msgs), $a_msg_headers, NULL); 
     1147            $a_msg_headers = $this->fetch_headers($mailbox, $msgs); 
    11391148 
    11401149            // I didn't found in RFC that FETCH always returns messages sorted by index 
    11411150            $sorter = new rcube_header_sorter(); 
    1142             $sorter->set_sequence_numbers($msgs); 
     1151            $sorter->set_index($msgs); 
    11431152            $sorter->sort_headers($a_msg_headers); 
    11441153 
     
    11661175 
    11671176            // fetch headers 
    1168             $this->_fetch_headers($mailbox, join(',',$msgs), $a_msg_headers, NULL); 
     1177            $a_msg_headers = $this->fetch_headers($mailbox, $msgs); 
    11691178 
    11701179            $sorter = new rcube_header_sorter(); 
    1171             $sorter->set_sequence_numbers($msgs); 
     1180            $sorter->set_index($msgs); 
    11721181            $sorter->sort_headers($a_msg_headers); 
    11731182 
     
    11851194                    $msgs = array_slice($msgs, -$slice, $slice); 
    11861195                // ...and fetch headers 
    1187                 $this->_fetch_headers($mailbox, join(',', $msgs), $a_msg_headers, NULL); 
     1196                $a_msg_headers = $this->fetch_headers($mailbox, $msgs); 
     1197 
    11881198 
    11891199                // return empty array if no messages found 
     
    11921202 
    11931203                $sorter = new rcube_header_sorter(); 
    1194                 $sorter->set_sequence_numbers($msgs); 
     1204                $sorter->set_index($msgs); 
    11951205                $sorter->sort_headers($a_msg_headers); 
    11961206 
     
    11991209            else { 
    12001210                // 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); 
    12021212 
    12031213                // return empty array if no messages found 
     
    12571267        $this->_set_sort_order($sort_field, $sort_order); 
    12581268 
    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)); 
    12601270 
    12611271        return $this->_fetch_thread_headers($mailbox, 
     
    12981308 
    12991309    /** 
    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 
    13071318     * @access private 
    13081319     */ 
    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 
    13111329        // 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) { 
    13191337            $a_msg_headers[$headers->uid] = $headers; 
    13201338        } 
    13211339 
    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; 
    13581341    } 
    13591342 
     
    14721455            else { 
    14731456                $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); 
    14751458 
    14761459                if (is_array($a_index)) { 
     
    14931476 
    14941477        // 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    { 
    15051504        // use message index sort as default sorting 
    1506         if (!$this->sort_field) { 
     1505        if (!$sort_field) { 
    15071506            if ($this->skip_deleted) { 
    15081507                $a_index = $this->_search_index($mailbox, 'ALL'); 
     
    15111510            } 
    15121511 
    1513             if ($a_index !== false && $this->sort_order == 'DESC') 
     1512            if ($a_index !== false && $sort_order == 'DESC') 
    15141513                $a_index = array_reverse($a_index); 
    1515  
    1516             $this->icache[$key] = $a_index; 
    15171514        } 
    15181515        // fetch complete message index 
    15191516        else if ($this->get_capability('SORT') && 
    15201517            ($a_index = $this->conn->sort($mailbox, 
    1521                 $this->sort_field, $this->skip_deleted ? 'UNDELETED' : '')) !== false 
     1518                $sort_field, $this->skip_deleted ? 'UNDELETED' : '')) !== false 
    15221519        ) { 
    1523             if ($this->sort_order == 'DESC') 
     1520            if ($sort_order == 'DESC') 
    15241521                $a_index = array_reverse($a_index); 
    1525  
    1526             $this->icache[$key] = $a_index; 
    15271522        } 
    15281523        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") 
    15311526                asort($a_index); 
    1532             else if ($this->sort_order=="DESC") 
     1527            else if ($sort_order=="DESC") 
    15331528                arsort($a_index); 
    15341529 
    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(); 
    15391534    } 
    15401535 
     
    15681563        if (isset($this->icache[$key])) 
    15691564            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 
    15811566        // get all threads (default sort order) 
    1582         list ($thread_tree) = $this->_fetch_threads($mailbox); 
     1567        list ($thread_tree) = $this->fetch_threads($mailbox); 
    15831568 
    15841569        $this->icache[$key] = $this->_flatten_threads($mailbox, $thread_tree); 
     
    15921577     * 
    15931578     * @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()) 
    15951580     * @param array  $ids         Message IDs if we know what we need (e.g. search result) 
    15961581     *                            for better performance 
     
    16041589            return array(); 
    16051590 
    1606         $msg_index = $this->_sort_threads($mailbox, $thread_tree, $ids); 
     1591        $msg_index = $this->sort_threads($mailbox, $thread_tree, $ids); 
    16071592 
    16081593        if ($this->sort_order == 'DESC') 
     
    16241609 
    16251610    /** 
    1626      * @param string $mailbox Mailbox name 
    1627      * @access private 
    1628      */ 
    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 messages 
    1636         if (is_array($cache_index) && empty($cache_index)) { 
    1637             $max = $this->_messagecount($mailbox); 
    1638             // syncing a big folder maybe slow 
    1639             @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 index 
    1661         if (isset($this->icache['folder_index'])) 
    1662             $a_message_index = &$this->icache['folder_index']; 
    1663         else 
    1664             $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 index 
    1670         foreach ($a_message_index as $id => $uid) { 
    1671             // message in cache at correct position 
    1672             if ($cache_index[$id] == $uid) { 
    1673                 unset($cache_index[$id]); 
    1674                 continue; 
    1675             } 
    1676  
    1677             // other message at this position 
    1678             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 position 
    1684             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_index 
    1693         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 cache 
    1700         if (!empty($for_update)) { 
    1701             // syncing a big folder maybe slow 
    1702             @set_time_limit(0); 
    1703             // To save memory do this in chunks 
    1704             $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     /** 
    17191611     * Invoke search request to IMAP server 
    17201612     * 
     
    17511643     * @param string $charset    Charset 
    17521644     * @param string $sort_field Sorting field 
     1645     * 
    17531646     * @return array   search results as list of message ids 
    1754      * @access private 
    17551647     * @see rcube_imap::search() 
    17561648     */ 
     
    17741666                list ($thread_tree, $msg_depth, $has_children) = $a_messages; 
    17751667                $a_messages = array( 
    1776                     'tree'      => $thread_tree, 
    1777                         'depth' => $msg_depth, 
    1778                         'children' => $has_children 
     1668                    'tree' => $thread_tree, 
     1669                    'depth'=> $msg_depth, 
     1670                    'children' => $has_children 
    17791671                ); 
    17801672            } 
     
    17881680 
    17891681            // 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) 
    17911683            if ($a_messages === false && $charset && $charset != 'US-ASCII') 
    17921684                $a_messages = $this->conn->sort($mailbox, $sort_field, 
     
    18301722     * @param  string  $str     Search string 
    18311723     * @param  boolean $ret_uid True if UIDs should be returned 
     1724     * 
    18321725     * @return array   Search results as list of message IDs or UIDs 
    1833      * @access public 
    18341726     */ 
    18351727    function search_once($mailbox='', $str=NULL, $ret_uid=false) 
     
    18851777     * @param  array $thread_tree Unsorted thread tree (rcube_imap_generic::thread() result) 
    18861778     * @param  array $ids         Message IDs if we know what we need (e.g. search result) 
     1779     * 
    18871780     * @return array Sorted roots IDs 
    1888      * @access private 
    1889      */ 
    1890     private function _sort_threads($mailbox, $thread_tree, $ids=NULL) 
    1891     { 
    1892         // THREAD=ORDEREDSUBJECT:       sorting by sent date of root message 
    1893         // THREAD=REFERENCES:   sorting by sent date of root message 
    1894         // 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 
    18951788        // default sorting 
    18961789        if (!$this->sort_field || ($this->sort_field == 'date' && $this->threading == 'REFS')) { 
    18971790            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            } 
    19011803            // use SORT command 
    1902             if ($this->get_capability('SORT') &&  
     1804            else if ($this->get_capability('SORT') && 
    19031805                ($a_index = $this->conn->sort($mailbox, $this->sort_field, 
    1904                         !empty($ids) ? $ids : ($this->skip_deleted ? 'UNDELETED' : ''))) !== false 
     1806                    !empty($ids) ? $ids : ($this->skip_deleted ? 'UNDELETED' : ''))) !== false 
    19051807            ) { 
    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 
    19091809            } 
    19101810            else { 
    19111811                // fetch specified headers for all messages and sort them 
    19121812                $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); 
    19241826        } 
    19251827    } 
     
    19291831     * THREAD=REFS sorting implementation 
    19301832     * 
    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     * 
    19331836     * @return array   Array of sorted roots messages 
    1934      * @access private 
    19351837     */ 
    19361838    private function _sort_thread_refs($tree, $index) 
     
    19811883        if (!empty($this->search_string)) 
    19821884            $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); 
    19841886 
    19851887        return $this->get_search_set(); 
     
    20091911     * Return message headers object of a specific message 
    20101912     * 
    2011      * @param int     $id       Message ID 
     1913     * @param int     $id       Message sequence ID or UID 
    20121914     * @param string  $mailbox  Mailbox to read from 
    2013      * @param boolean $is_uid   True if $id is the message UID 
    2014      * @param boolean $bodystr  True if we need also BODYSTRUCTURE in headers 
    2015      * @return object Message headers representation 
    2016      */ 
    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) 
    20181920    { 
    20191921        if (!strlen($mailbox)) { 
    20201922            $mailbox = $this->mailbox; 
    20211923        } 
    2022         $uid = $is_uid ? $id : $this->_id2uid($id, $mailbox); 
    20231924 
    20241925        // 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)) 
    20261964            return $headers; 
    20271965 
    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; 
    20711976 
    20721977        // set message charset from message headers 
     
    20911996            } 
    20921997            else 
    2093                 return false; 
     1998                return $headers; 
    20941999        } 
    20952000 
     
    21042009        } 
    21052010 
    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; 
    21132014    } 
    21142015 
     
    21752076            if ($mime_part_headers) { 
    21762077                $mime_part_headers = $this->conn->fetchMIMEHeaders($this->mailbox, 
    2177                     $this->_msg_id, $mime_part_headers); 
     2078                    $this->_msg_uid, $mime_part_headers); 
    21782079            } 
    21792080 
     
    22772178            if (empty($mime_headers)) { 
    22782179                $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); 
    22802181            } 
    22812182 
     
    23402241                if (!$headers) { 
    23412242                    $headers = $this->conn->fetchPartHeader( 
    2342                         $this->mailbox, $this->_msg_id, false, $part->mime_id); 
     2243                        $this->mailbox, $this->_msg_uid, true, $part->mime_id); 
    23432244                } 
    23442245                $filename_mime = ''; 
     
    23592260                if (!$headers) { 
    23602261                    $headers = $this->conn->fetchPartHeader( 
    2361                             $this->mailbox, $this->_msg_id, false, $part->mime_id); 
     2262                            $this->mailbox, $this->_msg_uid, true, $part->mime_id); 
    23622263                } 
    23632264                $filename_encoded = ''; 
     
    23782279                if (!$headers) { 
    23792280                    $headers = $this->conn->fetchPartHeader( 
    2380                         $this->mailbox, $this->_msg_id, false, $part->mime_id); 
     2281                        $this->mailbox, $this->_msg_uid, true, $part->mime_id); 
    23812282                } 
    23822283                $filename_mime = ''; 
     
    23972298                if (!$headers) { 
    23982299                    $headers = $this->conn->fetchPartHeader( 
    2399                         $this->mailbox, $this->_msg_id, false, $part->mime_id); 
     2300                        $this->mailbox, $this->_msg_uid, true, $part->mime_id); 
    24002301                } 
    24012302                $filename_encoded = ''; 
     
    24662367        // get part encoding if not provided 
    24672368        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); 
    24742370 
    24752371            $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); 
    24792375        } 
    24802376 
     
    25852481        if ($result) { 
    25862482            // 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                 else 
    2592                     $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); 
    25932489            } 
    25942490 
     
    27222618                    $this->refresh_search(); 
    27232619                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); 
    27272623                    $this->search_set = array_diff($this->search_set, $a_mids); 
    27282624                } 
    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)); 
    27372632        } 
    27382633 
     
    28192714                    $this->refresh_search(); 
    28202715                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); 
    28242719                    $this->search_set = array_diff($this->search_set, $a_mids); 
     2720                    unset($a_uids); 
     2721                    unset($a_mids); 
    28252722                } 
    28262723            } 
    28272724 
    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)); 
    28342727        } 
    28352728 
     
    28562749        } 
    28572750 
    2858         // make sure the message count cache is cleared as well 
     2751        // make sure the cache is cleared as well 
    28592752        if ($cleared) { 
    2860             $this->clear_message_cache($mailbox.'.msg'); 
     2753            $this->clear_message_cache($mailbox); 
    28612754            $a_mailbox_cache = $this->get_cache('messagecount'); 
    28622755            unset($a_mailbox_cache[$mailbox]); 
     
    28992792    { 
    29002793        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); 
    29022795        else 
    2903             $a_uids = NULL; 
     2796            $uids = null; 
    29042797 
    29052798        // force mailbox selection and check if mailbox is writeable 
     
    29162809 
    29172810        // CLOSE(+SELECT) should be faster than EXPUNGE 
    2918         if (empty($a_uids) || $a_uids == '1:*') 
     2811        if (empty($uids) || $all_mode) 
    29192812            $result = $this->conn->close(); 
    29202813        else 
    2921             $result = $this->conn->expunge($mailbox, $a_uids); 
     2814            $result = $this->conn->expunge($mailbox, $uids); 
    29222815 
    29232816        if ($result && $clear_cache) { 
    2924             $this->clear_message_cache($mailbox.'.msg'); 
     2817            $this->clear_message_cache($mailbox, $all_mode ? null : explode(',', $uids)); 
    29252818            $this->_clear_messagecount($mailbox); 
    29262819        } 
     
    29872880        } 
    29882881 
    2989         return $this->_uid2id($uid, $mailbox); 
     2882        return $this->uid2id($uid, $mailbox); 
    29902883    } 
    29912884 
     
    30052898        } 
    30062899 
    3007         return $this->_id2uid($id, $mailbox); 
     2900        return $this->id2uid($id, $mailbox); 
    30082901    } 
    30092902 
     
    33033196                    $this->conn->subscribe(preg_replace('/^'.preg_quote($mailbox, '/').'/', 
    33043197                        $new_name, $c_subscribed)); 
     3198 
     3199                    // clear cache 
     3200                    $this->clear_message_cache($c_subscribed); 
    33053201                } 
    33063202            } 
    33073203 
    33083204            // clear cache 
    3309             $this->clear_message_cache($mailbox.'.msg'); 
     3205            $this->clear_message_cache($mailbox); 
    33103206            $this->clear_cache('mailboxes', true); 
    33113207        } 
     
    33433239                    $this->conn->unsubscribe($c_mbox); 
    33443240                    if ($this->conn->deleteFolder($c_mbox)) { 
    3345                             $this->clear_message_cache($c_mbox.'.msg'); 
     3241                            $this->clear_message_cache($c_mbox); 
    33463242                    } 
    33473243                } 
     
    33493245 
    33503246            // clear mailbox-related cache 
    3351             $this->clear_message_cache($mailbox.'.msg'); 
     3247            $this->clear_message_cache($mailbox); 
    33523248            $this->clear_cache('mailboxes', true); 
    33533249        } 
     
    35053401 
    35063402        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; 
    35073433    } 
    35083434 
     
    38493775            if ($this->cache) 
    38503776                $this->cache->close(); 
    3851             $this->cache = null; 
     3777            $this->cache   = null; 
    38523778            $this->caching = false; 
    38533779        } 
     
    39193845     * 
    39203846     * @param boolean $set Flag 
    3921      * @access public 
    39223847     */ 
    39233848    function set_messages_caching($set) 
    39243849    { 
    3925         $rcmail = rcmail::get_instance(); 
    3926  
    3927         if ($set && ($dbh = $rcmail->get_dbh())) { 
    3928             $this->db = $dbh; 
     3850        if ($set) { 
    39293851            $this->messages_caching = true; 
    39303852        } 
    39313853        else { 
     3854            if ($this->mcache) 
     3855                $this->mcache->close(); 
     3856            $this->mcache = null; 
    39323857            $this->messages_caching = false; 
    39333858        } 
     
    39353860 
    39363861    /** 
    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 
    43333890 
    43343891 
     
    46284185 
    46294186    /** 
    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     * 
    46324193     * @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) 
    46364196    { 
    46374197        if (!strlen($mailbox)) { 
     
    46394199        } 
    46404200 
    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 
    46554223     * 
    46564224     * @return int Message UID 
    4657      * @access private 
    4658      */ 
    4659     private function _id2uid($id, $mailbox=null) 
     4225     */ 
     4226    function id2uid($id, $mailbox = null, $force = false) 
    46604227    { 
    46614228        if (!strlen($mailbox)) { 
     
    46674234        } 
    46684235 
    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)) 
    46704240            $uid = $this->conn->ID2UID($mailbox, $id); 
    4671         } 
    46724241 
    46734242        $this->uid_id_map[$mailbox][$uid] = $id; 
     
    49554524class rcube_header_sorter 
    49564525{ 
    4957     var $sequence_numbers = array(); 
     4526    private $seqs = array(); 
     4527    private $uids = array(); 
     4528 
    49584529 
    49594530    /** 
    49604531     * Set the predetermined sort order. 
    49614532     * 
    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; 
    49674544    } 
    49684545 
     
    49744551    function sort_headers(&$headers) 
    49754552    { 
    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")); 
    49844557    } 
    49854558 
     
    49974570 
    49984571        // then find each sequence number in my ordered list 
    4999         $posa = isset($this->sequence_numbers[$seqa]) ? intval($this->sequence_numbers[$seqa]) : -1; 
    5000         $posb = isset($this->sequence_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; 
    50014574 
    50024575        // return the relative position as the comparison value 
    50034576        return $posa - $posb; 
    50044577    } 
     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    } 
    50054594} 
  • trunk/roundcubemail/program/include/rcube_imap_generic.php

    r5116 r5190  
    4949    public $charset; 
    5050    public $ctype; 
    51     public $flags; 
    5251    public $timestamp; 
    53     public $body_structure; 
     52    public $bodystructure; 
    5453    public $internaldate; 
    5554    public $references; 
    5655    public $priority; 
    5756    public $mdn_to; 
    58     public $mdn_sent = false; 
     57 
     58    public $flags; 
     59    public $mdnsent = false; 
    5960    public $seen = false; 
    6061    public $deleted = false; 
     
    6263    public $forwarded = false; 
    6364    public $flagged = false; 
    64     public $has_children = false; 
    65     public $depth = 0; 
    66     public $unread_children = 0; 
    6765    public $others = array(); 
    6866} 
     
    8583    public $result; 
    8684    public $resultcode; 
     85    public $selected; 
    8786    public $data = array(); 
    8887    public $flags = array( 
     
    9796    ); 
    9897 
    99     private $selected; 
    10098    private $fp; 
    10199    private $host; 
     
    236234    } 
    237235 
    238     function multLine($line, $escape=false) 
     236    function multLine($line, $escape = false) 
    239237    { 
    240238        $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 
    246244            while (strlen($out) < $bytes) { 
    247245                $line = $this->readBytes($bytes); 
     
    251249            } 
    252250 
    253             $line = $a[1][0] . ($escape ? $this->escape($out) : $out); 
     251            $line = $str . ($escape ? $this->escape($out) : $out); 
    254252        } 
    255253 
     
    878876     * Executes SELECT command (if mailbox is already not in selected state) 
    879877     * 
    880      * @param string $mailbox Mailbox name 
     878     * @param string $mailbox      Mailbox name 
     879     * @param array  $qresync_data QRESYNC data (RFC5162) 
    881880     * 
    882881     * @return boolean True on success, false on error 
    883      * @access public 
    884      */ 
    885     function select($mailbox) 
     882     */ 
     883    function select($mailbox, $qresync_data = null) 
    886884    { 
    887885        if (!strlen($mailbox)) { 
     
    902900        } 
    903901*/ 
    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); 
    905917 
    906918        if ($code == self::ERROR_OK) { 
     
    910922                    $this->data[strtoupper($m[2])] = (int) $m[1]; 
    911923                } 
    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; 
    917957                } 
    918958            } 
     
    936976     * 
    937977     * @return array Status item-value hash 
    938      * @access public 
    939978     * @since 0.5-beta 
    940979     */ 
     
    9721011 
    9731012            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]; 
    9751014            } 
    9761015 
     
    9901029     * 
    9911030     * @return boolean True on success, False on error 
    992      * @access public 
    9931031     */ 
    9941032    function expunge($mailbox, $messages=NULL) 
     
    10231061     * 
    10241062     * @return boolean True on success, False on error 
    1025      * @access public 
    10261063     * @since 0.5 
    10271064     */ 
     
    10441081     * 
    10451082     * @return boolean True on success, False on error 
    1046      * @access public 
    10471083     */ 
    10481084    function subscribe($mailbox) 
     
    10601096     * 
    10611097     * @return boolean True on success, False on error 
    1062      * @access public 
    10631098     */ 
    10641099    function unsubscribe($mailbox) 
     
    10761111     * 
    10771112     * @return boolean True on success, False on error 
    1078      * @access public 
    10791113     */ 
    10801114    function deleteFolder($mailbox) 
     
    10921126     * 
    10931127     * @return boolean True on success, False on error 
    1094      * @access public 
    10951128     */ 
    10961129    function clearFolder($mailbox) 
     
    11171150     * 
    11181151     * @return int Number of messages, False on error 
    1119      * @access public 
    11201152     */ 
    11211153    function countMessages($mailbox, $refresh = false) 
     
    11501182     * 
    11511183     * @return int Number of messages, False on error 
    1152      * @access public 
    11531184     */ 
    11541185    function countRecent($mailbox) 
     
    11731204     * 
    11741205     * @return int Number of messages, False on error 
    1175      * @access public 
    11761206     */ 
    11771207    function countUnseen($mailbox) 
     
    12041234     * 
    12051235     * @return array Server identification information key/value hash 
    1206      * @access public 
    12071236     * @since 0.6 
    12081237     */ 
     
    12291258                $result[$items[$i]] = $items[$i+1]; 
    12301259            } 
     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); 
    12311291 
    12321292            return $result; 
     
    14731533     * 
    14741534     * @return int Message sequence identifier 
    1475      * @access public 
    14761535     */ 
    14771536    function UID2ID($mailbox, $uid) 
     
    14931552     * 
    14941553     * @return int Message unique identifier 
    1495      * @access public 
    14961554     */ 
    14971555    function ID2UID($mailbox, $id) 
    14981556    { 
    14991557        if (empty($id) || $id < 0) { 
    1500             return      null; 
     1558            return null; 
    15011559        } 
    15021560 
     
    15161574    function fetchUIDs($mailbox, $message_set=null) 
    15171575    { 
    1518         if (is_array($message_set)) 
    1519             $message_set = join(',', $message_set); 
    1520         else if (empty($message_set)) 
     1576        if (empty($message_set)) 
    15211577            $message_set = '1:*'; 
    15221578 
     
    15241580    } 
    15251581 
    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    { 
    15301598        if (!$this->select($mailbox)) { 
    15311599            return false; 
     
    15331601 
    15341602        $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 
    15401605        $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        } 
    15471612 
    15481613        if (!$this->putLine($request)) { 
     
    15501615            return false; 
    15511616        } 
     1617 
    15521618        do { 
    15531619            $line = $this->readLine(4096); 
    1554             $line = $this->multLine($line); 
    15551620 
    15561621            if (!$line) 
    15571622                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 ... 
    15581628 
    15591629            if (preg_match('/^\* ([0-9]+) FETCH/', $line, $m)) { 
     
    15661636 
    15671637                $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                                } 
    16131704                            } 
    16141705                        } 
    16151706                    } 
    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); 
    16591716                        } 
    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 
    16631744                    while (list($lines_key, $str) = each($lines)) { 
    16641745                        list($field, $string) = explode(':', $str, 2); 
     
    17241805                            } 
    17251806                            break; 
    1726                         } // end switch () 
    1727                     } // end while () 
    1728                 } 
    1729  
    1730                 // process flags 
    1731                 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; 
    17551807                        } 
    17561808                    } 
    17571809                } 
    17581810            } 
     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 
    17591822        } while (!$this->startsWith($line, $key, true)); 
    17601823 
     
    17621825    } 
    17631826 
     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 
    17641842    function fetchHeader($mailbox, $id, $uidfetch=false, $bodystr=false, $add='') 
    17651843    { 
    1766         $a  = $this->fetchHeaders($mailbox, $id, $uidfetch, $bodystr, $add); 
     1844        $a = $this->fetchHeaders($mailbox, $id, $uidfetch, $bodystr, $add); 
    17671845        if (is_array($a)) { 
    17681846            return array_shift($a); 
     
    20442122        } 
    20452123        if (!empty($criteria)) { 
     2124            $modseq = stripos($criteria, 'MODSEQ') !== false; 
    20462125            $params .= ($params ? ' ' : '') . $criteria; 
    20472126        } 
     
    20552134        if ($code == self::ERROR_OK) { 
    20562135            // remove prefix... 
    2057             $response = substr($response, stripos($response,  
     2136            $response = substr($response, stripos($response, 
    20582137                $esearch ? '* ESEARCH' : '* SEARCH') + ($esearch ? 10 : 9)); 
    20592138            // ...and unilateral untagged server responses 
     
    20622141            } 
    20632142 
     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 
    20642150            if ($esearch) { 
    20652151                // Skip prefix: ... (TAG "A285") UID ... 
     
    20682154                $result = array(); 
    20692155                for ($i=0; $i<count($items); $i++) { 
    2070                     // If the SEARCH results in no matches, the server MUST NOT 
     2156                    // If the SEARCH returns no matches, the server MUST NOT 
    20712157                    // include the item result option in the ESEARCH response 
    20722158                    if ($ret = $this->tokenizeResponse($response, 2)) { 
     
    21172203     * @return array List of mailboxes or hash of options if $status_opts argument 
    21182204     *               is non-empty. 
    2119      * @access public 
    21202205     */ 
    21212206    function listMailboxes($ref, $mailbox, $status_opts=array(), $select_opts=array()) 
     
    21332218     * @return array List of mailboxes or hash of options if $status_opts argument 
    21342219     *               is non-empty. 
    2135      * @access public 
    21362220     */ 
    21372221    function listSubscribed($ref, $mailbox, $status_opts=array()) 
     
    21532237     * @return array List of mailboxes or hash of options if $status_ops argument 
    21542238     *               is non-empty. 
    2155      * @access private 
    21562239     */ 
    21572240    private function _listMailboxes($ref, $mailbox, $subscribed=false, 
     
    22322315    } 
    22332316 
    2234     function fetchMIMEHeaders($mailbox, $id, $parts, $mime=true) 
     2317    function fetchMIMEHeaders($mailbox, $uid, $parts, $mime=true) 
    22352318    { 
    22362319        if (!$this->select($mailbox)) { 
     
    22502333        } 
    22512334 
    2252         $request = "$key FETCH $id (" . implode(' ', $peeks) . ')'; 
     2335        $request = "$key UID FETCH $uid (" . implode(' ', $peeks) . ')'; 
    22532336 
    22542337        // send request 
     
    22642347            if (preg_match('/BODY\[([0-9\.]+)\.'.$type.'\]/', $line, $matches)) { 
    22652348                $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); 
    22672350                $result[$idx] = trim($result[$idx], '"'); 
    22682351                $result[$idx] = rtrim($result[$idx], "\t\r\n\0\x0B"); 
     
    25712654    } 
    25722655 
    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  
    26002656    function getQuota() 
    26012657    { 
     
    26612717     * @return boolean True on success, False on failure 
    26622718     * 
    2663      * @access public 
    26642719     * @since 0.5-beta 
    26652720     */ 
     
    26852740     * @return boolean True on success, False on failure 
    26862741     * 
    2687      * @access public 
    26882742     * @since 0.5-beta 
    26892743     */ 
     
    27032757     * 
    27042758     * @return array User-rights array on success, NULL on error 
    2705      * @access public 
    27062759     * @since 0.5-beta 
    27072760     */ 
     
    27442797     * 
    27452798     * @return array List of user rights 
    2746      * @access public 
    27472799     * @since 0.5-beta 
    27482800     */ 
     
    27762828     * 
    27772829     * @return array MYRIGHTS response on success, NULL on error 
    2778      * @access public 
    27792830     * @since 0.5-beta 
    27802831     */ 
     
    28032854     * 
    28042855     * @return boolean True on success, False on failure 
    2805      * @access public 
    28062856     * @since 0.5-beta 
    28072857     */ 
     
    28332883     * @return boolean True on success, False on failure 
    28342884     * 
    2835      * @access public 
    28362885     * @since 0.5-beta 
    28372886     */ 
     
    28632912     * @return array GETMETADATA result on success, NULL on error 
    28642913     * 
    2865      * @access public 
    28662914     * @since 0.5-beta 
    28672915     */ 
     
    29553003     * 
    29563004     * @return boolean True on success, False on failure 
    2957      * @access public 
    29583005     * @since 0.5-beta 
    29593006     */ 
     
    29873034     * @return boolean True on success, False on failure 
    29883035     * 
    2989      * @access public 
    29903036     * @since 0.5-beta 
    29913037     */ 
     
    30093055     * @return array Annotations result on success, NULL on error 
    30103056     * 
    3011      * @access public 
    30123057     * @since 0.5-beta 
    30133058     */ 
     
    30943139 
    30953140    /** 
     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    /** 
    30963235     * Creates next command identifier (tag) 
    30973236     * 
    30983237     * @return string Command identifier 
    3099      * @access public 
    31003238     * @since 0.5-beta 
    31013239     */ 
     
    31163254     * 
    31173255     * @return mixed Response code or list of response code and data 
    3118      * @access public 
    31193256     * @since 0.5-beta 
    31203257     */ 
     
    31273264 
    31283265        if (!empty($arguments)) { 
    3129             $query .= ' ' . implode(' ', $arguments); 
     3266            foreach ($arguments as $arg) { 
     3267                $query .= ' ' . self::r_implode($arg); 
     3268            } 
    31303269        } 
    31313270 
     
    31743313     * 
    31753314     * @return mixed Tokens array or string if $num=1 
    3176      * @access public 
    31773315     * @since 0.5-beta 
    31783316     */ 
     
    31953333                    // error 
    31963334                } 
    3197                 $result[] = substr($str, $epos + 3, $bytes); 
     3335                $result[] = $bytes ? substr($str, $epos + 3, $bytes) : ''; 
    31983336                // Advance the string 
    31993337                $str = substr($str, $epos + 3 + $bytes); 
     
    32243362            // Parenthesized list 
    32253363            case '(': 
     3364            case '[': 
    32263365                $str = substr($str, 1); 
    32273366                $result[] = self::tokenizeResponse($str); 
    32283367                break; 
    32293368            case ')': 
     3369            case ']': 
    32303370                $str = substr($str, 1); 
    32313371                return $result; 
     
    32443384                } 
    32453385 
    3246                 // excluded chars: SP, CTL, ) 
    3247                 if (preg_match('/^([^\x00-\x20\x29\x7F]+)/', $str, $m)) { 
     3386                // excluded chars: SP, CTL, ), [, ] 
     3387                if (preg_match('/^([^\x00-\x20\x29\x5B\x5D\x7F]+)/', $str, $m)) { 
    32483388                    $result[] = $m[1] == 'NIL' ? NULL : $m[1]; 
    32493389                    $str = substr($str, strlen($m[1])); 
     
    32543394 
    32553395        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) . ')'; 
    32563413    } 
    32573414 
     
    33493506     * @param   boolean $debug      New value for the debugging flag. 
    33503507     * 
    3351      * @access  public 
    33523508     * @since   0.5-stable 
    33533509     */ 
     
    33633519     * @param   string  $message    Debug mesage text. 
    33643520     * 
    3365      * @access  private 
    33663521     * @since   0.5-stable 
    33673522     */ 
  • trunk/roundcubemail/program/include/rcube_message.php

    r5149 r5190  
    7878 
    7979        $this->uid = $uid; 
    80         $this->headers = $this->imap->get_headers($uid, NULL, true, true); 
     80        $this->headers = $this->imap->get_message($uid); 
    8181 
    8282        if (!$this->headers) 
     
    9595        ); 
    9696 
    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); 
    100100        } 
    101101        else { 
  • trunk/roundcubemail/program/include/rcube_mime_struct.php

    r4980 r5190  
    1 <?php 
    21 
    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) 
    383    { 
    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); 
    755            if (!empty($part_a)) { 
    766                    if (is_array($part_a[0])) 
     
    8313    } 
    8414 
    85     function getPartEncoding($part) 
     15    function getStructurePartEncoding($structure, $part) 
    8616    { 
    87             $part_a = $this->getPartArray($this->structure, $part); 
     17            $part_a = self::getPartArray($structure, $part); 
    8818            if ($part_a) { 
    8919                    if (!is_array($part_a[0])) 
     
    9424    } 
    9525 
    96     function getPartCharset($part) 
     26    function getStructurePartCharset($structure, $part) 
    9727    { 
    98             $part_a = $this->getPartArray($this->structure, $part); 
     28            $part_a = self::getPartArray($structure, $part); 
    9929            if ($part_a) { 
    10030                    if (is_array($part_a[0])) 
     
    11343    } 
    11444 
    115     function getPartArray($a, $part) 
     45    function getStructurePartArray($a, $part) 
    11646    { 
    11747            if (!is_array($a)) { 
     
    13868                return $a; 
    13969            } 
    140         else if (($part==0) || (empty($part))) { 
     70        else if (($part == 0) || (empty($part))) { 
    14171                    return $a; 
    14272            } 
    14373    } 
    144  
    145 } 
  • trunk/roundcubemail/program/steps/mail/func.inc

    r5138 r5190  
    14551455    $message = new rcube_message($message); 
    14561456 
    1457   if ($message->headers->mdn_to && !$message->headers->mdn_sent && 
     1457  if ($message->headers->mdn_to && !$message->headers->mdnsent && 
    14581458    ($IMAP->check_permflag('MDNSENT') || $IMAP->check_permflag('*'))) 
    14591459  { 
  • trunk/roundcubemail/program/steps/mail/show.inc

    r5144 r5190  
    7878  // check for unset disposition notification 
    7979  if ($MESSAGE->headers->mdn_to && 
    80       !$MESSAGE->headers->mdn_sent && !$MESSAGE->headers->seen && 
     80      !$MESSAGE->headers->mdnsent && !$MESSAGE->headers->seen && 
    8181      ($IMAP->check_permflag('MDNSENT') || $IMAP->check_permflag('*')) && 
    8282      $mbox_name != $CONFIG['drafts_mbox'] && 
Note: See TracChangeset for help on using the changeset viewer.