0
|
1 <?php
|
|
2
|
|
3 /**
|
|
4 +-----------------------------------------------------------------------+
|
|
5 | This file is part of the Roundcube Webmail client |
|
|
6 | Copyright (C) 2005-2012, The Roundcube Dev Team |
|
|
7 | |
|
|
8 | Licensed under the GNU General Public License version 3 or |
|
|
9 | any later version with exceptions for skins & plugins. |
|
|
10 | See the README file for a full license statement. |
|
|
11 | |
|
|
12 | PURPOSE: |
|
|
13 | Caching of IMAP folder contents (messages and index) |
|
|
14 +-----------------------------------------------------------------------+
|
|
15 | Author: Thomas Bruederli <roundcube@gmail.com> |
|
|
16 | Author: Aleksander Machniak <alec@alec.pl> |
|
|
17 +-----------------------------------------------------------------------+
|
|
18 */
|
|
19
|
|
20 /**
|
|
21 * Interface class for accessing Roundcube messages cache
|
|
22 *
|
|
23 * @package Framework
|
|
24 * @subpackage Storage
|
|
25 * @author Thomas Bruederli <roundcube@gmail.com>
|
|
26 * @author Aleksander Machniak <alec@alec.pl>
|
|
27 */
|
|
28 class rcube_imap_cache
|
|
29 {
|
|
30 const MODE_INDEX = 1;
|
|
31 const MODE_MESSAGE = 2;
|
|
32
|
|
33 /**
|
|
34 * Instance of rcube_imap
|
|
35 *
|
|
36 * @var rcube_imap
|
|
37 */
|
|
38 private $imap;
|
|
39
|
|
40 /**
|
|
41 * Instance of rcube_db
|
|
42 *
|
|
43 * @var rcube_db
|
|
44 */
|
|
45 private $db;
|
|
46
|
|
47 /**
|
|
48 * User ID
|
|
49 *
|
|
50 * @var int
|
|
51 */
|
|
52 private $userid;
|
|
53
|
|
54 /**
|
|
55 * Expiration time in seconds
|
|
56 *
|
|
57 * @var int
|
|
58 */
|
|
59 private $ttl;
|
|
60
|
|
61 /**
|
|
62 * Maximum cached message size
|
|
63 *
|
|
64 * @var int
|
|
65 */
|
|
66 private $threshold;
|
|
67
|
|
68 /**
|
|
69 * Internal (in-memory) cache
|
|
70 *
|
|
71 * @var array
|
|
72 */
|
|
73 private $icache = array();
|
|
74
|
|
75 private $skip_deleted = false;
|
|
76 private $mode;
|
|
77
|
|
78 /**
|
|
79 * List of known flags. Thanks to this we can handle flag changes
|
|
80 * with good performance. Bad thing is we need to know used flags.
|
|
81 */
|
|
82 public $flags = array(
|
|
83 1 => 'SEEN', // RFC3501
|
|
84 2 => 'DELETED', // RFC3501
|
|
85 4 => 'ANSWERED', // RFC3501
|
|
86 8 => 'FLAGGED', // RFC3501
|
|
87 16 => 'DRAFT', // RFC3501
|
|
88 32 => 'MDNSENT', // RFC3503
|
|
89 64 => 'FORWARDED', // RFC5550
|
|
90 128 => 'SUBMITPENDING', // RFC5550
|
|
91 256 => 'SUBMITTED', // RFC5550
|
|
92 512 => 'JUNK',
|
|
93 1024 => 'NONJUNK',
|
|
94 2048 => 'LABEL1',
|
|
95 4096 => 'LABEL2',
|
|
96 8192 => 'LABEL3',
|
|
97 16384 => 'LABEL4',
|
|
98 32768 => 'LABEL5',
|
|
99 );
|
|
100
|
|
101
|
|
102 /**
|
|
103 * Object constructor.
|
|
104 *
|
|
105 * @param rcube_db $db DB handler
|
|
106 * @param rcube_imap $imap IMAP handler
|
|
107 * @param int $userid User identifier
|
|
108 * @param bool $skip_deleted skip_deleted flag
|
|
109 * @param string $ttl Expiration time of memcache/apc items
|
|
110 * @param int $threshold Maximum cached message size
|
|
111 */
|
|
112 function __construct($db, $imap, $userid, $skip_deleted, $ttl=0, $threshold=0)
|
|
113 {
|
|
114 // convert ttl string to seconds
|
|
115 $ttl = get_offset_sec($ttl);
|
|
116 if ($ttl > 2592000) $ttl = 2592000;
|
|
117
|
|
118 $this->db = $db;
|
|
119 $this->imap = $imap;
|
|
120 $this->userid = $userid;
|
|
121 $this->skip_deleted = $skip_deleted;
|
|
122 $this->ttl = $ttl;
|
|
123 $this->threshold = $threshold;
|
|
124
|
|
125 // cache all possible information by default
|
|
126 $this->mode = self::MODE_INDEX | self::MODE_MESSAGE;
|
|
127
|
|
128 // database tables
|
|
129 $this->index_table = $db->table_name('cache_index', true);
|
|
130 $this->thread_table = $db->table_name('cache_thread', true);
|
|
131 $this->messages_table = $db->table_name('cache_messages', true);
|
|
132 }
|
|
133
|
|
134 /**
|
|
135 * Cleanup actions (on shutdown).
|
|
136 */
|
|
137 public function close()
|
|
138 {
|
|
139 $this->save_icache();
|
|
140 $this->icache = null;
|
|
141 }
|
|
142
|
|
143 /**
|
|
144 * Set cache mode
|
|
145 *
|
|
146 * @param int $mode Cache mode
|
|
147 */
|
|
148 public function set_mode($mode)
|
|
149 {
|
|
150 $this->mode = $mode;
|
|
151 }
|
|
152
|
|
153 /**
|
|
154 * Return (sorted) messages index (UIDs).
|
|
155 * If index doesn't exist or is invalid, will be updated.
|
|
156 *
|
|
157 * @param string $mailbox Folder name
|
|
158 * @param string $sort_field Sorting column
|
|
159 * @param string $sort_order Sorting order (ASC|DESC)
|
|
160 * @param bool $exiting Skip index initialization if it doesn't exist in DB
|
|
161 *
|
|
162 * @return array Messages index
|
|
163 */
|
|
164 function get_index($mailbox, $sort_field = null, $sort_order = null, $existing = false)
|
|
165 {
|
|
166 if (empty($this->icache[$mailbox])) {
|
|
167 $this->icache[$mailbox] = array();
|
|
168 }
|
|
169
|
|
170 $sort_order = strtoupper($sort_order) == 'ASC' ? 'ASC' : 'DESC';
|
|
171
|
|
172 // Seek in internal cache
|
|
173 if (array_key_exists('index', $this->icache[$mailbox])) {
|
|
174 // The index was fetched from database already, but not validated yet
|
|
175 if (empty($this->icache[$mailbox]['index']['validated'])) {
|
|
176 $index = $this->icache[$mailbox]['index'];
|
|
177 }
|
|
178 // We've got a valid index
|
|
179 else if ($sort_field == 'ANY' || $this->icache[$mailbox]['index']['sort_field'] == $sort_field) {
|
|
180 $result = $this->icache[$mailbox]['index']['object'];
|
|
181 if ($result->get_parameters('ORDER') != $sort_order) {
|
|
182 $result->revert();
|
|
183 }
|
|
184 return $result;
|
|
185 }
|
|
186 }
|
|
187
|
|
188 // Get index from DB (if DB wasn't already queried)
|
|
189 if (empty($index) && empty($this->icache[$mailbox]['index_queried'])) {
|
|
190 $index = $this->get_index_row($mailbox);
|
|
191
|
|
192 // set the flag that DB was already queried for index
|
|
193 // this way we'll be able to skip one SELECT, when
|
|
194 // get_index() is called more than once
|
|
195 $this->icache[$mailbox]['index_queried'] = true;
|
|
196 }
|
|
197
|
|
198 $data = null;
|
|
199
|
|
200 // @TODO: Think about skipping validation checks.
|
|
201 // If we could check only every 10 minutes, we would be able to skip
|
|
202 // expensive checks, mailbox selection or even IMAP connection, this would require
|
|
203 // additional logic to force cache invalidation in some cases
|
|
204 // and many rcube_imap changes to connect when needed
|
|
205
|
|
206 // Entry exists, check cache status
|
|
207 if (!empty($index)) {
|
|
208 $exists = true;
|
|
209
|
|
210 if ($sort_field == 'ANY') {
|
|
211 $sort_field = $index['sort_field'];
|
|
212 }
|
|
213
|
|
214 if ($sort_field != $index['sort_field']) {
|
|
215 $is_valid = false;
|
|
216 }
|
|
217 else {
|
|
218 $is_valid = $this->validate($mailbox, $index, $exists);
|
|
219 }
|
|
220
|
|
221 if ($is_valid) {
|
|
222 $data = $index['object'];
|
|
223 // revert the order if needed
|
|
224 if ($data->get_parameters('ORDER') != $sort_order) {
|
|
225 $data->revert();
|
|
226 }
|
|
227 }
|
|
228 }
|
|
229 else {
|
|
230 if ($existing) {
|
|
231 return null;
|
|
232 }
|
|
233 else if ($sort_field == 'ANY') {
|
|
234 $sort_field = '';
|
|
235 }
|
|
236
|
|
237 // Got it in internal cache, so the row already exist
|
|
238 $exists = array_key_exists('index', $this->icache[$mailbox]);
|
|
239 }
|
|
240
|
|
241 // Index not found, not valid or sort field changed, get index from IMAP server
|
|
242 if ($data === null) {
|
|
243 // Get mailbox data (UIDVALIDITY, counters, etc.) for status check
|
|
244 $mbox_data = $this->imap->folder_data($mailbox);
|
|
245 $data = $this->get_index_data($mailbox, $sort_field, $sort_order, $mbox_data);
|
|
246
|
|
247 // insert/update
|
|
248 $this->add_index_row($mailbox, $sort_field, $data, $mbox_data, $exists, $index['modseq']);
|
|
249 }
|
|
250
|
|
251 $this->icache[$mailbox]['index'] = array(
|
|
252 'validated' => true,
|
|
253 'object' => $data,
|
|
254 'sort_field' => $sort_field,
|
|
255 'modseq' => !empty($index['modseq']) ? $index['modseq'] : $mbox_data['HIGHESTMODSEQ']
|
|
256 );
|
|
257
|
|
258 return $data;
|
|
259 }
|
|
260
|
|
261 /**
|
|
262 * Return messages thread.
|
|
263 * If threaded index doesn't exist or is invalid, will be updated.
|
|
264 *
|
|
265 * @param string $mailbox Folder name
|
|
266 *
|
|
267 * @return array Messages threaded index
|
|
268 */
|
|
269 function get_thread($mailbox)
|
|
270 {
|
|
271 if (empty($this->icache[$mailbox])) {
|
|
272 $this->icache[$mailbox] = array();
|
|
273 }
|
|
274
|
|
275 // Seek in internal cache
|
|
276 if (array_key_exists('thread', $this->icache[$mailbox])) {
|
|
277 return $this->icache[$mailbox]['thread']['object'];
|
|
278 }
|
|
279
|
|
280 // Get thread from DB (if DB wasn't already queried)
|
|
281 if (empty($this->icache[$mailbox]['thread_queried'])) {
|
|
282 $index = $this->get_thread_row($mailbox);
|
|
283
|
|
284 // set the flag that DB was already queried for thread
|
|
285 // this way we'll be able to skip one SELECT, when
|
|
286 // get_thread() is called more than once or after clear()
|
|
287 $this->icache[$mailbox]['thread_queried'] = true;
|
|
288 }
|
|
289
|
|
290 // Entry exist, check cache status
|
|
291 if (!empty($index)) {
|
|
292 $exists = true;
|
|
293 $is_valid = $this->validate($mailbox, $index, $exists);
|
|
294
|
|
295 if (!$is_valid) {
|
|
296 $index = null;
|
|
297 }
|
|
298 }
|
|
299
|
|
300 // Index not found or not valid, get index from IMAP server
|
|
301 if ($index === null) {
|
|
302 // Get mailbox data (UIDVALIDITY, counters, etc.) for status check
|
|
303 $mbox_data = $this->imap->folder_data($mailbox);
|
|
304 // Get THREADS result
|
|
305 $index['object'] = $this->get_thread_data($mailbox, $mbox_data);
|
|
306
|
|
307 // insert/update
|
|
308 $this->add_thread_row($mailbox, $index['object'], $mbox_data, $exists);
|
|
309 }
|
|
310
|
|
311 $this->icache[$mailbox]['thread'] = $index;
|
|
312
|
|
313 return $index['object'];
|
|
314 }
|
|
315
|
|
316 /**
|
|
317 * Returns list of messages (headers). See rcube_imap::fetch_headers().
|
|
318 *
|
|
319 * @param string $mailbox Folder name
|
|
320 * @param array $msgs Message UIDs
|
|
321 *
|
|
322 * @return array The list of messages (rcube_message_header) indexed by UID
|
|
323 */
|
|
324 function get_messages($mailbox, $msgs = array())
|
|
325 {
|
|
326 if (empty($msgs)) {
|
|
327 return array();
|
|
328 }
|
|
329
|
|
330 $result = array();
|
|
331
|
|
332 if ($this->mode & self::MODE_MESSAGE) {
|
|
333 // Fetch messages from cache
|
|
334 $sql_result = $this->db->query(
|
|
335 "SELECT `uid`, `data`, `flags`"
|
|
336 ." FROM {$this->messages_table}"
|
|
337 ." WHERE `user_id` = ?"
|
|
338 ." AND `mailbox` = ?"
|
|
339 ." AND `uid` IN (".$this->db->array2list($msgs, 'integer').")",
|
|
340 $this->userid, $mailbox);
|
|
341
|
|
342 $msgs = array_flip($msgs);
|
|
343
|
|
344 while ($sql_arr = $this->db->fetch_assoc($sql_result)) {
|
|
345 $uid = intval($sql_arr['uid']);
|
|
346 $result[$uid] = $this->build_message($sql_arr);
|
|
347
|
|
348 if (!empty($result[$uid])) {
|
|
349 // save memory, we don't need message body here (?)
|
|
350 $result[$uid]->body = null;
|
|
351
|
|
352 unset($msgs[$uid]);
|
|
353 }
|
|
354 }
|
|
355
|
|
356 $this->db->reset();
|
|
357
|
|
358 $msgs = array_flip($msgs);
|
|
359 }
|
|
360
|
|
361 // Fetch not found messages from IMAP server
|
|
362 if (!empty($msgs)) {
|
|
363 $messages = $this->imap->fetch_headers($mailbox, $msgs, false, true);
|
|
364
|
|
365 // Insert to DB and add to result list
|
|
366 if (!empty($messages)) {
|
|
367 foreach ($messages as $msg) {
|
|
368 if ($this->mode & self::MODE_MESSAGE) {
|
|
369 $this->add_message($mailbox, $msg, !array_key_exists($msg->uid, $result));
|
|
370 }
|
|
371
|
|
372 $result[$msg->uid] = $msg;
|
|
373 }
|
|
374 }
|
|
375 }
|
|
376
|
|
377 return $result;
|
|
378 }
|
|
379
|
|
380 /**
|
|
381 * Returns message data.
|
|
382 *
|
|
383 * @param string $mailbox Folder name
|
|
384 * @param int $uid Message UID
|
|
385 * @param bool $update If message doesn't exists in cache it will be fetched
|
|
386 * from IMAP server
|
|
387 * @param bool $no_cache Enables internal cache usage
|
|
388 *
|
|
389 * @return rcube_message_header Message data
|
|
390 */
|
|
391 function get_message($mailbox, $uid, $update = true, $cache = true)
|
|
392 {
|
|
393 // Check internal cache
|
|
394 if ($this->icache['__message']
|
|
395 && $this->icache['__message']['mailbox'] == $mailbox
|
|
396 && $this->icache['__message']['object']->uid == $uid
|
|
397 ) {
|
|
398 return $this->icache['__message']['object'];
|
|
399 }
|
|
400
|
|
401 if ($this->mode & self::MODE_MESSAGE) {
|
|
402 $sql_result = $this->db->query(
|
|
403 "SELECT `flags`, `data`"
|
|
404 ." FROM {$this->messages_table}"
|
|
405 ." WHERE `user_id` = ?"
|
|
406 ." AND `mailbox` = ?"
|
|
407 ." AND `uid` = ?",
|
|
408 $this->userid, $mailbox, (int)$uid);
|
|
409
|
|
410 if ($sql_arr = $this->db->fetch_assoc($sql_result)) {
|
|
411 $message = $this->build_message($sql_arr);
|
|
412 $found = true;
|
|
413 }
|
|
414 }
|
|
415
|
|
416 // Get the message from IMAP server
|
|
417 if (empty($message) && $update) {
|
|
418 $message = $this->imap->get_message_headers($uid, $mailbox, true);
|
|
419 // cache will be updated in close(), see below
|
|
420 }
|
|
421
|
|
422 if (!($this->mode & self::MODE_MESSAGE)) {
|
|
423 return $message;
|
|
424 }
|
|
425
|
|
426 // Save the message in internal cache, will be written to DB in close()
|
|
427 // Common scenario: user opens unseen message
|
|
428 // - get message (SELECT)
|
|
429 // - set message headers/structure (INSERT or UPDATE)
|
|
430 // - set \Seen flag (UPDATE)
|
|
431 // This way we can skip one UPDATE
|
|
432 if (!empty($message) && $cache) {
|
|
433 // Save current message from internal cache
|
|
434 $this->save_icache();
|
|
435
|
|
436 $this->icache['__message'] = array(
|
|
437 'object' => $message,
|
|
438 'mailbox' => $mailbox,
|
|
439 'exists' => $found,
|
|
440 'md5sum' => md5(serialize($message)),
|
|
441 );
|
|
442 }
|
|
443
|
|
444 return $message;
|
|
445 }
|
|
446
|
|
447 /**
|
|
448 * Saves the message in cache.
|
|
449 *
|
|
450 * @param string $mailbox Folder name
|
|
451 * @param rcube_message_header $message Message data
|
|
452 * @param bool $force Skips message in-cache existence check
|
|
453 */
|
|
454 function add_message($mailbox, $message, $force = false)
|
|
455 {
|
|
456 if (!is_object($message) || empty($message->uid)) {
|
|
457 return;
|
|
458 }
|
|
459
|
|
460 if (!($this->mode & self::MODE_MESSAGE)) {
|
|
461 return;
|
|
462 }
|
|
463
|
|
464 $flags = 0;
|
|
465 $msg = clone $message;
|
|
466
|
|
467 if (!empty($message->flags)) {
|
|
468 foreach ($this->flags as $idx => $flag) {
|
|
469 if (!empty($message->flags[$flag])) {
|
|
470 $flags += $idx;
|
|
471 }
|
|
472 }
|
|
473 }
|
|
474
|
|
475 unset($msg->flags);
|
|
476 $msg = $this->db->encode($msg, true);
|
|
477
|
|
478 // update cache record (even if it exists, the update
|
|
479 // here will work as select, assume row exist if affected_rows=0)
|
|
480 if (!$force) {
|
|
481 $res = $this->db->query(
|
|
482 "UPDATE {$this->messages_table}"
|
|
483 ." SET `flags` = ?, `data` = ?, `expires` = " . ($this->ttl ? $this->db->now($this->ttl) : 'NULL')
|
|
484 ." WHERE `user_id` = ?"
|
|
485 ." AND `mailbox` = ?"
|
|
486 ." AND `uid` = ?",
|
|
487 $flags, $msg, $this->userid, $mailbox, (int) $message->uid);
|
|
488
|
|
489 if ($this->db->affected_rows($res)) {
|
|
490 return;
|
|
491 }
|
|
492 }
|
|
493
|
|
494 $this->db->set_option('ignore_key_errors', true);
|
|
495
|
|
496 // insert new record
|
|
497 $res = $this->db->query(
|
|
498 "INSERT INTO {$this->messages_table}"
|
|
499 ." (`user_id`, `mailbox`, `uid`, `flags`, `expires`, `data`)"
|
|
500 ." VALUES (?, ?, ?, ?, ". ($this->ttl ? $this->db->now($this->ttl) : 'NULL') . ", ?)",
|
|
501 $this->userid, $mailbox, (int) $message->uid, $flags, $msg);
|
|
502
|
|
503 // race-condition, insert failed so try update (#1489146)
|
|
504 // thanks to ignore_key_errors "duplicate row" errors will be ignored
|
|
505 if ($force && !$res && !$this->db->is_error($res)) {
|
|
506 $this->db->query(
|
|
507 "UPDATE {$this->messages_table}"
|
|
508 ." SET `expires` = " . ($this->ttl ? $this->db->now($this->ttl) : 'NULL')
|
|
509 .", `flags` = ?, `data` = ?"
|
|
510 ." WHERE `user_id` = ?"
|
|
511 ." AND `mailbox` = ?"
|
|
512 ." AND `uid` = ?",
|
|
513 $flags, $msg, $this->userid, $mailbox, (int) $message->uid);
|
|
514 }
|
|
515
|
|
516 $this->db->set_option('ignore_key_errors', false);
|
|
517 }
|
|
518
|
|
519 /**
|
|
520 * Sets the flag for specified message.
|
|
521 *
|
|
522 * @param string $mailbox Folder name
|
|
523 * @param array $uids Message UIDs or null to change flag
|
|
524 * of all messages in a folder
|
|
525 * @param string $flag The name of the flag
|
|
526 * @param bool $enabled Flag state
|
|
527 */
|
|
528 function change_flag($mailbox, $uids, $flag, $enabled = false)
|
|
529 {
|
|
530 if (empty($uids)) {
|
|
531 return;
|
|
532 }
|
|
533
|
|
534 if (!($this->mode & self::MODE_MESSAGE)) {
|
|
535 return;
|
|
536 }
|
|
537
|
|
538 $flag = strtoupper($flag);
|
|
539 $idx = (int) array_search($flag, $this->flags);
|
|
540 $uids = (array) $uids;
|
|
541
|
|
542 if (!$idx) {
|
|
543 return;
|
|
544 }
|
|
545
|
|
546 // Internal cache update
|
|
547 if (($message = $this->icache['__message'])
|
|
548 && $message['mailbox'] === $mailbox
|
|
549 && in_array($message['object']->uid, $uids)
|
|
550 ) {
|
|
551 $message['object']->flags[$flag] = $enabled;
|
|
552
|
|
553 if (count($uids) == 1) {
|
|
554 return;
|
|
555 }
|
|
556 }
|
|
557
|
|
558 $binary_check = $this->db->db_provider == 'oracle' ? "BITAND(`flags`, %d)" : "(`flags` & %d)";
|
|
559
|
|
560 $this->db->query(
|
|
561 "UPDATE {$this->messages_table}"
|
|
562 ." SET `expires` = ". ($this->ttl ? $this->db->now($this->ttl) : 'NULL')
|
|
563 .", `flags` = `flags` ".($enabled ? "+ $idx" : "- $idx")
|
|
564 ." WHERE `user_id` = ?"
|
|
565 ." AND `mailbox` = ?"
|
|
566 .(!empty($uids) ? " AND `uid` IN (".$this->db->array2list($uids, 'integer').")" : "")
|
|
567 ." AND " . sprintf($binary_check, $idx) . ($enabled ? " = 0" : " = $idx"),
|
|
568 $this->userid, $mailbox);
|
|
569 }
|
|
570
|
|
571 /**
|
|
572 * Removes message(s) from cache.
|
|
573 *
|
|
574 * @param string $mailbox Folder name
|
|
575 * @param array $uids Message UIDs, NULL removes all messages
|
|
576 */
|
|
577 function remove_message($mailbox = null, $uids = null)
|
|
578 {
|
|
579 if (!($this->mode & self::MODE_MESSAGE)) {
|
|
580 return;
|
|
581 }
|
|
582
|
|
583 if (!strlen($mailbox)) {
|
|
584 $this->db->query(
|
|
585 "DELETE FROM {$this->messages_table}"
|
|
586 ." WHERE `user_id` = ?",
|
|
587 $this->userid);
|
|
588 }
|
|
589 else {
|
|
590 // Remove the message from internal cache
|
|
591 if (!empty($uids) && ($message = $this->icache['__message'])
|
|
592 && $message['mailbox'] === $mailbox
|
|
593 && in_array($message['object']->uid, (array)$uids)
|
|
594 ) {
|
|
595 $this->icache['__message'] = null;
|
|
596 }
|
|
597
|
|
598 $this->db->query(
|
|
599 "DELETE FROM {$this->messages_table}"
|
|
600 ." WHERE `user_id` = ?"
|
|
601 ." AND `mailbox` = ?"
|
|
602 .($uids !== null ? " AND `uid` IN (".$this->db->array2list((array)$uids, 'integer').")" : ""),
|
|
603 $this->userid, $mailbox);
|
|
604 }
|
|
605 }
|
|
606
|
|
607 /**
|
|
608 * Clears index cache.
|
|
609 *
|
|
610 * @param string $mailbox Folder name
|
|
611 * @param bool $remove Enable to remove the DB row
|
|
612 */
|
|
613 function remove_index($mailbox = null, $remove = false)
|
|
614 {
|
|
615 // The index should be only removed from database when
|
|
616 // UIDVALIDITY was detected or the mailbox is empty
|
|
617 // otherwise use 'valid' flag to not loose HIGHESTMODSEQ value
|
|
618 if ($remove) {
|
|
619 $this->db->query(
|
|
620 "DELETE FROM {$this->index_table}"
|
|
621 ." WHERE `user_id` = ?"
|
|
622 .(strlen($mailbox) ? " AND `mailbox` = ".$this->db->quote($mailbox) : ""),
|
|
623 $this->userid
|
|
624 );
|
|
625 }
|
|
626 else {
|
|
627 $this->db->query(
|
|
628 "UPDATE {$this->index_table}"
|
|
629 ." SET `valid` = 0"
|
|
630 ." WHERE `user_id` = ?"
|
|
631 .(strlen($mailbox) ? " AND `mailbox` = ".$this->db->quote($mailbox) : ""),
|
|
632 $this->userid
|
|
633 );
|
|
634 }
|
|
635
|
|
636 if (strlen($mailbox)) {
|
|
637 unset($this->icache[$mailbox]['index']);
|
|
638 // Index removed, set flag to skip SELECT query in get_index()
|
|
639 $this->icache[$mailbox]['index_queried'] = true;
|
|
640 }
|
|
641 else {
|
|
642 $this->icache = array();
|
|
643 }
|
|
644 }
|
|
645
|
|
646 /**
|
|
647 * Clears thread cache.
|
|
648 *
|
|
649 * @param string $mailbox Folder name
|
|
650 */
|
|
651 function remove_thread($mailbox = null)
|
|
652 {
|
|
653 $this->db->query(
|
|
654 "DELETE FROM {$this->thread_table}"
|
|
655 ." WHERE `user_id` = ?"
|
|
656 .(strlen($mailbox) ? " AND `mailbox` = ".$this->db->quote($mailbox) : ""),
|
|
657 $this->userid
|
|
658 );
|
|
659
|
|
660 if (strlen($mailbox)) {
|
|
661 unset($this->icache[$mailbox]['thread']);
|
|
662 // Thread data removed, set flag to skip SELECT query in get_thread()
|
|
663 $this->icache[$mailbox]['thread_queried'] = true;
|
|
664 }
|
|
665 else {
|
|
666 $this->icache = array();
|
|
667 }
|
|
668 }
|
|
669
|
|
670 /**
|
|
671 * Clears the cache.
|
|
672 *
|
|
673 * @param string $mailbox Folder name
|
|
674 * @param array $uids Message UIDs, NULL removes all messages in a folder
|
|
675 */
|
|
676 function clear($mailbox = null, $uids = null)
|
|
677 {
|
|
678 $this->remove_index($mailbox, true);
|
|
679 $this->remove_thread($mailbox);
|
|
680 $this->remove_message($mailbox, $uids);
|
|
681 }
|
|
682
|
|
683 /**
|
|
684 * Delete expired cache entries
|
|
685 */
|
|
686 static function gc()
|
|
687 {
|
|
688 $rcube = rcube::get_instance();
|
|
689 $db = $rcube->get_dbh();
|
|
690 $now = $db->now();
|
|
691
|
|
692 $db->query("DELETE FROM " . $db->table_name('cache_messages', true)
|
|
693 ." WHERE `expires` < $now");
|
|
694
|
|
695 $db->query("DELETE FROM " . $db->table_name('cache_index', true)
|
|
696 ." WHERE `expires` < $now");
|
|
697
|
|
698 $db->query("DELETE FROM ".$db->table_name('cache_thread', true)
|
|
699 ." WHERE `expires` < $now");
|
|
700 }
|
|
701
|
|
702 /**
|
|
703 * Fetches index data from database
|
|
704 */
|
|
705 private function get_index_row($mailbox)
|
|
706 {
|
|
707 // Get index from DB
|
|
708 $sql_result = $this->db->query(
|
|
709 "SELECT `data`, `valid`"
|
|
710 ." FROM {$this->index_table}"
|
|
711 ." WHERE `user_id` = ?"
|
|
712 ." AND `mailbox` = ?",
|
|
713 $this->userid, $mailbox);
|
|
714
|
|
715 if ($sql_arr = $this->db->fetch_assoc($sql_result)) {
|
|
716 $data = explode('@', $sql_arr['data']);
|
|
717 $index = $this->db->decode($data[0], true);
|
|
718 unset($data[0]);
|
|
719
|
|
720 if (empty($index)) {
|
|
721 $index = new rcube_result_index($mailbox);
|
|
722 }
|
|
723
|
|
724 return array(
|
|
725 'valid' => $sql_arr['valid'],
|
|
726 'object' => $index,
|
|
727 'sort_field' => $data[1],
|
|
728 'deleted' => $data[2],
|
|
729 'validity' => $data[3],
|
|
730 'uidnext' => $data[4],
|
|
731 'modseq' => $data[5],
|
|
732 );
|
|
733 }
|
|
734
|
|
735 return null;
|
|
736 }
|
|
737
|
|
738 /**
|
|
739 * Fetches thread data from database
|
|
740 */
|
|
741 private function get_thread_row($mailbox)
|
|
742 {
|
|
743 // Get thread from DB
|
|
744 $sql_result = $this->db->query(
|
|
745 "SELECT `data`"
|
|
746 ." FROM {$this->thread_table}"
|
|
747 ." WHERE `user_id` = ?"
|
|
748 ." AND `mailbox` = ?",
|
|
749 $this->userid, $mailbox);
|
|
750
|
|
751 if ($sql_arr = $this->db->fetch_assoc($sql_result)) {
|
|
752 $data = explode('@', $sql_arr['data']);
|
|
753 $thread = $this->db->decode($data[0], true);
|
|
754 unset($data[0]);
|
|
755
|
|
756 if (empty($thread)) {
|
|
757 $thread = new rcube_result_thread($mailbox);
|
|
758 }
|
|
759
|
|
760 return array(
|
|
761 'object' => $thread,
|
|
762 'deleted' => $data[1],
|
|
763 'validity' => $data[2],
|
|
764 'uidnext' => $data[3],
|
|
765 );
|
|
766 }
|
|
767
|
|
768 return null;
|
|
769 }
|
|
770
|
|
771 /**
|
|
772 * Saves index data into database
|
|
773 */
|
|
774 private function add_index_row($mailbox, $sort_field,
|
|
775 $data, $mbox_data = array(), $exists = false, $modseq = null)
|
|
776 {
|
|
777 $data = array(
|
|
778 $this->db->encode($data, true),
|
|
779 $sort_field,
|
|
780 (int) $this->skip_deleted,
|
|
781 (int) $mbox_data['UIDVALIDITY'],
|
|
782 (int) $mbox_data['UIDNEXT'],
|
|
783 $modseq ? $modseq : $mbox_data['HIGHESTMODSEQ'],
|
|
784 );
|
|
785
|
|
786 $data = implode('@', $data);
|
|
787 $expires = $this->ttl ? $this->db->now($this->ttl) : 'NULL';
|
|
788
|
|
789 if ($exists) {
|
|
790 $res = $this->db->query(
|
|
791 "UPDATE {$this->index_table}"
|
|
792 ." SET `data` = ?, `valid` = 1, `expires` = $expires"
|
|
793 ." WHERE `user_id` = ?"
|
|
794 ." AND `mailbox` = ?",
|
|
795 $data, $this->userid, $mailbox);
|
|
796
|
|
797 if ($this->db->affected_rows($res)) {
|
|
798 return;
|
|
799 }
|
|
800 }
|
|
801
|
|
802 $this->db->set_option('ignore_key_errors', true);
|
|
803
|
|
804 $res = $this->db->query(
|
|
805 "INSERT INTO {$this->index_table}"
|
|
806 ." (`user_id`, `mailbox`, `valid`, `expires`, `data`)"
|
|
807 ." VALUES (?, ?, 1, $expires, ?)",
|
|
808 $this->userid, $mailbox, $data);
|
|
809
|
|
810 // race-condition, insert failed so try update (#1489146)
|
|
811 // thanks to ignore_key_errors "duplicate row" errors will be ignored
|
|
812 if (!$exists && !$res && !$this->db->is_error($res)) {
|
|
813 $res = $this->db->query(
|
|
814 "UPDATE {$this->index_table}"
|
|
815 ." SET `data` = ?, `valid` = 1, `expires` = $expires"
|
|
816 ." WHERE `user_id` = ?"
|
|
817 ." AND `mailbox` = ?",
|
|
818 $data, $this->userid, $mailbox);
|
|
819 }
|
|
820
|
|
821 $this->db->set_option('ignore_key_errors', false);
|
|
822 }
|
|
823
|
|
824 /**
|
|
825 * Saves thread data into database
|
|
826 */
|
|
827 private function add_thread_row($mailbox, $data, $mbox_data = array(), $exists = false)
|
|
828 {
|
|
829 $data = array(
|
|
830 $this->db->encode($data, true),
|
|
831 (int) $this->skip_deleted,
|
|
832 (int) $mbox_data['UIDVALIDITY'],
|
|
833 (int) $mbox_data['UIDNEXT'],
|
|
834 );
|
|
835
|
|
836 $data = implode('@', $data);
|
|
837 $expires = $this->ttl ? $this->db->now($this->ttl) : 'NULL';
|
|
838
|
|
839 if ($exists) {
|
|
840 $res = $this->db->query(
|
|
841 "UPDATE {$this->thread_table}"
|
|
842 ." SET `data` = ?, `expires` = $expires"
|
|
843 ." WHERE `user_id` = ?"
|
|
844 ." AND `mailbox` = ?",
|
|
845 $data, $this->userid, $mailbox);
|
|
846
|
|
847 if ($this->db->affected_rows($res)) {
|
|
848 return;
|
|
849 }
|
|
850 }
|
|
851
|
|
852 $this->db->set_option('ignore_key_errors', true);
|
|
853
|
|
854 $res = $this->db->query(
|
|
855 "INSERT INTO {$this->thread_table}"
|
|
856 ." (`user_id`, `mailbox`, `expires`, `data`)"
|
|
857 ." VALUES (?, ?, $expires, ?)",
|
|
858 $this->userid, $mailbox, $data);
|
|
859
|
|
860 // race-condition, insert failed so try update (#1489146)
|
|
861 // thanks to ignore_key_errors "duplicate row" errors will be ignored
|
|
862 if (!$exists && !$res && !$this->db->is_error($res)) {
|
|
863 $this->db->query(
|
|
864 "UPDATE {$this->thread_table}"
|
|
865 ." SET `expires` = $expires, `data` = ?"
|
|
866 ." WHERE `user_id` = ?"
|
|
867 ." AND `mailbox` = ?",
|
|
868 $data, $this->userid, $mailbox);
|
|
869 }
|
|
870
|
|
871 $this->db->set_option('ignore_key_errors', false);
|
|
872 }
|
|
873
|
|
874 /**
|
|
875 * Checks index/thread validity
|
|
876 */
|
|
877 private function validate($mailbox, $index, &$exists = true)
|
|
878 {
|
|
879 $object = $index['object'];
|
|
880 $is_thread = is_a($object, 'rcube_result_thread');
|
|
881
|
|
882 // sanity check
|
|
883 if (empty($object)) {
|
|
884 return false;
|
|
885 }
|
|
886
|
|
887 $index['validated'] = true;
|
|
888
|
|
889 // Get mailbox data (UIDVALIDITY, counters, etc.) for status check
|
|
890 $mbox_data = $this->imap->folder_data($mailbox);
|
|
891
|
|
892 // @TODO: Think about skipping validation checks.
|
|
893 // If we could check only every 10 minutes, we would be able to skip
|
|
894 // expensive checks, mailbox selection or even IMAP connection, this would require
|
|
895 // additional logic to force cache invalidation in some cases
|
|
896 // and many rcube_imap changes to connect when needed
|
|
897
|
|
898 // Check UIDVALIDITY
|
|
899 if ($index['validity'] != $mbox_data['UIDVALIDITY']) {
|
|
900 $this->clear($mailbox);
|
|
901 $exists = false;
|
|
902 return false;
|
|
903 }
|
|
904
|
|
905 // Folder is empty but cache isn't
|
|
906 if (empty($mbox_data['EXISTS'])) {
|
|
907 if (!$object->is_empty()) {
|
|
908 $this->clear($mailbox);
|
|
909 $exists = false;
|
|
910 return false;
|
|
911 }
|
|
912 }
|
|
913 // Folder is not empty but cache is
|
|
914 else if ($object->is_empty()) {
|
|
915 unset($this->icache[$mailbox][$is_thread ? 'thread' : 'index']);
|
|
916 return false;
|
|
917 }
|
|
918
|
|
919 // Validation flag
|
|
920 if (!$is_thread && empty($index['valid'])) {
|
|
921 unset($this->icache[$mailbox]['index']);
|
|
922 return false;
|
|
923 }
|
|
924
|
|
925 // Index was created with different skip_deleted setting
|
|
926 if ($this->skip_deleted != $index['deleted']) {
|
|
927 return false;
|
|
928 }
|
|
929
|
|
930 // Check HIGHESTMODSEQ
|
|
931 if (!empty($index['modseq']) && !empty($mbox_data['HIGHESTMODSEQ'])
|
|
932 && $index['modseq'] == $mbox_data['HIGHESTMODSEQ']
|
|
933 ) {
|
|
934 return true;
|
|
935 }
|
|
936
|
|
937 // Check UIDNEXT
|
|
938 if ($index['uidnext'] != $mbox_data['UIDNEXT']) {
|
|
939 unset($this->icache[$mailbox][$is_thread ? 'thread' : 'index']);
|
|
940 return false;
|
|
941 }
|
|
942
|
|
943 // @TODO: find better validity check for threaded index
|
|
944 if ($is_thread) {
|
|
945 // check messages number...
|
|
946 if (!$this->skip_deleted && $mbox_data['EXISTS'] != $object->count_messages()) {
|
|
947 return false;
|
|
948 }
|
|
949 return true;
|
|
950 }
|
|
951
|
|
952 // The rest of checks, more expensive
|
|
953 if (!empty($this->skip_deleted)) {
|
|
954 // compare counts if available
|
|
955 if (!empty($mbox_data['UNDELETED'])
|
|
956 && $mbox_data['UNDELETED']->count() != $object->count()
|
|
957 ) {
|
|
958 return false;
|
|
959 }
|
|
960 // compare UID sets
|
|
961 if (!empty($mbox_data['UNDELETED'])) {
|
|
962 $uids_new = $mbox_data['UNDELETED']->get();
|
|
963 $uids_old = $object->get();
|
|
964
|
|
965 if (count($uids_new) != count($uids_old)) {
|
|
966 return false;
|
|
967 }
|
|
968
|
|
969 sort($uids_new, SORT_NUMERIC);
|
|
970 sort($uids_old, SORT_NUMERIC);
|
|
971
|
|
972 if ($uids_old != $uids_new)
|
|
973 return false;
|
|
974 }
|
|
975 else {
|
|
976 // get all undeleted messages excluding cached UIDs
|
|
977 $ids = $this->imap->search_once($mailbox, 'ALL UNDELETED NOT UID '.
|
|
978 rcube_imap_generic::compressMessageSet($object->get()));
|
|
979
|
|
980 if (!$ids->is_empty()) {
|
|
981 return false;
|
|
982 }
|
|
983 }
|
|
984 }
|
|
985 else {
|
|
986 // check messages number...
|
|
987 if ($mbox_data['EXISTS'] != $object->count()) {
|
|
988 return false;
|
|
989 }
|
|
990 // ... and max UID
|
|
991 if ($object->max() != $this->imap->id2uid($mbox_data['EXISTS'], $mailbox)) {
|
|
992 return false;
|
|
993 }
|
|
994 }
|
|
995
|
|
996 return true;
|
|
997 }
|
|
998
|
|
999 /**
|
|
1000 * Synchronizes the mailbox.
|
|
1001 *
|
|
1002 * @param string $mailbox Folder name
|
|
1003 */
|
|
1004 function synchronize($mailbox)
|
|
1005 {
|
|
1006 // RFC4549: Synchronization Operations for Disconnected IMAP4 Clients
|
|
1007 // RFC4551: IMAP Extension for Conditional STORE Operation
|
|
1008 // or Quick Flag Changes Resynchronization
|
|
1009 // RFC5162: IMAP Extensions for Quick Mailbox Resynchronization
|
|
1010
|
|
1011 // @TODO: synchronize with other methods?
|
|
1012 $qresync = $this->imap->get_capability('QRESYNC');
|
|
1013 $condstore = $qresync ? true : $this->imap->get_capability('CONDSTORE');
|
|
1014
|
|
1015 if (!$qresync && !$condstore) {
|
|
1016 return;
|
|
1017 }
|
|
1018
|
|
1019 // Get stored index
|
|
1020 $index = $this->get_index_row($mailbox);
|
|
1021
|
|
1022 // database is empty
|
|
1023 if (empty($index)) {
|
|
1024 // set the flag that DB was already queried for index
|
|
1025 // this way we'll be able to skip one SELECT in get_index()
|
|
1026 $this->icache[$mailbox]['index_queried'] = true;
|
|
1027 return;
|
|
1028 }
|
|
1029
|
|
1030 $this->icache[$mailbox]['index'] = $index;
|
|
1031
|
|
1032 // no last HIGHESTMODSEQ value
|
|
1033 if (empty($index['modseq'])) {
|
|
1034 return;
|
|
1035 }
|
|
1036
|
|
1037 if (!$this->imap->check_connection()) {
|
|
1038 return;
|
|
1039 }
|
|
1040
|
|
1041 // Enable QRESYNC
|
|
1042 $res = $this->imap->conn->enable($qresync ? 'QRESYNC' : 'CONDSTORE');
|
|
1043 if ($res === false) {
|
|
1044 return;
|
|
1045 }
|
|
1046
|
|
1047 // Close mailbox if already selected to get most recent data
|
|
1048 if ($this->imap->conn->selected == $mailbox) {
|
|
1049 $this->imap->conn->close();
|
|
1050 }
|
|
1051
|
|
1052 // Get mailbox data (UIDVALIDITY, HIGHESTMODSEQ, counters, etc.)
|
|
1053 $mbox_data = $this->imap->folder_data($mailbox);
|
|
1054
|
|
1055 if (empty($mbox_data)) {
|
|
1056 return;
|
|
1057 }
|
|
1058
|
|
1059 // Check UIDVALIDITY
|
|
1060 if ($index['validity'] != $mbox_data['UIDVALIDITY']) {
|
|
1061 $this->clear($mailbox);
|
|
1062 return;
|
|
1063 }
|
|
1064
|
|
1065 // QRESYNC not supported on specified mailbox
|
|
1066 if (!empty($mbox_data['NOMODSEQ']) || empty($mbox_data['HIGHESTMODSEQ'])) {
|
|
1067 return;
|
|
1068 }
|
|
1069
|
|
1070 // Nothing new
|
|
1071 if ($mbox_data['HIGHESTMODSEQ'] == $index['modseq']) {
|
|
1072 return;
|
|
1073 }
|
|
1074
|
|
1075 $uids = array();
|
|
1076 $removed = array();
|
|
1077
|
|
1078 // Get known UIDs
|
|
1079 if ($this->mode & self::MODE_MESSAGE) {
|
|
1080 $sql_result = $this->db->query(
|
|
1081 "SELECT `uid`"
|
|
1082 ." FROM {$this->messages_table}"
|
|
1083 ." WHERE `user_id` = ?"
|
|
1084 ." AND `mailbox` = ?",
|
|
1085 $this->userid, $mailbox);
|
|
1086
|
|
1087 while ($sql_arr = $this->db->fetch_assoc($sql_result)) {
|
|
1088 $uids[] = $sql_arr['uid'];
|
|
1089 }
|
|
1090 }
|
|
1091
|
|
1092 // Synchronize messages data
|
|
1093 if (!empty($uids)) {
|
|
1094 // Get modified flags and vanished messages
|
|
1095 // UID FETCH 1:* (FLAGS) (CHANGEDSINCE 0123456789 VANISHED)
|
|
1096 $result = $this->imap->conn->fetch($mailbox,
|
|
1097 $uids, true, array('FLAGS'), $index['modseq'], $qresync);
|
|
1098
|
|
1099 if (!empty($result)) {
|
|
1100 foreach ($result as $msg) {
|
|
1101 $uid = $msg->uid;
|
|
1102 // Remove deleted message
|
|
1103 if ($this->skip_deleted && !empty($msg->flags['DELETED'])) {
|
|
1104 $removed[] = $uid;
|
|
1105 // Invalidate index
|
|
1106 $index['valid'] = false;
|
|
1107 continue;
|
|
1108 }
|
|
1109
|
|
1110 $flags = 0;
|
|
1111 if (!empty($msg->flags)) {
|
|
1112 foreach ($this->flags as $idx => $flag) {
|
|
1113 if (!empty($msg->flags[$flag])) {
|
|
1114 $flags += $idx;
|
|
1115 }
|
|
1116 }
|
|
1117 }
|
|
1118
|
|
1119 $this->db->query(
|
|
1120 "UPDATE {$this->messages_table}"
|
|
1121 ." SET `flags` = ?, `expires` = " . ($this->ttl ? $this->db->now($this->ttl) : 'NULL')
|
|
1122 ." WHERE `user_id` = ?"
|
|
1123 ." AND `mailbox` = ?"
|
|
1124 ." AND `uid` = ?"
|
|
1125 ." AND `flags` <> ?",
|
|
1126 $flags, $this->userid, $mailbox, $uid, $flags);
|
|
1127 }
|
|
1128 }
|
|
1129
|
|
1130 // VANISHED found?
|
|
1131 if ($qresync) {
|
|
1132 $mbox_data = $this->imap->folder_data($mailbox);
|
|
1133
|
|
1134 // Removed messages found
|
|
1135 $uids = rcube_imap_generic::uncompressMessageSet($mbox_data['VANISHED']);
|
|
1136 if (!empty($uids)) {
|
|
1137 $removed = array_merge($removed, $uids);
|
|
1138 // Invalidate index
|
|
1139 $index['valid'] = false;
|
|
1140 }
|
|
1141 }
|
|
1142
|
|
1143 // remove messages from database
|
|
1144 if (!empty($removed)) {
|
|
1145 $this->remove_message($mailbox, $removed);
|
|
1146 }
|
|
1147 }
|
|
1148
|
|
1149 $sort_field = $index['sort_field'];
|
|
1150 $sort_order = $index['object']->get_parameters('ORDER');
|
|
1151 $exists = true;
|
|
1152
|
|
1153 // Validate index
|
|
1154 if (!$this->validate($mailbox, $index, $exists)) {
|
|
1155 // Invalidate (remove) thread index
|
|
1156 // if $exists=false it was already removed in validate()
|
|
1157 if ($exists) {
|
|
1158 $this->remove_thread($mailbox);
|
|
1159 }
|
|
1160
|
|
1161 // Update index
|
|
1162 $data = $this->get_index_data($mailbox, $sort_field, $sort_order, $mbox_data);
|
|
1163 }
|
|
1164 else {
|
|
1165 $data = $index['object'];
|
|
1166 }
|
|
1167
|
|
1168 // update index and/or HIGHESTMODSEQ value
|
|
1169 $this->add_index_row($mailbox, $sort_field, $data, $mbox_data, $exists);
|
|
1170
|
|
1171 // update internal cache for get_index()
|
|
1172 $this->icache[$mailbox]['index']['object'] = $data;
|
|
1173 }
|
|
1174
|
|
1175 /**
|
|
1176 * Converts cache row into message object.
|
|
1177 *
|
|
1178 * @param array $sql_arr Message row data
|
|
1179 *
|
|
1180 * @return rcube_message_header Message object
|
|
1181 */
|
|
1182 private function build_message($sql_arr)
|
|
1183 {
|
|
1184 $message = $this->db->decode($sql_arr['data'], true);
|
|
1185
|
|
1186 if ($message) {
|
|
1187 $message->flags = array();
|
|
1188 foreach ($this->flags as $idx => $flag) {
|
|
1189 if (($sql_arr['flags'] & $idx) == $idx) {
|
|
1190 $message->flags[$flag] = true;
|
|
1191 }
|
|
1192 }
|
|
1193 }
|
|
1194
|
|
1195 return $message;
|
|
1196 }
|
|
1197
|
|
1198 /**
|
|
1199 * Saves message stored in internal cache
|
|
1200 */
|
|
1201 private function save_icache()
|
|
1202 {
|
|
1203 // Save current message from internal cache
|
|
1204 if ($message = $this->icache['__message']) {
|
|
1205 // clean up some object's data
|
|
1206 $this->message_object_prepare($message['object']);
|
|
1207
|
|
1208 // calculate current md5 sum
|
|
1209 $md5sum = md5(serialize($message['object']));
|
|
1210
|
|
1211 if ($message['md5sum'] != $md5sum) {
|
|
1212 $this->add_message($message['mailbox'], $message['object'], !$message['exists']);
|
|
1213 }
|
|
1214
|
|
1215 $this->icache['__message']['md5sum'] = $md5sum;
|
|
1216 }
|
|
1217 }
|
|
1218
|
|
1219 /**
|
|
1220 * Prepares message object to be stored in database.
|
|
1221 *
|
|
1222 * @param rcube_message_header|rcube_message_part
|
|
1223 */
|
|
1224 private function message_object_prepare(&$msg, &$size = 0)
|
|
1225 {
|
|
1226 // Remove body too big
|
|
1227 if (isset($msg->body)) {
|
|
1228 $length = strlen($msg->body);
|
|
1229
|
|
1230 if ($msg->body_modified || $size + $length > $this->threshold * 1024) {
|
|
1231 unset($msg->body);
|
|
1232 }
|
|
1233 else {
|
|
1234 $size += $length;
|
|
1235 }
|
|
1236 }
|
|
1237
|
|
1238 // Fix mimetype which might be broken by some code when message is displayed
|
|
1239 // Another solution would be to use object's copy in rcube_message class
|
|
1240 // to prevent related issues, however I'm not sure which is better
|
|
1241 if ($msg->mimetype) {
|
|
1242 list($msg->ctype_primary, $msg->ctype_secondary) = explode('/', $msg->mimetype);
|
|
1243 }
|
|
1244
|
|
1245 unset($msg->replaces);
|
|
1246
|
|
1247 if (is_object($msg->structure)) {
|
|
1248 $this->message_object_prepare($msg->structure, $size);
|
|
1249 }
|
|
1250
|
|
1251 if (is_array($msg->parts)) {
|
|
1252 foreach ($msg->parts as $part) {
|
|
1253 $this->message_object_prepare($part, $size);
|
|
1254 }
|
|
1255 }
|
|
1256 }
|
|
1257
|
|
1258 /**
|
|
1259 * Fetches index data from IMAP server
|
|
1260 */
|
|
1261 private function get_index_data($mailbox, $sort_field, $sort_order, $mbox_data = array())
|
|
1262 {
|
|
1263 if (empty($mbox_data)) {
|
|
1264 $mbox_data = $this->imap->folder_data($mailbox);
|
|
1265 }
|
|
1266
|
|
1267 if ($mbox_data['EXISTS']) {
|
|
1268 // fetch sorted sequence numbers
|
|
1269 $index = $this->imap->index_direct($mailbox, $sort_field, $sort_order);
|
|
1270 }
|
|
1271 else {
|
|
1272 $index = new rcube_result_index($mailbox, '* SORT');
|
|
1273 }
|
|
1274
|
|
1275 return $index;
|
|
1276 }
|
|
1277
|
|
1278 /**
|
|
1279 * Fetches thread data from IMAP server
|
|
1280 */
|
|
1281 private function get_thread_data($mailbox, $mbox_data = array())
|
|
1282 {
|
|
1283 if (empty($mbox_data)) {
|
|
1284 $mbox_data = $this->imap->folder_data($mailbox);
|
|
1285 }
|
|
1286
|
|
1287 if ($mbox_data['EXISTS']) {
|
|
1288 // get all threads (default sort order)
|
|
1289 return $this->imap->threads_direct($mailbox);
|
|
1290 }
|
|
1291
|
|
1292 return new rcube_result_thread($mailbox, '* THREAD');
|
|
1293 }
|
|
1294 }
|
|
1295
|
|
1296 // for backward compat.
|
|
1297 class rcube_mail_header extends rcube_message_header { }
|