From 11bcac5802dbdd01ee37b97e84f9a91c5777d9e6 Mon Sep 17 00:00:00 2001
From: alecpl <alec@alec.pl>
Date: Wed, 29 Sep 2010 09:32:41 -0400
Subject: [PATCH] - Fix HTML to plain text conversion doesn't handle citation blocks (#1486921)
---
program/include/rcube_imap.php | 1055 ++++++++++++++++++++++++++++++++++------------------------
1 files changed, 614 insertions(+), 441 deletions(-)
diff --git a/program/include/rcube_imap.php b/program/include/rcube_imap.php
index 9facb32..62996e2 100644
--- a/program/include/rcube_imap.php
+++ b/program/include/rcube_imap.php
@@ -4,8 +4,8 @@
+-----------------------------------------------------------------------+
| program/include/rcube_imap.php |
| |
- | This file is part of the RoundCube Webmail client |
- | Copyright (C) 2005-2010, RoundCube Dev. - Switzerland |
+ | This file is part of the Roundcube Webmail client |
+ | Copyright (C) 2005-2010, Roundcube Dev. - Switzerland |
| Licensed under the GNU GPL |
| |
| PURPOSE: |
@@ -26,7 +26,7 @@
*
* @package Mail
* @author Thomas Bruederli <roundcube@gmail.com>
- * @author Aleksander Machniak <alec@alec.pl>
+ * @author Aleksander Machniak <alec@alec.pl>
* @version 2.0
*/
class rcube_imap
@@ -53,7 +53,7 @@
private $default_folders = array('INBOX');
private $icache = array();
private $cache = array();
- private $cache_keys = array();
+ private $cache_keys = array();
private $cache_changes = array();
private $uid_id_map = array();
private $msg_headers = array();
@@ -92,7 +92,7 @@
*/
function connect($host, $user, $pass, $port=143, $use_ssl=null)
{
- // check for Open-SSL support in PHP build
+ // check for OpenSSL support in PHP build
if ($use_ssl && extension_loaded('openssl'))
$this->options['ssl_mode'] = $use_ssl == 'imaps' ? 'ssl' : $use_ssl;
else if ($use_ssl) {
@@ -108,7 +108,7 @@
do {
$data = rcmail::get_instance()->plugins->exec_hook('imap_connect',
array('host' => $host, 'user' => $user, 'attempt' => ++$attempt));
-
+
if (!empty($data['pass']))
$pass = $data['pass'];
@@ -138,9 +138,10 @@
// write error log
else if ($this->conn->error) {
$this->error_code = $this->conn->errornum;
- raise_error(array('code' => 403, 'type' => 'imap',
- 'file' => __FILE__, 'line' => __LINE__,
- 'message' => $this->conn->error), true, false);
+ if ($pass && $user)
+ raise_error(array('code' => 403, 'type' => 'imap',
+ 'file' => __FILE__, 'line' => __LINE__,
+ 'message' => $this->conn->error), true, false);
}
return false;
@@ -154,9 +155,8 @@
* @access public
*/
function close()
- {
- if ($this->conn && $this->conn->connected())
- $this->conn->close();
+ {
+ $this->conn->close();
$this->write_cache();
}
@@ -171,7 +171,7 @@
{
$this->close();
$this->connect($this->host, $this->user, $this->pass, $this->port, $this->ssl);
-
+
// issue SELECT command to restore connection status
if ($this->mailbox)
$this->conn->select($this->mailbox);
@@ -201,7 +201,7 @@
$this->root_dir = $root;
$this->options['rootdir'] = $root;
-
+
if (empty($this->delimiter))
$this->get_hierarchy_delimiter();
}
@@ -283,7 +283,7 @@
{
$this->page_size = (int)$size;
}
-
+
/**
* Save a set of message ids for future message listing methods
@@ -297,7 +297,9 @@
{
if (is_array($str) && $msgs == null)
list($str, $msgs, $charset, $sort_field, $threads) = $str;
- if ($msgs != null && !is_array($msgs))
+ if ($msgs === false)
+ $msgs = array();
+ else if ($msgs != null && !is_array($msgs))
$msgs = explode(',', $msgs);
$this->search_string = $str;
@@ -358,7 +360,7 @@
function set_threading($enable=false)
{
$this->threading = false;
-
+
if ($enable) {
if ($this->get_capability('THREAD=REFS'))
$this->threading = 'REFS';
@@ -412,14 +414,15 @@
* @param string Mailbox/folder name
* @param string Mode for count [ALL|THREADS|UNSEEN|RECENT]
* @param boolean Force reading from server and update cache
- * @param boolean Enables MAXUIDs checking
+ * @param boolean Enables storing folder status info (max UID/count),
+ * required for mailbox_status()
* @return int Number of messages
* @access public
*/
- function messagecount($mbox_name='', $mode='ALL', $force=false, $maxuid=true)
+ function messagecount($mbox_name='', $mode='ALL', $force=false, $status=true)
{
$mailbox = $mbox_name ? $this->mod_mailbox($mbox_name) : $this->mailbox;
- return $this->_messagecount($mailbox, $mode, $force, $maxuid);
+ return $this->_messagecount($mailbox, $mode, $force, $status);
}
@@ -429,7 +432,7 @@
* @access private
* @see rcube_imap::messagecount()
*/
- private function _messagecount($mailbox='', $mode='ALL', $force=false, $maxuid=true)
+ private function _messagecount($mailbox='', $mode='ALL', $force=false, $status=true)
{
$mode = strtoupper($mode);
@@ -443,9 +446,9 @@
else
return count((array)$this->search_set);
}
-
+
$a_mailbox_cache = $this->get_cache('messagecount');
-
+
// return cached value
if (!$force && is_array($a_mailbox_cache[$mailbox]) && isset($a_mailbox_cache[$mailbox][$mode]))
return $a_mailbox_cache[$mailbox][$mode];
@@ -455,8 +458,10 @@
if ($mode == 'THREADS') {
$count = $this->_threadcount($mailbox, $msg_count);
- if ($maxuid)
- $_SESSION['maxuid'][$mailbox] = $msg_count ? $this->_id2uid($msg_count, $mailbox) : 0;
+ if ($status) {
+ $this->set_folder_stats($mailbox, 'cnt', $msg_count);
+ $this->set_folder_stats($mailbox, 'maxuid', $msg_count ? $this->_id2uid($msg_count, $mailbox) : 0);
+ }
}
// RECENT count is fetched a bit different
else if ($mode == 'RECENT') {
@@ -469,27 +474,26 @@
// get message count and store in cache
if ($mode == 'UNSEEN')
$search_str .= " UNSEEN";
-
// get message count using SEARCH
// not very performant but more precise (using UNDELETED)
- // disable THREADS for this request
- $threads = $this->threading;
- $this->threading = false;
- $index = $this->_search_index($mailbox, $search_str);
- $this->threading = $threads;
-
+ $index = $this->conn->search($mailbox, $search_str);
+
$count = is_array($index) ? count($index) : 0;
- if ($mode == 'ALL' && $maxuid)
- $_SESSION['maxuid'][$mailbox] = $index ? $this->_id2uid(max($index), $mailbox) : 0;
+ if ($mode == 'ALL' && $status) {
+ $this->set_folder_stats($mailbox, 'cnt', $count);
+ $this->set_folder_stats($mailbox, 'maxuid', $index ? $this->_id2uid(max($index), $mailbox) : 0);
+ }
}
else {
if ($mode == 'UNSEEN')
$count = $this->conn->countUnseen($mailbox);
else {
$count = $this->conn->countMessages($mailbox);
- if ($maxuid)
- $_SESSION['maxuid'][$mailbox] = $count ? $this->_id2uid($count, $mailbox) : 0;
+ if ($status) {
+ $this->set_folder_stats($mailbox,'cnt', $count);
+ $this->set_folder_stats($mailbox, 'maxuid', $count ? $this->_id2uid($count, $mailbox) : 0);
+ }
}
}
@@ -512,13 +516,13 @@
{
if (!empty($this->icache['threads']))
return count($this->icache['threads']['tree']);
-
+
list ($thread_tree, $msg_depth, $has_children) = $this->_fetch_threads($mailbox);
-
+
$msg_count = count($msg_depth);
// $this->update_thread_cache($mailbox, $thread_tree, $msg_depth, $has_children);
- return count($thread_tree);
+ return count($thread_tree);
}
@@ -532,7 +536,7 @@
* @param string Sort order [ASC|DESC]
* @param boolean Number of slice items to extract from result array
* @return array Indexed array with message header objects
- * @access public
+ * @access public
*/
function list_headers($mbox_name='', $page=NULL, $sort_field=NULL, $sort_order=NULL, $slice=0)
{
@@ -563,22 +567,24 @@
$page = $page ? $page : $this->list_page;
$cache_key = $mailbox.'.msg';
- $cache_status = $this->check_cache_status($mailbox, $cache_key);
- // cache is OK, we can get all messages from local cache
- if ($cache_status>0) {
- $start_msg = ($page-1) * $this->page_size;
- $a_msg_headers = $this->get_message_cache($cache_key, $start_msg,
- $start_msg+$this->page_size, $this->sort_field, $this->sort_order);
- $result = array_values($a_msg_headers);
- if ($slice)
- $result = array_slice($result, -$slice, $slice);
- return $result;
- }
- // cache is dirty, sync it
- else if ($this->caching_enabled && $cache_status==-1 && !$recursive) {
- $this->sync_header_index($mailbox);
- return $this->_list_headers($mailbox, $page, $this->sort_field, $this->sort_order, true, $slice);
+ if ($this->caching_enabled) {
+ // cache is OK, we can get messages from local cache
+ // (assume cache is in sync when in recursive mode)
+ if ($recursive || $this->check_cache_status($mailbox, $cache_key)>0) {
+ $start_msg = ($page-1) * $this->page_size;
+ $a_msg_headers = $this->get_message_cache($cache_key, $start_msg,
+ $start_msg+$this->page_size, $this->sort_field, $this->sort_order);
+ $result = array_values($a_msg_headers);
+ if ($slice)
+ $result = array_slice($result, -$slice, $slice);
+ return $result;
+ }
+ // cache is incomplete, sync it (all messages in the folder)
+ else if (!$recursive) {
+ $this->sync_header_index($mailbox);
+ return $this->_list_headers($mailbox, $page, $this->sort_field, $this->sort_order, true, $slice);
+ }
}
// retrieve headers from IMAP
@@ -601,7 +607,7 @@
else
$msg_index = array();
- if ($slice)
+ if ($slice && $msg_index)
$msg_index = array_slice($msg_index, ($this->sort_order == 'DESC' ? 0 : -$slice), $slice);
// fetch reqested headers from server
@@ -647,14 +653,14 @@
// return empty array if no messages found
if (!is_array($a_msg_headers) || empty($a_msg_headers))
return array();
-
+
// use this class for message sorting
$sorter = new rcube_header_sorter();
$sorter->set_sequence_numbers($msg_index);
$sorter->sort_headers($a_msg_headers);
if ($this->sort_order == 'DESC')
- $a_msg_headers = array_reverse($a_msg_headers);
+ $a_msg_headers = array_reverse($a_msg_headers);
return array_values($a_msg_headers);
}
@@ -700,7 +706,7 @@
// get all threads
list ($thread_tree, $msg_depth, $has_children) = $this->conn->thread(
$mailbox, $this->threading, $this->skip_deleted ? 'UNDELETED' : '');
-
+
// add to internal (fast) cache
$this->icache['threads'] = array();
$this->icache['threads']['tree'] = $thread_tree;
@@ -749,7 +755,7 @@
// return empty array if no messages found
if (!is_array($a_msg_headers) || empty($a_msg_headers))
return array();
-
+
// use this class for message sorting
$sorter = new rcube_header_sorter();
$sorter->set_sequence_numbers($all_ids);
@@ -816,9 +822,12 @@
return $this->_list_thread_header_set($mailbox, $page, $sort_field, $sort_order, $slice);
// search set is threaded, we need a new one
- if ($this->search_threads)
+ if ($this->search_threads) {
+ if (empty($this->search_set['tree']))
+ return array();
$this->search('', $this->search_string, $this->search_charset, $sort_field);
-
+ }
+
$msgs = $this->search_set;
$a_msg_headers = array();
$page = $page ? $page : $this->list_page;
@@ -902,7 +911,7 @@
else {
// for small result set we can fetch all messages headers
$this->_fetch_headers($mailbox, join(',', $msgs), $a_msg_headers, NULL);
-
+
// return empty array if no messages found
if (!is_array($a_msg_headers) || empty($a_msg_headers))
return array();
@@ -939,8 +948,15 @@
private function _list_thread_header_set($mailbox, $page=NULL, $sort_field=NULL, $sort_order=NULL, $slice=0)
{
// update search_set if previous data was fetched with disabled threading
- if (!$this->search_threads)
+ if (!$this->search_threads) {
+ if (empty($this->search_set))
+ return array();
$this->search('', $this->search_string, $this->search_charset, $sort_field);
+ }
+
+ // empty result
+ if (empty($this->search_set['tree']))
+ return array();
$thread_tree = $this->search_set['tree'];
$msg_depth = $this->search_set['depth'];
@@ -970,7 +986,7 @@
private function _get_message_range($max, $page)
{
$start_msg = ($page-1) * $this->page_size;
-
+
if ($page=='all') {
$begin = 0;
$end = $max;
@@ -987,10 +1003,10 @@
if ($begin < 0) $begin = 0;
if ($end < 0) $end = $max;
if ($end > $max) $end = $max;
-
+
return array($begin, $end);
}
-
+
/**
* Fetches message headers
@@ -999,7 +1015,7 @@
* @param string Mailbox name
* @param string Message index to fetch
* @param array Reference to message headers array
- * @param array Array with cache index
+ * @param string Cache index string
* @return int Messages count
* @access private
*/
@@ -1012,53 +1028,112 @@
if (empty($a_header_index))
return 0;
- // cache is incomplete
- $cache_index = $this->get_message_cache_index($cache_key);
-
foreach ($a_header_index as $i => $headers) {
- if ($this->caching_enabled && $cache_index[$headers->id] != $headers->uid) {
- // prevent index duplicates
+ $a_msg_headers[$headers->uid] = $headers;
+ }
+
+ // Update cache
+ if ($this->caching_enabled && $cache_key) {
+ // cache is incomplete?
+ $cache_index = $this->get_message_cache_index($cache_key);
+
+ foreach ($a_header_index as $headers) {
+ // message in cache
+ if ($cache_index[$headers->id] == $headers->uid) {
+ unset($cache_index[$headers->id]);
+ continue;
+ }
+ // wrong UID at this position
if ($cache_index[$headers->id]) {
- $this->remove_message_cache($cache_key, $headers->id, true);
+ $for_remove[] = $cache_index[$headers->id];
unset($cache_index[$headers->id]);
}
- // add message to cache
- $this->add_message_cache($cache_key, $headers->id, $headers, NULL,
- !in_array($headers->uid, $cache_index));
- }
+ // message UID in cache but at wrong position
+ if (is_int($key = array_search($headers->uid, $cache_index))) {
+ $for_remove[] = $cache_index[$key];
+ unset($cache_index[$key]);
+ }
- $a_msg_headers[$headers->uid] = $headers;
+ $for_create[] = $headers->uid;
+ }
+
+ if ($for_remove)
+ $this->remove_message_cache($cache_key, $for_remove);
+
+ // add messages to cache
+ foreach ((array)$for_create as $uid) {
+ $headers = $a_msg_headers[$uid];
+ $this->add_message_cache($cache_key, $headers->id, $headers, NULL, true);
+ }
}
return count($a_msg_headers);
}
-
+
+
/**
- * Fetches IDS of pseudo recent messages.
+ * Returns current status of mailbox
*
* We compare the maximum UID to determine the number of
* new messages because the RECENT flag is not reliable.
*
- * @param string Mailbox/folder name
- * @return array List of recent message UIDs
+ * @param string Mailbox/folder name
+ * @return int Folder status
*/
- function recent_uids($mbox_name = null, $nofetch = false)
+ function mailbox_status($mbox_name = null)
{
$mailbox = $mbox_name ? $this->mod_mailbox($mbox_name) : $this->mailbox;
- $old_maxuid = intval($_SESSION['maxuid'][$mailbox]);
-
- // refresh message count -> will update $_SESSION['maxuid'][$mailbox]
+ $old = $this->get_folder_stats($mailbox);
+
+ // refresh message count -> will update
$this->_messagecount($mailbox, 'ALL', true);
-
- if ($_SESSION['maxuid'][$mailbox] > $old_maxuid) {
- $maxuid = max(1, $old_maxuid+1);
- return array_values((array)$this->conn->fetchHeaderIndex(
- $mailbox, "$maxuid:*", 'UID', $this->skip_deleted, true));
- }
-
- return array();
+
+ $result = 0;
+ $new = $this->get_folder_stats($mailbox);
+
+ // got new messages
+ if ($new['maxuid'] > $old['maxuid'])
+ $result += 1;
+ // some messages has been deleted
+ if ($new['cnt'] < $old['cnt'])
+ $result += 2;
+
+ // @TODO: optional checking for messages flags changes (?)
+ // @TODO: UIDVALIDITY checking
+
+ return $result;
}
-
+
+
+ /**
+ * Stores folder statistic data in session
+ * @TODO: move to separate DB table (cache?)
+ *
+ * @param string Mailbox name
+ * @param string Data name
+ * @param mixed Data value
+ */
+ private function set_folder_stats($mbox_name, $name, $data)
+ {
+ $_SESSION['folders'][$mbox_name][$name] = $data;
+ }
+
+
+ /**
+ * Gets folder statistic data
+ *
+ * @param string Mailbox name
+ * @return array Stats data
+ */
+ private function get_folder_stats($mbox_name)
+ {
+ if ($_SESSION['folders'][$mbox_name])
+ return (array) $_SESSION['folders'][$mbox_name];
+ else
+ return array();
+ }
+
+
/**
* Return sorted array of message IDs (not UIDs)
*
@@ -1105,7 +1180,7 @@
else {
$a_index = $this->conn->fetchHeaderIndex($mailbox,
join(',', $this->search_set), $this->sort_field, $this->skip_deleted);
-
+
if (is_array($a_index)) {
if ($this->sort_order=="ASC")
asort($a_index);
@@ -1131,7 +1206,7 @@
// cache is OK
if ($cache_status>0) {
$a_index = $this->get_message_cache_index($cache_key,
- true, $this->sort_field, $this->sort_order);
+ $this->sort_field, $this->sort_order);
return array_keys($a_index);
}
@@ -1143,20 +1218,20 @@
$a_index = range(1, $max);
}
- if ($this->sort_order == 'DESC')
+ if ($a_index !== false && $this->sort_order == 'DESC')
$a_index = array_reverse($a_index);
$this->cache[$key] = $a_index;
}
// fetch complete message index
else if ($this->get_capability('SORT')) {
- if ($a_index = $this->conn->sort($mailbox,
- $this->sort_field, $this->skip_deleted ? 'UNDELETED' : '')) {
- if ($this->sort_order == 'DESC')
- $a_index = array_reverse($a_index);
-
- $this->cache[$key] = $a_index;
- }
+ $a_index = $this->conn->sort($mailbox,
+ $this->sort_field, $this->skip_deleted ? 'UNDELETED' : '');
+
+ if ($a_index !== false && $this->sort_order == 'DESC')
+ $a_index = array_reverse($a_index);
+
+ $this->cache[$key] = $a_index;
}
else if ($a_index = $this->conn->fetchHeaderIndex(
$mailbox, "1:*", $this->sort_field, $this->skip_deleted)) {
@@ -1164,11 +1239,11 @@
asort($a_index);
else if ($this->sort_order=="DESC")
arsort($a_index);
-
+
$this->cache[$key] = array_keys($a_index);
}
- return $this->cache[$key];
+ return $this->cache[$key] !== false ? $this->cache[$key] : array();
}
@@ -1205,7 +1280,7 @@
// cache is OK
if ($cache_status>0) {
- $a_index = $this->get_message_cache_index($cache_key, true, $this->sort_field, $this->sort_order);
+ $a_index = $this->get_message_cache_index($cache_key, $this->sort_field, $this->sort_order);
return array_keys($a_index);
}
*/
@@ -1213,7 +1288,7 @@
list ($thread_tree) = $this->_fetch_threads($mailbox);
$this->cache[$key] = $this->_flatten_threads($mailbox, $thread_tree);
-
+
return $this->cache[$key];
}
@@ -1238,13 +1313,15 @@
if ($this->sort_order == 'DESC')
$msg_index = array_reverse($msg_index);
-
+
// flatten threads array
$all_ids = array();
foreach($msg_index as $root) {
$all_ids[] = $root;
- if (!empty($thread_tree[$root]))
- $all_ids = array_merge($all_ids, array_keys_recursive($thread_tree[$root]));
+ if (!empty($thread_tree[$root])) {
+ foreach (array_keys_recursive($thread_tree[$root]) as $val)
+ $all_ids[] = $val;
+ }
}
return $all_ids;
@@ -1258,13 +1335,43 @@
{
$cache_key = $mailbox.'.msg';
$cache_index = $this->get_message_cache_index($cache_key);
+ $chunk_size = 1000;
+
+ // cache is empty, get all messages
+ if (is_array($cache_index) && empty($cache_index)) {
+ $max = $this->_messagecount($mailbox);
+ // syncing a big folder maybe slow
+ @set_time_limit(0);
+ $start = 1;
+ $end = min($chunk_size, $max);
+ while (true) {
+ // do this in loop to save memory (1000 msgs ~= 10 MB)
+ if ($headers = $this->conn->fetchHeaders($mailbox,
+ "$start:$end", false, false, $this->fetch_add_headers)
+ ) {
+ foreach ($headers as $header) {
+ $this->add_message_cache($cache_key, $header->id, $header, NULL, true);
+ }
+ }
+ if ($end - $start < $chunk_size - 1)
+ break;
+
+ $end = min($end+$chunk_size, $max);
+ $start += $chunk_size;
+ }
+ return;
+ }
// fetch complete message index
- $a_message_index = $this->conn->fetchHeaderIndex($mailbox, "1:*", 'UID', $this->skip_deleted);
-
- if ($a_message_index === false)
- return false;
-
+ if (isset($this->icache['folder_index']))
+ $a_message_index = &$this->icache['folder_index'];
+ else
+ $a_message_index = $this->conn->fetchHeaderIndex($mailbox, "1:*", 'UID', $this->skip_deleted);
+
+ if ($a_message_index === false || $cache_index === null)
+ return;
+
+ // compare cache index with real index
foreach ($a_message_index as $id => $uid) {
// message in cache at correct position
if ($cache_index[$id] == $uid) {
@@ -1272,34 +1379,41 @@
continue;
}
- // message in cache but in wrong position
- if (in_array((string)$uid, $cache_index, true)) {
- unset($cache_index[$id]);
- }
-
// other message at this position
if (isset($cache_index[$id])) {
$for_remove[] = $cache_index[$id];
unset($cache_index[$id]);
}
-
+
+ // message in cache but at wrong position
+ if (is_int($key = array_search($uid, $cache_index))) {
+ $for_remove[] = $uid;
+ unset($cache_index[$key]);
+ }
+
$for_update[] = $id;
}
- // clear messages at wrong positions and those deleted that are still in cache_index
+ // remove messages at wrong positions and those deleted that are still in cache_index
if (!empty($for_remove))
$cache_index = array_merge($cache_index, $for_remove);
-
+
if (!empty($cache_index))
$this->remove_message_cache($cache_key, $cache_index);
// fetch complete headers and add to cache
if (!empty($for_update)) {
- if ($headers = $this->conn->fetchHeader($mailbox,
- join(',', $for_update), false, $this->fetch_add_headers)) {
- foreach ($headers as $header) {
- $this->add_message_cache($cache_key, $header->id, $header, NULL,
- in_array($header->uid, (array)$for_remove));
+ // syncing a big folder maybe slow
+ @set_time_limit(0);
+ // To save memory do this in chunks
+ $for_update = array_chunk($for_update, $chunk_size);
+ foreach ($for_update as $uids) {
+ if ($headers = $this->conn->fetchHeaders($mailbox,
+ $uids, false, false, $this->fetch_add_headers)
+ ) {
+ foreach ($headers as $header) {
+ $this->add_message_cache($cache_key, $header->id, $header, NULL, true);
+ }
}
}
}
@@ -1320,36 +1434,10 @@
{
if (!$str)
return false;
-
+
$mailbox = $mbox_name ? $this->mod_mailbox($mbox_name) : $this->mailbox;
$results = $this->_search_index($mailbox, $str, $charset, $sort_field);
-
- // try search with US-ASCII charset (should be supported by server)
- // only if UTF-8 search is not supported
- if (empty($results) && !is_array($results) && !empty($charset) && $charset != 'US-ASCII')
- {
- // convert strings to US_ASCII
- if(preg_match_all('/\{([0-9]+)\}\r\n/', $str, $matches, PREG_OFFSET_CAPTURE)) {
- $last = 0; $res = '';
- foreach($matches[1] as $m)
- {
- $string_offset = $m[1] + strlen($m[0]) + 4; // {}\r\n
- $string = substr($str, $string_offset - 1, $m[0]);
- $string = rcube_charset_convert($string, $charset, 'US-ASCII');
- if (!$string)
- continue;
- $res .= sprintf("%s{%d}\r\n%s", substr($str, $last, $m[1] - $last - 1), strlen($string), $string);
- $last = $m[0] + $string_offset - 1;
- }
- if ($last < strlen($str))
- $res .= substr($str, $last, strlen($str)-$last);
- }
- else // strings for conversion not found
- $res = $str;
-
- $results = $this->search($mbox_name, $res, NULL, $sort_field);
- }
$this->set_search_set($str, $results, $charset, $sort_field, (bool)$this->threading);
@@ -1372,21 +1460,32 @@
$criteria = 'UNDELETED '.$criteria;
if ($this->threading) {
- list ($thread_tree, $msg_depth, $has_children) = $this->conn->thread(
- $mailbox, $this->threading, $criteria, $charset);
+ $a_messages = $this->conn->thread($mailbox, $this->threading, $criteria, $charset);
- $a_messages = array(
- 'tree' => $thread_tree,
- 'depth' => $msg_depth,
- 'children' => $has_children
- );
+ // Error, try with US-ASCII (RFC5256: SORT/THREAD must support US-ASCII and UTF-8,
+ // but I've seen that Courier doesn't support UTF-8)
+ if ($a_messages === false && $charset && $charset != 'US-ASCII')
+ $a_messages = $this->conn->thread($mailbox, $this->threading,
+ $this->convert_criteria($criteria, $charset), 'US-ASCII');
+
+ if ($a_messages !== false) {
+ list ($thread_tree, $msg_depth, $has_children) = $a_messages;
+ $a_messages = array(
+ 'tree' => $thread_tree,
+ 'depth' => $msg_depth,
+ 'children' => $has_children
+ );
+ }
}
else if ($sort_field && $this->get_capability('SORT')) {
$charset = $charset ? $charset : $this->default_charset;
$a_messages = $this->conn->sort($mailbox, $sort_field, $criteria, false, $charset);
- if (!$a_messages)
- return array();
+ // Error, try with US-ASCII (RFC5256: SORT/THREAD must support US-ASCII and UTF-8,
+ // but I've seen that Courier doesn't support UTF-8)
+ if ($a_messages === false && $charset && $charset != 'US-ASCII')
+ $a_messages = $this->conn->sort($mailbox, $sort_field,
+ $this->convert_criteria($criteria, $charset), false, 'US-ASCII');
}
else {
if ($orig_criteria == 'ALL') {
@@ -1395,14 +1494,16 @@
}
else {
$a_messages = $this->conn->search($mailbox,
- ($charset ? "CHARSET $charset " : '') . $criteria);
+ ($charset ? "CHARSET $charset " : '') . $criteria);
- if (!$a_messages)
- return array();
+ // Error, try with US-ASCII (some servers may support only US-ASCII)
+ if ($a_messages === false && $charset && $charset != 'US-ASCII')
+ $a_messages = $this->conn->search($mailbox,
+ 'CHARSET US-ASCII ' . $this->convert_criteria($criteria, $charset));
- // I didn't found that SEARCH always returns sorted IDs
- if (!$this->sort_field)
- sort($a_messages);
+ // I didn't found that SEARCH should return sorted IDs
+ if (is_array($a_messages) && !$this->sort_field)
+ sort($a_messages);
}
}
@@ -1410,10 +1511,10 @@
// $a_mailbox_cache = get_cache('messagecount');
// $a_mailbox_cache[$mailbox][$criteria] = sizeof($a_messages);
// $this->update_cache('messagecount', $a_mailbox_cache);
-
+
return $a_messages;
}
-
+
/**
* Direct (real and simple) SEARCH request to IMAP server,
@@ -1429,13 +1530,46 @@
{
if (!$str)
return false;
-
+
$mailbox = $mbox_name ? $this->mod_mailbox($mbox_name) : $this->mailbox;
return $this->conn->search($mailbox, $str, $ret_uid);
}
-
+
+ /**
+ * Converts charset of search criteria string
+ *
+ * @param string Search string
+ * @param string Original charset
+ * @param string Destination charset (default US-ASCII)
+ * @return string Search string
+ * @access private
+ */
+ private function convert_criteria($str, $charset, $dest_charset='US-ASCII')
+ {
+ // convert strings to US_ASCII
+ if (preg_match_all('/\{([0-9]+)\}\r\n/', $str, $matches, PREG_OFFSET_CAPTURE)) {
+ $last = 0; $res = '';
+ foreach ($matches[1] as $m) {
+ $string_offset = $m[1] + strlen($m[0]) + 4; // {}\r\n
+ $string = substr($str, $string_offset - 1, $m[0]);
+ $string = rcube_charset_convert($string, $charset, $dest_charset);
+ if (!$string)
+ continue;
+ $res .= sprintf("%s{%d}\r\n%s", substr($str, $last, $m[1] - $last - 1), strlen($string), $string);
+ $last = $m[0] + $string_offset - 1;
+ }
+ if ($last < strlen($str))
+ $res .= substr($str, $last, strlen($str)-$last);
+ }
+ else // strings for conversion not found
+ $res = $str;
+
+ return $res;
+ }
+
+
/**
* Sort thread
*
@@ -1495,7 +1629,7 @@
{
if (empty($tree))
return array();
-
+
$index = array_combine(array_values($index), $index);
// assign roots
@@ -1508,7 +1642,7 @@
}
}
- $index = array_values($index);
+ $index = array_values($index);
// create sorted array of roots
$msg_index = array();
@@ -1538,13 +1672,12 @@
{
if (!empty($this->search_string))
$this->search_set = $this->search('', $this->search_string, $this->search_charset,
+ $this->search_sort_field, $this->search_threads);
- $this->search_sort_field, $this->search_threads);
-
return $this->get_search_set();
}
-
-
+
+
/**
* Check if the given message ID is part of the current search set
*
@@ -1567,7 +1700,7 @@
* Return message headers object of a specific message
*
* @param int Message ID
- * @param string Mailbox to read from
+ * @param string Mailbox to read from
* @param boolean True if $id is the message UID
* @param boolean True if we need also BODYSTRUCTURE in headers
* @return object Message headers representation
@@ -1589,7 +1722,7 @@
if ($headers->uid && $headers->id)
$this->uid_id_map[$mailbox][$headers->uid] = $headers->id;
- $this->add_message_cache($mailbox.'.msg', $headers->id, $headers, NULL, true);
+ $this->add_message_cache($mailbox.'.msg', $headers->id, $headers, NULL, false, true);
}
return $headers;
@@ -1631,14 +1764,22 @@
else
$this->struct_charset = $this->_structure_charset($structure);
- // Here we can recognize malformed BODYSTRUCTURE and
+ $headers->ctype = strtolower($headers->ctype);
+
+ // Here we can recognize malformed BODYSTRUCTURE and
// 1. [@TODO] parse the message in other way to create our own message structure
// 2. or just show the raw message body.
// Example of structure for malformed MIME message:
- // ("text" "plain" ("charset" "us-ascii") NIL NIL "7bit" 2154 70 NIL NIL NIL)
- if ($headers->ctype && $headers->ctype != 'text/plain'
- && $structure[0] == 'text' && $structure[1] == 'plain') {
- return false;
+ // ("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') {
+ // we can handle single-part messages, by simple fix in structure (#1486898)
+ if (preg_match('/^(text|application)\/(.*)/', $headers->ctype, $m)) {
+ $structure[0] = $m[1];
+ $structure[1] = $m[2];
+ }
+ else
+ return false;
}
$struct = &$this->_structure_part($structure);
@@ -1653,19 +1794,20 @@
// write structure to cache
if ($this->caching_enabled)
- $this->add_message_cache($cache_key, $this->_msg_id, $headers, $struct);
+ $this->add_message_cache($cache_key, $this->_msg_id, $headers, $struct,
+ $this->icache['message.id'][$uid], true);
}
return $struct;
}
-
+
/**
* Build message part object
*
* @access private
*/
- function &_structure_part($part, $count=0, $parent='', $mime_headers=null, $raw_headers=null)
+ function &_structure_part($part, $count=0, $parent='', $mime_headers=null)
{
$struct = new rcube_message_part;
$struct->mime_id = empty($parent) ? (string)$count : "$parent.$count";
@@ -1673,7 +1815,19 @@
// multipart
if (is_array($part[0])) {
$struct->ctype_primary = 'multipart';
-
+
+ /* RFC3501: BODYSTRUCTURE fields of multipart part
+ part1 array
+ part2 array
+ part3 array
+ ....
+ 1. subtype
+ 2. parameters (optional)
+ 3. description (optional)
+ 4. language (optional)
+ 5. location (optional)
+ */
+
// find first non-array entry
for ($i=1; $i<count($part); $i++) {
if (!is_array($part[$i])) {
@@ -1681,27 +1835,26 @@
break;
}
}
-
+
$struct->mimetype = 'multipart/'.$struct->ctype_secondary;
// build parts list for headers pre-fetching
- for ($i=0, $count=0; $i<count($part); $i++) {
- if (is_array($part[$i]) && count($part[$i]) > 3) {
- // fetch message headers if message/rfc822
- // or named part (could contain Content-Location header)
- if (!is_array($part[$i][0])) {
- $tmp_part_id = $struct->mime_id ? $struct->mime_id.'.'.($i+1) : $i+1;
- if (strtolower($part[$i][0]) == 'message' && strtolower($part[$i][1]) == 'rfc822') {
- $raw_part_headers[] = $tmp_part_id;
- $mime_part_headers[] = $tmp_part_id;
- }
- else if (in_array('name', (array)$part[$i][2]) && (empty($part[$i][3]) || $part[$i][3]=='NIL')) {
- $mime_part_headers[] = $tmp_part_id;
- }
+ for ($i=0; $i<count($part); $i++) {
+ if (!is_array($part[$i]))
+ break;
+ // fetch message headers if message/rfc822
+ // or named part (could contain Content-Location header)
+ if (!is_array($part[$i][0])) {
+ $tmp_part_id = $struct->mime_id ? $struct->mime_id.'.'.($i+1) : $i+1;
+ if (strtolower($part[$i][0]) == 'message' && strtolower($part[$i][1]) == 'rfc822') {
+ $mime_part_headers[] = $tmp_part_id;
+ }
+ else if (in_array('name', (array)$part[$i][2]) && (empty($part[$i][3]) || $part[$i][3]=='NIL')) {
+ $mime_part_headers[] = $tmp_part_id;
}
}
}
-
+
// pre-fetch headers of all parts (in one command for better performance)
// @TODO: we could do this before _structure_part() call, to fetch
// headers for parts on all levels
@@ -1709,22 +1862,39 @@
$mime_part_headers = $this->conn->fetchMIMEHeaders($this->mailbox,
$this->_msg_id, $mime_part_headers);
}
- // we'll need a real content-type of message/rfc822 part
- if ($raw_part_headers) {
- $raw_part_headers = $this->conn->fetchMIMEHeaders($this->mailbox,
- $this->_msg_id, $raw_part_headers, false);
- }
+
$struct->parts = array();
for ($i=0, $count=0; $i<count($part); $i++) {
- if (is_array($part[$i]) && count($part[$i]) > 3) {
- $tmp_part_id = $struct->mime_id ? $struct->mime_id.'.'.($i+1) : $i+1;
- $struct->parts[] = $this->_structure_part($part[$i], ++$count, $struct->mime_id,
- $mime_part_headers[$tmp_part_id], $raw_part_headers[$tmp_part_id]);
- }
+ if (!is_array($part[$i]))
+ break;
+ $tmp_part_id = $struct->mime_id ? $struct->mime_id.'.'.($i+1) : $i+1;
+ $struct->parts[] = $this->_structure_part($part[$i], ++$count, $struct->mime_id,
+ $mime_part_headers[$tmp_part_id]);
}
return $struct;
}
+
+ /* RFC3501: BODYSTRUCTURE fields of non-multipart part
+ 0. type
+ 1. subtype
+ 2. parameters
+ 3. id
+ 4. description
+ 5. encoding
+ 6. size
+ -- text
+ 7. lines
+ -- message/rfc822
+ 7. envelope structure
+ 8. body structure
+ 9. lines
+ --
+ x. md5 (optional)
+ x. disposition (optional)
+ x. language (optional)
+ x. location (optional)
+ */
// regular part
$struct->ctype_primary = strtolower($part[0]);
@@ -1736,49 +1906,53 @@
$struct->ctype_parameters = array();
for ($i=0; $i<count($part[2]); $i+=2)
$struct->ctype_parameters[strtolower($part[2][$i])] = $part[2][$i+1];
-
+
if (isset($struct->ctype_parameters['charset']))
$struct->charset = $struct->ctype_parameters['charset'];
}
-
+
// read content encoding
if (!empty($part[5]) && $part[5]!='NIL') {
$struct->encoding = strtolower($part[5]);
$struct->headers['content-transfer-encoding'] = $struct->encoding;
}
-
+
// get part size
if (!empty($part[6]) && $part[6]!='NIL')
$struct->size = intval($part[6]);
// read part disposition
- $di = count($part) - 2;
- if ((is_array($part[$di]) && count($part[$di]) == 2 && is_array($part[$di][1])) ||
- (is_array($part[--$di]) && count($part[$di]) == 2)) {
+ $di = 8;
+ if ($struct->ctype_primary == 'text') $di += 1;
+ else if ($struct->mimetype == 'message/rfc822') $di += 3;
+
+ if (is_array($part[$di]) && count($part[$di]) == 2) {
$struct->disposition = strtolower($part[$di][0]);
if (is_array($part[$di][1]))
for ($n=0; $n<count($part[$di][1]); $n+=2)
$struct->d_parameters[strtolower($part[$di][1][$n])] = $part[$di][1][$n+1];
}
-
- // get child parts
+
+ // get message/rfc822's child-parts
if (is_array($part[8]) && $di != 8) {
$struct->parts = array();
- for ($i=0, $count=0; $i<count($part[8]); $i++)
- if (is_array($part[8][$i]) && count($part[8][$i]) > 5)
- $struct->parts[] = $this->_structure_part($part[8][$i], ++$count, $struct->mime_id);
+ for ($i=0, $count=0; $i<count($part[8]); $i++) {
+ if (!is_array($part[8][$i]))
+ break;
+ $struct->parts[] = $this->_structure_part($part[8][$i], ++$count, $struct->mime_id);
+ }
}
// get part ID
if (!empty($part[3]) && $part[3]!='NIL') {
$struct->content_id = $part[3];
$struct->headers['content-id'] = $part[3];
-
+
if (empty($struct->disposition))
$struct->disposition = 'inline';
}
-
+
// fetch message headers if message/rfc822 or named part (could contain Content-Location header)
if ($struct->ctype_primary == 'message' || ($struct->ctype_parameters['name'] && !$struct->content_id)) {
if (empty($mime_headers)) {
@@ -1787,24 +1961,24 @@
}
$struct->headers = $this->_parse_headers($mime_headers) + $struct->headers;
- // get real headers for message of type 'message/rfc822'
+ // get real content-type of message/rfc822
if ($struct->mimetype == 'message/rfc822') {
- if (empty($raw_headers)) {
- $raw_headers = $this->conn->fetchMIMEHeaders(
- $this->mailbox, $this->_msg_id, (array)$struct->mime_id, false);
+ // single-part
+ if (!is_array($part[8][0]))
+ $struct->real_mimetype = strtolower($part[8][0] . '/' . $part[8][1]);
+ // multi-part
+ else {
+ for ($n=0; $n<count($part[8]); $n++)
+ if (!is_array($part[8][$n]))
+ break;
+ $struct->real_mimetype = 'multipart/' . strtolower($part[8][$n]);
}
- $struct->real_headers = $this->_parse_headers($raw_headers);
-
- // get real content-type of message/rfc822
- if (preg_match('/^([a-z0-9_\/-]+)/i', $struct->real_headers['content-type'], $matches)) {
- $struct->real_mimetype = strtolower($matches[1]);
- }
}
- }
- if ($struct->ctype_primary=='message') {
- if (is_array($part[8]) && $di != 8 && empty($struct->parts))
- $struct->parts[] = $this->_structure_part($part[8], ++$count, $struct->mime_id);
+ if ($struct->ctype_primary == 'message' && empty($struct->parts)) {
+ if (is_array($part[8]) && $di != 8)
+ $struct->parts[] = $this->_structure_part($part[8], ++$count, $struct->mime_id);
+ }
}
// normalize filename property
@@ -1812,10 +1986,10 @@
return $struct;
}
-
+
/**
- * Set attachment filename from message part structure
+ * Set attachment filename from message part structure
*
* @access private
* @param object rcube_message_part Part object
@@ -1920,10 +2094,10 @@
// decode filename
if (!empty($filename_mime)) {
- $part->filename = rcube_imap::decode_mime_string($filename_mime,
+ $part->filename = rcube_imap::decode_mime_string($filename_mime,
$part->charset ? $part->charset : ($this->struct_charset ? $this->struct_charset :
rc_detect_encoding($filename_mime, $this->default_charset)));
- }
+ }
else if (!empty($filename_encoded)) {
// decode filename according to RFC 2231, Section 4
if (preg_match("/^([^']*)'[^']*'(.*)$/", $filename_encoded, $fmatches)) {
@@ -1949,7 +2123,7 @@
return $structure[2][1];
$structure = $structure[0];
}
- }
+ }
/**
@@ -1966,7 +2140,7 @@
{
// get part encoding if not provided
if (!is_object($o_part)) {
- $structure_str = $this->conn->fetchStructureString($this->mailbox, $uid, true);
+ $structure_str = $this->conn->fetchStructureString($this->mailbox, $uid, true);
$structure = new rcube_mime_struct();
// error or message not found
if (!$structure->loadStructure($structure_str)) {
@@ -1978,7 +2152,7 @@
$o_part->encoding = strtolower($structure->getPartEncoding($part));
$o_part->charset = $structure->getPartCharset($part);
}
-
+
// TODO: Add caching for message parts
if (!$part) $part = 'TEXT';
@@ -1990,14 +2164,14 @@
return true;
// convert charset (if text or message part)
- if ($o_part->ctype_primary=='text' || $o_part->ctype_primary=='message') {
+ if ($body && ($o_part->ctype_primary == 'text' || $o_part->ctype_primary == 'message')) {
// assume default if no charset specified
if (empty($o_part->charset) || strtolower($o_part->charset) == 'us-ascii')
$o_part->charset = $this->default_charset;
$body = rcube_charset_convert($body, $o_part->charset);
}
-
+
return $body;
}
@@ -2039,13 +2213,13 @@
{
return $this->conn->fetchPartHeader($this->mailbox, $uid, true);
}
-
+
/**
* Sends the whole message source to stdout
*
* @param int Message UID
- */
+ */
function print_raw_body($uid)
{
$this->conn->handlePartBody($this->mailbox, $uid, true, NULL, NULL, true);
@@ -2126,11 +2300,8 @@
// make sure mailbox exists
if ($this->mailbox_exists($mbox_name, true)) {
- if ($is_file) {
- $separator = rcmail::get_instance()->config->header_delimiter();
- $saved = $this->conn->appendFromFile($mailbox, $message,
- $headers, $separator.$separator);
- }
+ if ($is_file)
+ $saved = $this->conn->appendFromFile($mailbox, $message, $headers);
else
$saved = $this->conn->append($mailbox, $message);
}
@@ -2294,7 +2465,7 @@
// unset threads internal cache
unset($this->icache['threads']);
-
+
// remove message ids from search set
if ($this->search_set && $mailbox == $this->mailbox) {
// threads are too complicated to just remove messages from set
@@ -2330,21 +2501,21 @@
{
$mailbox = !empty($mbox_name) ? $this->mod_mailbox($mbox_name) : $this->mailbox;
$msg_count = $this->_messagecount($mailbox, 'ALL');
-
+
if (!$msg_count) {
return 0;
}
-
+
$cleared = $this->conn->clearFolder($mailbox);
-
+
// make sure the message count cache is cleared as well
if ($cleared) {
- $this->clear_message_cache($mailbox.'.msg');
+ $this->clear_message_cache($mailbox.'.msg');
$a_mailbox_cache = $this->get_cache('messagecount');
unset($a_mailbox_cache[$mailbox]);
$this->update_cache('messagecount', $a_mailbox_cache);
}
-
+
return $cleared;
}
@@ -2375,7 +2546,7 @@
*/
private function _expunge($mailbox, $clear_cache=true, $uids=NULL)
{
- if ($uids && $this->get_capability('UIDPLUS'))
+ if ($uids && $this->get_capability('UIDPLUS'))
$a_uids = is_array($uids) ? join(',', $uids) : $uids;
else
$a_uids = NULL;
@@ -2386,7 +2557,7 @@
$this->clear_message_cache($mailbox.'.msg');
$this->_clear_messagecount($mailbox);
}
-
+
return $result;
}
@@ -2396,7 +2567,7 @@
*
* @param mixed UIDs array or comma-separated list or '*' or '1:*'
* @param string Mailbox name
- * @return array Two elements array with UIDs converted to list and ALL flag
+ * @return array Two elements array with UIDs converted to list and ALL flag
* @access private
*/
private function _parse_uids($uids, $mailbox)
@@ -2413,7 +2584,7 @@
$uids = $this->conn->fetchUIDs($mailbox, array_keys($this->search_set['depth']));
else
$uids = $this->conn->fetchUIDs($mailbox, $this->search_set);
-
+
// save ID-to-UID mapping in local cache
if (is_array($uids))
foreach ($uids as $id => $uid)
@@ -2441,7 +2612,7 @@
* @param string Mailbox name
* @return int Message ID
*/
- function get_id($uid, $mbox_name=NULL)
+ function get_id($uid, $mbox_name=NULL)
{
$mailbox = $mbox_name ? $this->mod_mailbox($mbox_name) : $this->mailbox;
return $this->_uid2id($uid, $mailbox);
@@ -2468,7 +2639,7 @@
* --------------------------------*/
/**
- * Public method for mailbox listing.
+ * Public method for listing subscribed folders
*
* Converts mailbox name with root dir first
*
@@ -2482,10 +2653,10 @@
$a_out = array();
$a_mboxes = $this->_list_mailboxes($root, $filter);
- foreach ($a_mboxes as $mbox_row) {
- $name = $this->mod_mailbox($mbox_row, 'out');
- if (strlen($name))
+ foreach ($a_mboxes as $idx => $mbox_row) {
+ if ($name = $this->mod_mailbox($mbox_row, 'out'))
$a_out[] = $name;
+ unset($a_mboxes[$idx]);
}
// INBOX should always be available
@@ -2508,17 +2679,17 @@
*/
private function _list_mailboxes($root='', $filter='*')
{
- $a_defaults = $a_out = array();
-
- // get cached folder list
+ // get cached folder list
$a_mboxes = $this->get_cache('mailboxes');
if (is_array($a_mboxes))
return $a_mboxes;
+ $a_defaults = $a_out = array();
+
// Give plugins a chance to provide a list of mailboxes
- $data = rcmail::get_instance()->plugins->exec_hook('list_mailboxes',
- array('root'=>$root,'filter'=>$filter));
-
+ $data = rcmail::get_instance()->plugins->exec_hook('mailboxes_list',
+ array('root' => $root, 'filter' => $filter, 'mode' => 'LSUB'));
+
if (isset($data['folders'])) {
$a_folders = $data['folders'];
}
@@ -2526,38 +2697,52 @@
// retrieve list of folders from IMAP server
$a_folders = $this->conn->listSubscribed($this->mod_mailbox($root), $filter);
}
-
+
if (!is_array($a_folders) || !sizeof($a_folders))
$a_folders = array();
// write mailboxlist to cache
$this->update_cache('mailboxes', $a_folders);
-
+
return $a_folders;
}
/**
* Get a list of all folders available on the IMAP server
- *
+ *
* @param string IMAP root dir
+ * @param string Optional filter for mailbox listing
* @return array Indexed array with folder names
*/
- function list_unsubscribed($root='')
+ function list_unsubscribed($root='', $filter='*')
{
- static $a_folders;
-
- if (is_array($a_folders))
- return $a_folders;
-
- // retrieve list of folders from IMAP server
- $a_mboxes = $this->conn->listMailboxes($this->mod_mailbox($root), '*');
+ // Give plugins a chance to provide a list of mailboxes
+ $data = rcmail::get_instance()->plugins->exec_hook('mailboxes_list',
+ array('root' => $root, 'filter' => $filter, 'mode' => 'LIST'));
+
+ if (isset($data['folders'])) {
+ $a_mboxes = $data['folders'];
+ }
+ else {
+ // retrieve list of folders from IMAP server
+ $a_mboxes = $this->conn->listMailboxes($this->mod_mailbox($root), $filter);
+ }
+
+ $a_folders = array();
+ if (!is_array($a_mboxes))
+ $a_mboxes = array();
// modify names with root dir
- foreach ($a_mboxes as $mbox_name) {
+ foreach ($a_mboxes as $idx => $mbox_name) {
if ($name = $this->mod_mailbox($mbox_name, 'out'))
$a_folders[] = $name;
+ unset($a_mboxes[$idx]);
}
+
+ // INBOX should always be available
+ if (!in_array('INBOX', $a_folders))
+ array_unshift($a_folders, 'INBOX');
// filter folders and sort them
$a_folders = $this->_sort_mailbox_list($a_folders);
@@ -2568,14 +2753,14 @@
/**
* Get mailbox quota information
* added by Nuny
- *
+ *
* @return mixed Quota info or False if not supported
*/
function get_quota()
{
if ($this->get_capability('QUOTA'))
return $this->conn->getQuota();
-
+
return false;
}
@@ -2585,7 +2770,7 @@
*
* @param array Mailbox name(s)
* @return boolean True on success
- */
+ */
function subscribe($a_mboxes)
{
if (!is_array($a_mboxes))
@@ -2622,7 +2807,7 @@
function create_mailbox($name, $subscribe=false)
{
$result = false;
-
+
// reduce mailbox name to 100 chars
$name = substr($name, 0, 100);
$abs_name = $this->mod_mailbox($name);
@@ -2653,11 +2838,11 @@
// make absolute path
$mailbox = $this->mod_mailbox($mbox_name);
$abs_name = $this->mod_mailbox($name);
-
+
// check if mailbox is subscribed
$a_subscribed = $this->_list_mailboxes();
$subscribed = in_array($mailbox, $a_subscribed);
-
+
// unsubscribe folder
if ($subscribed)
$this->conn->unsubscribe($mailbox);
@@ -2667,7 +2852,7 @@
if ($result) {
$delm = $this->get_hierarchy_delimiter();
-
+
// check if mailbox children are subscribed
foreach ($a_subscribed as $c_subscribed)
if (preg_match('/^'.preg_quote($mailbox.$delm, '/').'/', $c_subscribed)) {
@@ -2678,7 +2863,7 @@
// clear cache
$this->clear_message_cache($mailbox.'.msg');
- $this->clear_cache('mailboxes');
+ $this->clear_cache('mailboxes');
}
// try to subscribe it
@@ -2708,23 +2893,23 @@
foreach ($a_mboxes as $mbox_name) {
$mailbox = $this->mod_mailbox($mbox_name);
$sub_mboxes = $this->conn->listMailboxes($this->mod_mailbox(''),
- $mbox_name . $this->delimiter . '*');
+ $mbox_name . $this->delimiter . '*');
// unsubscribe mailbox before deleting
$this->conn->unsubscribe($mailbox);
// send delete command to server
$result = $this->conn->deleteFolder($mailbox);
- if ($result >= 0) {
+ if ($result) {
$deleted = true;
$this->clear_message_cache($mailbox.'.msg');
}
-
+
foreach ($sub_mboxes as $c_mbox) {
if ($c_mbox != 'INBOX') {
$this->conn->unsubscribe($c_mbox);
$result = $this->conn->deleteFolder($c_mbox);
- if ($result >= 0) {
+ if ($result) {
$deleted = true;
$this->clear_message_cache($c_mbox.'.msg');
}
@@ -2769,14 +2954,19 @@
if ($mbox_name == 'INBOX')
return true;
+ $key = $subscription ? 'subscribed' : 'existing';
+ if (is_array($this->icache[$key]) && in_array($mbox_name, $this->icache[$key]))
+ return true;
+
if ($subscription) {
- if ($a_folders = $this->conn->listSubscribed($this->mod_mailbox(''), $mbox_name))
- return true;
+ $a_folders = $this->conn->listSubscribed($this->mod_mailbox(''), $mbox_name);
}
else {
$a_folders = $this->conn->listMailboxes($this->mod_mailbox(''), $mbox_name);
-
- if (is_array($a_folders) && in_array($this->mod_mailbox($mbox_name), $a_folders))
+ }
+
+ if (is_array($a_folders) && in_array($this->mod_mailbox($mbox_name), $a_folders)) {
+ $this->icache[$key][] = $mbox_name;
return true;
}
}
@@ -2803,7 +2993,7 @@
else if (!empty($mbox_name)) // $mode=='out'
$mbox_name = substr($mbox_name, strlen($this->root_dir)+1);
}
-
+
return $mbox_name;
}
@@ -2832,7 +3022,7 @@
if (!count($this->cache) && $this->caching_enabled) {
return $this->_read_cache_record($key);
}
-
+
return $this->cache[$key];
}
@@ -2866,7 +3056,7 @@
{
if (!$this->caching_enabled)
return;
-
+
if ($key===NULL) {
foreach ($this->cache as $key => $data)
$this->_clear_cache_record($key);
@@ -2961,7 +3151,7 @@
"AND cache_key=?",
$_SESSION['user_id'],
'IMAP.'.$key);
-
+
unset($this->cache_keys[$key]);
}
@@ -2970,7 +3160,7 @@
/* --------------------------------
* message caching methods
* --------------------------------*/
-
+
/**
* Checks if the cache is up-to-date
*
@@ -2991,15 +3181,12 @@
if (!$msg_count)
return $cache_count ? -2 : 1;
- // @TODO: We've got one big performance problem in cache status checking method
- // E.g. mailbox contains 1000 messages, in cache table we've got first 100
- // of them. Now if we want to display only that 100 (which we've got)
- // check_cache_status returns 'incomplete' and messages are fetched
- // from IMAP instead of DB.
-
if ($cache_count==$msg_count) {
if ($this->skip_deleted) {
$h_index = $this->conn->fetchHeaderIndex($mailbox, "1:*", 'UID', $this->skip_deleted);
+
+ // Save index in internal cache, will be used when syncing the cache
+ $this->icache['folder_index'] = $h_index;
if (empty($h_index))
return -2;
@@ -3014,10 +3201,10 @@
}
return -2;
} else {
- // get UID of message with highest index
- $uid = $this->conn->ID2UID($mailbox, $msg_count);
+ // get UID of the message with highest index
+ $uid = $this->_id2uid($msg_count, $mailbox);
$cache_uid = array_pop($cache_index);
-
+
// uids of highest message matches -> cache seems OK
if ($cache_uid == $uid)
return 1;
@@ -3037,16 +3224,17 @@
*/
private function get_message_cache($key, $from, $to, $sort_field, $sort_order)
{
- $cache_key = "$key:$from:$to:$sort_field:$sort_order";
-
+ if (!$this->caching_enabled)
+ return NULL;
+
// use idx sort as default sorting
if (!$sort_field || !in_array($sort_field, $this->db_header_fields)) {
$sort_field = 'idx';
}
-
- if ($this->caching_enabled && !isset($this->cache[$cache_key])) {
- $this->cache[$cache_key] = array();
- $sql_result = $this->db->limitquery(
+
+ $result = array();
+
+ $sql_result = $this->db->limitquery(
"SELECT idx, uid, headers".
" FROM ".get_table_name('messages').
" WHERE user_id=?".
@@ -3057,18 +3245,17 @@
$_SESSION['user_id'],
$key);
- while ($sql_arr = $this->db->fetch_assoc($sql_result)) {
- $uid = $sql_arr['uid'];
- $this->cache[$cache_key][$uid] = $this->db->decode(unserialize($sql_arr['headers']));
+ while ($sql_arr = $this->db->fetch_assoc($sql_result)) {
+ $uid = intval($sql_arr['uid']);
+ $result[$uid] = $this->db->decode(unserialize($sql_arr['headers']));
- // featch headers if unserialize failed
- if (empty($this->cache[$cache_key][$uid]))
- $this->cache[$cache_key][$uid] = $this->conn->fetchHeader(
- preg_replace('/.msg$/', '', $key), $uid, true, $this->fetch_add_headers);
- }
+ // featch headers if unserialize failed
+ if (empty($result[$uid]))
+ $result[$uid] = $this->conn->fetchHeader(
+ preg_replace('/.msg$/', '', $key), $uid, true, false, $this->fetch_add_headers);
}
- return $this->cache[$cache_key];
+ return $result;
}
/**
@@ -3077,10 +3264,10 @@
private function &get_cached_message($key, $uid)
{
$internal_key = 'message';
-
+
if ($this->caching_enabled && !isset($this->icache[$internal_key][$uid])) {
$sql_result = $this->db->query(
- "SELECT idx, headers, structure".
+ "SELECT idx, headers, structure, message_id".
" FROM ".get_table_name('messages').
" WHERE user_id=?".
" AND cache_key=?".
@@ -3090,8 +3277,10 @@
$uid);
if ($sql_arr = $this->db->fetch_assoc($sql_result)) {
- $this->uid_id_map[preg_replace('/\.msg$/', '', $key)][$uid] = $sql_arr['idx'];
+ $this->icache['message.id'][$uid] = intval($sql_arr['message_id']);
+ $this->uid_id_map[preg_replace('/\.msg$/', '', $key)][$uid] = intval($sql_arr['idx']);
$this->icache[$internal_key][$uid] = $this->db->decode(unserialize($sql_arr['headers']));
+
if (is_object($this->icache[$internal_key][$uid]) && !empty($sql_arr['structure']))
$this->icache[$internal_key][$uid]->structure = $this->db->decode(unserialize($sql_arr['structure']));
}
@@ -3102,23 +3291,31 @@
/**
* @access private
- */
- private function get_message_cache_index($key, $force=false, $sort_field='idx', $sort_order='ASC')
+ */
+ private function get_message_cache_index($key, $sort_field='idx', $sort_order='ASC')
{
- static $sa_message_index = array();
-
- // empty key -> empty array
if (!$this->caching_enabled || empty($key))
- return array();
-
- if (!empty($sa_message_index[$key]) && !$force)
- return $sa_message_index[$key];
+ return NULL;
// use idx sort as default
if (!$sort_field || !in_array($sort_field, $this->db_header_fields))
$sort_field = 'idx';
-
- $sa_message_index[$key] = array();
+
+ $ord = $sort_field . $sort_order;
+
+ if (array_key_exists('index', $this->icache)
+ && $this->icache['index']['key'] == $key
+ && $this->icache['index']['ord'] == $ord
+ ) {
+ return $this->icache['index']['result'];
+ }
+
+ $this->icache['index'] = array(
+ 'result' => array(),
+ 'ord' => $ord,
+ 'key' => $key,
+ );
+
$sql_result = $this->db->query(
"SELECT idx, uid".
" FROM ".get_table_name('messages').
@@ -3129,29 +3326,35 @@
$key);
while ($sql_arr = $this->db->fetch_assoc($sql_result))
- $sa_message_index[$key][$sql_arr['idx']] = $sql_arr['uid'];
-
- return $sa_message_index[$key];
+ $this->icache['index']['result'][$sql_arr['idx']] = intval($sql_arr['uid']);
+
+ return $this->icache['index']['result'];
}
/**
* @access private
*/
- private function add_message_cache($key, $index, $headers, $struct=null, $force=false)
+ private function add_message_cache($key, $index, $headers, $struct=null, $force=false, $internal_cache=false)
{
if (empty($key) || !is_object($headers) || empty($headers->uid))
return;
// add to internal (fast) cache
- $this->icache['message'][$headers->uid] = clone $headers;
- $this->icache['message'][$headers->uid]->structure = $struct;
+ if ($internal_cache) {
+ $this->icache['message'][$headers->uid] = clone $headers;
+ $this->icache['message'][$headers->uid]->structure = $struct;
+ }
// no further caching
if (!$this->caching_enabled)
return;
-
- // check for an existing record (probly headers are cached but structure not)
- if (!$force) {
+
+ // known message id
+ if (is_int($force) && $force > 0) {
+ $message_id = $force;
+ }
+ // check for an existing record (probably headers are cached but structure not)
+ else if (!$force) {
$sql_result = $this->db->query(
"SELECT message_id".
" FROM ".get_table_name('messages').
@@ -3200,8 +3403,10 @@
is_object($struct) ? serialize($this->db->encode(clone $struct)) : NULL
);
}
+
+ unset($this->icache['index']);
}
-
+
/**
* @access private
*/
@@ -3209,7 +3414,7 @@
{
if (!$this->caching_enabled)
return;
-
+
$this->db->query(
"DELETE FROM ".get_table_name('messages').
" WHERE user_id=?".
@@ -3217,6 +3422,8 @@
" AND ".($idx ? "idx" : "uid")." IN (".$this->db->array2list($ids, 'integer').")",
$_SESSION['user_id'],
$key);
+
+ unset($this->icache['index']);
}
/**
@@ -3226,13 +3433,15 @@
{
if (!$this->caching_enabled)
return;
-
+
$this->db->query(
"DELETE FROM ".get_table_name('messages').
" WHERE user_id=?".
" AND cache_key=?".
" AND idx>=?",
$_SESSION['user_id'], $key, $start_index);
+
+ unset($this->icache['index']);
}
/**
@@ -3242,7 +3451,7 @@
{
if (!$this->caching_enabled)
return;
-
+
if (!empty($uids) && !is_array($uids)) {
if ($uids == '*' || $uids == '1:*')
$uids = NULL;
@@ -3262,7 +3471,7 @@
if ($sql_arr = $this->db->fetch_assoc($sql_result))
return $sql_arr['minidx'];
else
- return 0;
+ return 0;
}
@@ -3284,7 +3493,7 @@
$out = array();
// Special chars as defined by RFC 822 need to in quoted string (or escaped).
$special_chars = '[\(\)\<\>\\\.\[\]@,;:"]';
-
+
if (!is_array($a))
return $out;
@@ -3296,8 +3505,8 @@
$address = trim($val['address']);
$name = trim($val['name']);
- if (preg_match('/^[\'"]/', $name) && preg_match('/[\'"]$/', $name))
- $name = preg_replace(array('/^[\'"]/', '/[\'"]$/'), '', $name);
+ if ($name && preg_match('/^[\'"]/', $name) && preg_match('/[\'"]$/', $name))
+ $name = trim($name, '\'"');
if ($name && $address && $name != $address)
$string = sprintf('%s <%s>', preg_match("/$special_chars/", $name) ? '"'.addcslashes($name, '"').'"' : $name, $address);
@@ -3305,8 +3514,9 @@
$string = $address;
else if ($name)
$string = $name;
-
- $out[$j] = array('name' => $name,
+
+ $out[$j] = array(
+ 'name' => $name,
'mailto' => $address,
'string' => $string
);
@@ -3314,45 +3524,8 @@
if ($max && $j==$max)
break;
}
-
+
return $out;
- }
-
-
- /**
- * Decode a Microsoft Outlook TNEF part (winmail.dat)
- *
- * @param object rcube_message_part Message part to decode
- * @param string UID of the message
- * @return array List of rcube_message_parts extracted from windmail.dat
- */
- function tnef_decode(&$part, $uid)
- {
- if (!isset($part->body))
- $part->body = $this->get_message_part($uid, $part->mime_id, $part);
-
- require_once('lib/tnef_decoder.inc');
-
- $pid = 0;
- $tnef_parts = array();
- $tnef_arr = tnef_decode($part->body);
-
- foreach ($tnef_arr as $winatt) {
- $tpart = new rcube_message_part;
- $tpart->filename = $winatt["name"];
- $tpart->encoding = 'stream';
- $tpart->ctype_primary = $winatt["type0"];
- $tpart->ctype_secondary = $winatt["type1"];
- $tpart->mimetype = strtolower($winatt["type0"] . "/" . $winatt["type1"]);
- $tpart->mime_id = "winmail." . $part->mime_id . ".$pid";
- $tpart->size = $winatt["size"];
- $tpart->body = $winatt['stream'];
-
- $tnef_parts[] = $tpart;
- $pid++;
- }
-
- return $tnef_parts;
}
@@ -3366,9 +3539,9 @@
function decode_header($input, $remove_quotes=false)
{
$str = rcube_imap::decode_mime_string((string)$input, $this->default_charset);
- if ($str{0}=='"' && $remove_quotes)
+ if ($str[0] == '"' && $remove_quotes)
$str = str_replace('"', '', $str);
-
+
return $str;
}
@@ -3388,15 +3561,15 @@
$out = '';
// Iterate instead of recursing, this way if there are too many values we don't have stack overflows
- // rfc: all line breaks or other characters not found
+ // rfc: all line breaks or other characters not found
// in the Base64 Alphabet must be ignored by decoding software
- // delete all blanks between MIME-lines, differently we can
+ // delete all blanks between MIME-lines, differently we can
// receive unnecessary blanks and broken utf-8 symbols
$input = preg_replace("/\?=\s+=\?/", '?==?', $input);
// Check if there is stuff to decode
if (strpos($input, '=?') !== false) {
- // Loop through the string to decode all occurences of =? ?= into the variable $out
+ // Loop through the string to decode all occurences of =? ?= into the variable $out
while(($pos = strpos($input, '=?')) !== false) {
// Append everything that is before the text to be decoded
$out .= substr($input, 0, $pos);
@@ -3424,7 +3597,7 @@
}
// no encoding information, use fallback
- return rcube_charset_convert($input,
+ return rcube_charset_convert($input,
!empty($fallback) ? $fallback : rcmail::get_instance()->config->get('default_charset', 'ISO-8859-1'));
}
@@ -3454,7 +3627,7 @@
return rcube_charset_convert($rest, $a[0]);
}
- // we dont' know what to do with this
+ // we dont' know what to do with this
return $str;
}
@@ -3544,15 +3717,15 @@
ksort($a_defaults);
$folders = array_merge($a_defaults, array_keys($folders));
- // finally we must rebuild the list to move
+ // finally we must rebuild the list to move
// subfolders of default folders to their place...
// ...also do this for the rest of folders because
// asort() is not properly sorting case sensitive names
while (list($key, $folder) = each($folders)) {
- // set the type of folder name variable (#1485527)
+ // set the type of folder name variable (#1485527)
$a_out[] = (string) $folder;
unset($folders[$key]);
- $this->_rsort($folder, $delimiter, $folders, $a_out);
+ $this->_rsort($folder, $delimiter, $folders, $a_out);
}
return $a_out;
@@ -3566,13 +3739,13 @@
{
while (list($key, $name) = each($list)) {
if (strpos($name, $folder.$delimiter) === 0) {
- // set the type of folder name variable (#1485527)
+ // set the type of folder name variable (#1485527)
$out[] = (string) $name;
unset($list[$key]);
$this->_rsort($name, $delimiter, $list, $out);
}
}
- reset($list);
+ reset($list);
}
@@ -3583,7 +3756,7 @@
{
if (!$mbox_name)
$mbox_name = $this->mailbox;
-
+
if (!isset($this->uid_id_map[$mbox_name][$uid]))
$this->uid_id_map[$mbox_name][$uid] = $this->conn->UID2ID($mbox_name, $uid);
@@ -3603,7 +3776,7 @@
$uid = $this->conn->ID2UID($mbox_name, $id);
$this->uid_id_map[$mbox_name][$uid] = $id;
-
+
return $uid;
}
@@ -3658,20 +3831,20 @@
$mode = strtoupper($mode);
$a_mailbox_cache = $this->get_cache('messagecount');
-
+
if (!is_array($a_mailbox_cache[$mailbox]) || !isset($a_mailbox_cache[$mailbox][$mode]) || !is_numeric($increment))
return false;
-
+
// add incremental value to messagecount
$a_mailbox_cache[$mailbox][$mode] += $increment;
-
+
// there's something wrong, delete from cache
if ($a_mailbox_cache[$mailbox][$mode] < 0)
unset($a_mailbox_cache[$mailbox][$mode]);
// write back to cache
$this->update_cache('messagecount', $a_mailbox_cache);
-
+
return true;
}
@@ -3713,7 +3886,7 @@
$a_headers[$field] = $value;
}
}
-
+
return $a_headers;
}
@@ -3739,13 +3912,13 @@
else
$result[$key]['name'] .= (empty($result[$key]['name'])?'':' ').str_replace("\"",'',stripslashes($v));
}
-
- if (empty($result[$key]['name']))
- $result[$key]['name'] = $result[$key]['address'];
- elseif (empty($result[$key]['address']))
+
+// if (empty($result[$key]['name']))
+// $result[$key]['name'] = $result[$key]['address'];
+ if (empty($result[$key]['address']))
$result[$key]['address'] = $result[$key]['name'];
}
-
+
return $result;
}
@@ -3791,7 +3964,7 @@
class rcube_header_sorter
{
var $sequence_numbers = array();
-
+
/**
* Set the predetermined sort order.
*
@@ -3813,12 +3986,12 @@
* uksort would work if the keys were the sequence number, but unfortunately
* the keys are the UIDs. We'll use uasort instead and dereference the value
* to get the sequence number (in the "id" field).
- *
- * uksort($headers, array($this, "compare_seqnums"));
+ *
+ * uksort($headers, array($this, "compare_seqnums"));
*/
uasort($headers, array($this, "compare_seqnums"));
}
-
+
/**
* Sort method called by uasort()
*/
@@ -3827,11 +4000,11 @@
// First get the sequence number from the header object (the 'id' field).
$seqa = $a->id;
$seqb = $b->id;
-
+
// then find each sequence number in my ordered list
$posa = isset($this->sequence_numbers[$seqa]) ? intval($this->sequence_numbers[$seqa]) : -1;
$posb = isset($this->sequence_numbers[$seqb]) ? intval($this->sequence_numbers[$seqb]) : -1;
-
+
// return the relative position as the comparison value
return $posa - $posb;
}
--
Gitblit v1.9.1