| | |
| | | |
| | | /* |
| | | +-----------------------------------------------------------------------+ |
| | | | program/include/rcube_imap.php | |
| | | | | |
| | | | This file is part of the Roundcube Webmail client | |
| | | | Copyright (C) 2005-2012, The Roundcube Dev Team | |
| | | | Copyright (C) 2011-2012, Kolab Systems AG | |
| | |
| | | | | |
| | | | PURPOSE: | |
| | | | IMAP Storage Engine | |
| | | | | |
| | | +-----------------------------------------------------------------------+ |
| | | | Author: Thomas Bruederli <roundcube@gmail.com> | |
| | | | Author: Aleksander Machniak <alec@alec.pl> | |
| | | +-----------------------------------------------------------------------+ |
| | | */ |
| | | |
| | | |
| | | /** |
| | | * Interface class for accessing an IMAP server |
| | |
| | | |
| | | $attempt = 0; |
| | | do { |
| | | $data = rcube::get_instance()->plugins->exec_hook('imap_connect', |
| | | $data = rcube::get_instance()->plugins->exec_hook('storage_connect', |
| | | array_merge($this->options, array('host' => $host, 'user' => $user, |
| | | 'attempt' => ++$attempt))); |
| | | |
| | |
| | | * Get message count for a specific folder |
| | | * |
| | | * @param string $folder Folder name |
| | | * @param string $mode Mode for count [ALL|THREADS|UNSEEN|RECENT] |
| | | * @param string $mode Mode for count [ALL|THREADS|UNSEEN|RECENT|EXISTS] |
| | | * @param boolean $force Force reading from server and update cache |
| | | * @param boolean $status Enables storing folder status info (max UID/count), |
| | | * required for folder_status() |
| | |
| | | * protected method for getting nr of messages |
| | | * |
| | | * @param string $folder Folder name |
| | | * @param string $mode Mode for count [ALL|THREADS|UNSEEN|RECENT] |
| | | * @param string $mode Mode for count [ALL|THREADS|UNSEEN|RECENT|EXISTS] |
| | | * @param boolean $force Force reading from server and update cache |
| | | * @param boolean $status Enables storing folder status info (max UID/count), |
| | | * required for folder_status() |
| | |
| | | return $this->search_set->count(); |
| | | } |
| | | } |
| | | |
| | | // EXISTS is a special alias for ALL, it allows to get the number |
| | | // of all messages in a folder also when search is active and with |
| | | // any skip_deleted setting |
| | | |
| | | $a_folder_cache = $this->get_cache('messagecount'); |
| | | |
| | |
| | | $count = $this->conn->countRecent($folder); |
| | | } |
| | | // use SEARCH for message counting |
| | | else if (!empty($this->options['skip_deleted'])) { |
| | | else if ($mode != 'EXISTS' && !empty($this->options['skip_deleted'])) { |
| | | $search_str = "ALL UNDELETED"; |
| | | $keys = array('COUNT'); |
| | | |
| | |
| | | } |
| | | else { |
| | | $count = $this->conn->countMessages($folder); |
| | | if ($status) { |
| | | $this->set_folder_stats($folder,'cnt', $count); |
| | | if ($status && $mode == 'ALL') { |
| | | $this->set_folder_stats($folder, 'cnt', $count); |
| | | $this->set_folder_stats($folder, 'maxuid', $count ? $this->id2uid($count, $folder) : 0); |
| | | } |
| | | } |
| | |
| | | return $mcache->get_thread($folder); |
| | | } |
| | | |
| | | if (empty($this->icache['threads'])) { |
| | | if (!$this->check_connection()) { |
| | | return new rcube_result_thread(); |
| | | if (!empty($this->icache['threads'])) { |
| | | if ($this->icache['threads']->get_parameters('MAILBOX') == $folder) { |
| | | return $this->icache['threads']; |
| | | } |
| | | |
| | | // get all threads |
| | | $result = $this->conn->thread($folder, $this->threading, |
| | | $this->options['skip_deleted'] ? 'UNDELETED' : '', true); |
| | | |
| | | // add to internal (fast) cache |
| | | $this->icache['threads'] = $result; |
| | | } |
| | | |
| | | return $this->icache['threads']; |
| | | if (!$this->check_connection()) { |
| | | return new rcube_result_thread(); |
| | | } |
| | | |
| | | // get all threads |
| | | $result = $this->conn->thread($folder, $this->threading, |
| | | $this->options['skip_deleted'] ? 'UNDELETED' : '', true); |
| | | |
| | | // add to internal (fast) cache |
| | | return $this->icache['threads'] = $result; |
| | | } |
| | | |
| | | |
| | |
| | | // use memory less expensive (and quick) method for big result set |
| | | $index = clone $this->index('', $this->sort_field, $this->sort_order); |
| | | // get messages uids for one page... |
| | | $index->slice($start_msg, min($cnt-$from, $this->page_size)); |
| | | $index->slice($from, min($cnt-$from, $this->page_size)); |
| | | |
| | | if ($slice) { |
| | | $index->slice(-$slice, $slice); |
| | |
| | | |
| | | |
| | | /** |
| | | * Returns current status of folder |
| | | * Returns current status of a folder (compared to the last time use) |
| | | * |
| | | * We compare the maximum UID to determine the number of |
| | | * new messages because the RECENT flag is not reliable. |
| | | * |
| | | * @param string $folder Folder name |
| | | * @param array $diff Difference data |
| | | * |
| | | * @return int Folder status |
| | | * @return int Folder status |
| | | */ |
| | | public function folder_status($folder = null) |
| | | public function folder_status($folder = null, &$diff = array()) |
| | | { |
| | | if (!strlen($folder)) { |
| | | $folder = $this->folder; |
| | |
| | | // got new messages |
| | | if ($new['maxuid'] > $old['maxuid']) { |
| | | $result += 1; |
| | | // get new message UIDs range, that can be used for example |
| | | // to get the data of these messages |
| | | $diff['new'] = ($old['maxuid'] + 1 < $new['maxuid'] ? ($old['maxuid']+1).':' : '') . $new['maxuid']; |
| | | } |
| | | // some messages has been deleted |
| | | if ($new['cnt'] < $old['cnt']) { |
| | |
| | | */ |
| | | protected function search_index($folder, $criteria='ALL', $charset=NULL, $sort_field=NULL) |
| | | { |
| | | $orig_criteria = $criteria; |
| | | |
| | | if (!$this->check_connection()) { |
| | | if ($this->threading) { |
| | | return new rcube_result_thread(); |
| | |
| | | // Example of structure for malformed MIME message: |
| | | // ("text" "plain" NIL NIL NIL "7bit" 2154 70 NIL NIL NIL) |
| | | if ($headers->ctype && !is_array($structure[0]) && $headers->ctype != 'text/plain' |
| | | && strtolower($structure[0].'/'.$structure[1]) == 'text/plain') { |
| | | && strtolower($structure[0].'/'.$structure[1]) == 'text/plain' |
| | | ) { |
| | | // A special known case "Content-type: text" (#1488968) |
| | | if ($headers->ctype == 'text') { |
| | | $structure[1] = 'plain'; |
| | | $headers->ctype = 'text/plain'; |
| | | } |
| | | // we can handle single-part messages, by simple fix in structure (#1486898) |
| | | if (preg_match('/^(text|application)\/(.*)/', $headers->ctype, $m)) { |
| | | else if (preg_match('/^(text|application)\/(.*)/', $headers->ctype, $m)) { |
| | | $structure[0] = $m[1]; |
| | | $structure[1] = $m[2]; |
| | | } |
| | |
| | | $struct = $this->structure_part($structure, 0, '', $headers); |
| | | } |
| | | |
| | | // don't trust given content-type |
| | | if (empty($struct->parts) && !empty($headers->ctype)) { |
| | | $struct->mime_id = '1'; |
| | | $struct->mimetype = strtolower($headers->ctype); |
| | | list($struct->ctype_primary, $struct->ctype_secondary) = explode('/', $struct->mimetype); |
| | | // some workarounds on simple messages... |
| | | if (empty($struct->parts)) { |
| | | // ...don't trust given content-type |
| | | if (!empty($headers->ctype)) { |
| | | $struct->mime_id = '1'; |
| | | $struct->mimetype = strtolower($headers->ctype); |
| | | list($struct->ctype_primary, $struct->ctype_secondary) = explode('/', $struct->mimetype); |
| | | } |
| | | |
| | | // ...and charset (there's a case described in #1488968 where invalid content-type |
| | | // results in invalid charset in BODYSTRUCTURE) |
| | | if (!empty($headers->charset) && $headers->charset != $struct->ctype_parameters['charset']) { |
| | | $struct->charset = $headers->charset; |
| | | $struct->ctype_parameters['charset'] = $headers->charset; |
| | | } |
| | | } |
| | | |
| | | $headers->structure = $struct; |
| | |
| | | // move messages |
| | | $moved = $this->conn->move($uids, $from_mbox, $to_mbox); |
| | | |
| | | // send expunge command in order to have the moved message |
| | | // really deleted from the source folder |
| | | if ($moved) { |
| | | $this->expunge_message($uids, $from_mbox, false); |
| | | $this->clear_messagecount($from_mbox); |
| | | $this->clear_messagecount($to_mbox); |
| | | } |
| | |
| | | |
| | | // filter folders list according to rights requirements |
| | | if ($rights && $this->get_capability('ACL')) { |
| | | $a_folders = $this->filter_rights($a_folders, $rights); |
| | | $a_mboxes = $this->filter_rights($a_mboxes, $rights); |
| | | } |
| | | |
| | | // filter folders and sort them |
| | |
| | | */ |
| | | private function list_folders_update(&$result, $type = null) |
| | | { |
| | | $delim = $this->get_hierarchy_delimiter(); |
| | | $namespace = $this->get_namespace(); |
| | | $search = array(); |
| | | |
| | |
| | | { |
| | | if (!empty($this->options['fetch_headers'])) { |
| | | $headers = explode(' ', $this->options['fetch_headers']); |
| | | $headers = array_map('strtoupper', $headers); |
| | | } |
| | | else { |
| | | $headers = array(); |
| | |
| | | $headers = array_merge($headers, $this->all_headers); |
| | | } |
| | | |
| | | return implode(' ', array_unique($headers)); |
| | | return $headers; |
| | | } |
| | | |
| | | |
| | |
| | | { |
| | | if ($this->caching && !$this->cache) { |
| | | $rcube = rcube::get_instance(); |
| | | $ttl = $rcube->config->get('message_cache_lifetime', '10d'); |
| | | $ttl = $rcube->config->get('imap_cache_ttl', '10d'); |
| | | $this->cache = $rcube->get_cache('IMAP', $this->caching, $ttl); |
| | | } |
| | | |
| | |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Delete outdated cache entries |
| | | */ |
| | | public function expunge_cache() |
| | | { |
| | | if ($this->mcache) { |
| | | $ttl = rcube::get_instance()->config->get('message_cache_lifetime', '10d'); |
| | | $this->mcache->expunge($ttl); |
| | | } |
| | | |
| | | if ($this->cache) { |
| | | $this->cache->expunge(); |
| | | } |
| | | } |
| | | |
| | | |
| | | /* -------------------------------- |
| | | * message caching methods |
| | |
| | | if ($this->messages_caching && !$this->mcache) { |
| | | $rcube = rcube::get_instance(); |
| | | if (($dbh = $rcube->get_dbh()) && ($userid = $rcube->get_user_id())) { |
| | | $ttl = $rcube->config->get('messages_cache_ttl', '10d'); |
| | | $this->mcache = new rcube_imap_cache( |
| | | $dbh, $this, $userid, $this->options['skip_deleted']); |
| | | $dbh, $this, $userid, $this->options['skip_deleted'], $ttl); |
| | | } |
| | | } |
| | | |
| | |
| | | if ($mcache = $this->get_mcache_engine()) { |
| | | $mcache->clear($folder, $uids); |
| | | } |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Delete outdated cache entries |
| | | */ |
| | | function cache_gc() |
| | | { |
| | | rcube_imap_cache::gc(); |
| | | } |
| | | |
| | | |
| | |
| | | $delimiter = $this->get_hierarchy_delimiter(); |
| | | |
| | | // find default folders and skip folders starting with '.' |
| | | foreach ($a_folders as $i => $folder) { |
| | | foreach ($a_folders as $folder) { |
| | | if ($folder[0] == '.') { |
| | | continue; |
| | | } |