Mercurial > hg > rc2
comparison program/lib/Roundcube/rcube_imap_cache.php @ 0:4681f974d28b
vanilla 1.3.3 distro, I hope
| author | Charlie Root |
|---|---|
| date | Thu, 04 Jan 2018 15:52:31 -0500 |
| parents | |
| children |
comparison
equal
deleted
inserted
replaced
| -1:000000000000 | 0:4681f974d28b |
|---|---|
| 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 { } |
