From 2965a981b7ec22866fbdf2d567d87e2d068d3617 Mon Sep 17 00:00:00 2001
From: Thomas Bruederli <thomas@roundcube.net>
Date: Fri, 31 Jul 2015 16:04:08 -0400
Subject: [PATCH] Allow to search and import missing PGP pubkeys from keyservers using Publickey.js
---
program/lib/Roundcube/rcube_imap.php | 636 +++++++++++++++++++++++++++++++++++++++++++++------------
1 files changed, 496 insertions(+), 140 deletions(-)
diff --git a/program/lib/Roundcube/rcube_imap.php b/program/lib/Roundcube/rcube_imap.php
index fdda1d4..65e0950 100644
--- a/program/lib/Roundcube/rcube_imap.php
+++ b/program/lib/Roundcube/rcube_imap.php
@@ -56,6 +56,7 @@
*/
protected $icache = array();
+ protected $plugins;
protected $list_page = 1;
protected $delimiter;
protected $namespace;
@@ -82,6 +83,7 @@
public function __construct()
{
$this->conn = new rcube_imap_generic();
+ $this->plugins = rcube::get_instance()->plugins;
// Set namespace and delimiter from session,
// so some methods would work before connection
@@ -110,13 +112,13 @@
/**
* Connect to an IMAP server
*
- * @param string $host Host to connect
- * @param string $user Username for IMAP account
- * @param string $pass Password for IMAP account
- * @param integer $port Port to connect to
- * @param string $use_ssl SSL schema (either ssl or tls) or null if plain connection
+ * @param string $host Host to connect
+ * @param string $user Username for IMAP account
+ * @param string $pass Password for IMAP account
+ * @param integer $port Port to connect to
+ * @param string $use_ssl SSL schema (either ssl or tls) or null if plain connection
*
- * @return boolean TRUE on success, FALSE on failure
+ * @return boolean True on success, False on failure
*/
public function connect($host, $user, $pass, $port=143, $use_ssl=null)
{
@@ -147,7 +149,7 @@
$attempt = 0;
do {
- $data = rcube::get_instance()->plugins->exec_hook('storage_connect',
+ $data = $this->plugins->exec_hook('storage_connect',
array_merge($this->options, array('host' => $host, 'user' => $user,
'attempt' => ++$attempt)));
@@ -170,8 +172,20 @@
$this->connect_done = true;
if ($this->conn->connected()) {
+ // check for session identifier
+ $session = null;
+ if (preg_match('/\s+SESSIONID=([^=\s]+)/', $this->conn->result, $m)) {
+ $session = $m[1];
+ }
+
// get namespace and delimiter
$this->set_env();
+
+ // trigger post-connect hook
+ $this->plugins->exec_hook('storage_connected', array(
+ 'host' => $host, 'user' => $user, 'session' => $session
+ ));
+
return true;
}
// write error log
@@ -332,6 +346,10 @@
$this->search_sort_field = $set[3];
$this->search_sorted = $set[4];
$this->search_threads = is_a($this->search_set, 'rcube_result_thread');
+
+ if (is_a($this->search_set, 'rcube_result_multifolder')) {
+ $this->set_threading(false);
+ }
}
@@ -680,6 +698,41 @@
/**
+ * Public method for listing message flags
+ *
+ * @param string $folder Folder name
+ * @param array $uids Message UIDs
+ * @param int $mod_seq Optional MODSEQ value (of last flag update)
+ *
+ * @return array Indexed array with message flags
+ */
+ public function list_flags($folder, $uids, $mod_seq = null)
+ {
+ if (!strlen($folder)) {
+ $folder = $this->folder;
+ }
+
+ if (!$this->check_connection()) {
+ return array();
+ }
+
+ // @TODO: when cache was synchronized in this request
+ // we might already have asked for flag updates, use it.
+
+ $flags = $this->conn->fetch($folder, $uids, true, array('FLAGS'), $mod_seq);
+ $result = array();
+
+ if (!empty($flags)) {
+ foreach ($flags as $message) {
+ $result[$message->uid] = $message->flags;
+ }
+ }
+
+ return $result;
+ }
+
+
+ /**
* Public method for listing headers
*
* @param string $folder Folder name
@@ -722,7 +775,7 @@
$page = $page ? $page : $this->list_page;
// use saved message set
- if ($this->search_string && $folder == $this->folder) {
+ if ($this->search_string) {
return $this->list_search_messages($folder, $page, $slice);
}
@@ -910,6 +963,75 @@
return array();
}
+ // gather messages from a multi-folder search
+ if ($this->search_set->multi) {
+ $page_size = $this->page_size;
+ $sort_field = $this->sort_field;
+ $search_set = $this->search_set;
+
+ // prepare paging
+ $cnt = $search_set->count();
+ $from = ($page-1) * $page_size;
+ $to = $from + $page_size;
+ $slice_length = min($page_size, $cnt - $from);
+
+ // fetch resultset headers, sort and slice them
+ if (!empty($sort_field)) {
+ $this->sort_field = null;
+ $this->page_size = 1000; // fetch up to 1000 matching messages per folder
+ $this->threading = false;
+
+ $a_msg_headers = array();
+ foreach ($search_set->sets as $resultset) {
+ if (!$resultset->is_empty()) {
+ $this->search_set = $resultset;
+ $this->search_threads = $resultset instanceof rcube_result_thread;
+ $a_msg_headers = array_merge($a_msg_headers, $this->list_search_messages($resultset->get_parameters('MAILBOX'), 1));
+ }
+ }
+
+ // sort headers
+ if (!empty($a_msg_headers)) {
+ $a_msg_headers = $this->conn->sortHeaders($a_msg_headers, $sort_field, $this->sort_order);
+ }
+
+ // store (sorted) message index
+ $search_set->set_message_index($a_msg_headers, $sort_field, $this->sort_order);
+
+ // only return the requested part of the set
+ $a_msg_headers = array_slice(array_values($a_msg_headers), $from, $slice_length);
+ }
+ else {
+ if ($this->sort_order != $search_set->get_parameters('ORDER')) {
+ $search_set->revert();
+ }
+
+ // slice resultset first...
+ $fetch = array();
+ foreach (array_slice($search_set->get(), $from, $slice_length) as $msg_id) {
+ list($uid, $folder) = explode('-', $msg_id, 2);
+ $fetch[$folder][] = $uid;
+ }
+
+ // ... and fetch the requested set of headers
+ $a_msg_headers = array();
+ foreach ($fetch as $folder => $a_index) {
+ $a_msg_headers = array_merge($a_msg_headers, array_values($this->fetch_headers($folder, $a_index)));
+ }
+ }
+
+ if ($slice) {
+ $a_msg_headers = array_slice($a_msg_headers, -$slice, $slice);
+ }
+
+ // restore members
+ $this->sort_field = $sort_field;
+ $this->page_size = $page_size;
+ $this->search_set = $search_set;
+
+ return $a_msg_headers;
+ }
+
// use saved messages from searching
if ($this->threading) {
return $this->list_search_thread_messages($folder, $page, $slice);
@@ -1076,6 +1198,7 @@
}
foreach ($headers as $h) {
+ $h->folder = $folder;
$a_msg_headers[$h->uid] = $h;
}
@@ -1199,8 +1322,13 @@
return new rcube_result_index($folder, '* SORT');
}
+ if ($this->search_set instanceof rcube_result_multifolder) {
+ $index = $this->search_set;
+ $index->folder = $folder;
+ // TODO: handle changed sorting
+ }
// search result is an index with the same sorting?
- if (($this->search_set instanceof rcube_result_index)
+ else if (($this->search_set instanceof rcube_result_index)
&& ((!$this->sort_field && !$this->search_sorted) ||
($this->search_sorted && $this->search_sort_field == $this->sort_field))
) {
@@ -1256,7 +1384,7 @@
public function index_direct($folder, $sort_field = null, $sort_order = null, $search = null)
{
if (!empty($search)) {
- $search = $this->search_set->get_compressed();
+ $search = $search->get_compressed();
}
// use message index sort as default sorting
@@ -1375,26 +1503,75 @@
* Invoke search request to IMAP server
*
* @param string $folder Folder name to search in
- * @param string $str Search criteria
+ * @param string $search Search criteria
* @param string $charset Search charset
* @param string $sort_field Header field to sort by
*
+ * @return rcube_result_index Search result object
* @todo: Search criteria should be provided in non-IMAP format, eg. array
*/
- public function search($folder='', $str='ALL', $charset=NULL, $sort_field=NULL)
+ public function search($folder = '', $search = 'ALL', $charset = null, $sort_field = null)
{
- if (!$str) {
- $str = 'ALL';
+ if (!$search) {
+ $search = 'ALL';
}
- if (!strlen($folder)) {
+ if ((is_array($folder) && empty($folder)) || (!is_array($folder) && !strlen($folder))) {
$folder = $this->folder;
}
- $results = $this->search_index($folder, $str, $charset, $sort_field);
+ $plugin = $this->plugins->exec_hook('imap_search_before', array(
+ 'folder' => $folder,
+ 'search' => $search,
+ 'charset' => $charset,
+ 'sort_field' => $sort_field,
+ 'threading' => $this->threading,
+ ));
- $this->set_search_set(array($str, $results, $charset, $sort_field,
- $this->threading || $this->search_sorted ? true : false));
+ $folder = $plugin['folder'];
+ $search = $plugin['search'];
+ $charset = $plugin['charset'];
+ $sort_field = $plugin['sort_field'];
+ $results = $plugin['result'];
+
+ // multi-folder search
+ if (!$results && is_array($folder) && count($folder) > 1 && $search != 'ALL') {
+ // connect IMAP to have all the required classes and settings loaded
+ $this->check_connection();
+
+ // disable threading
+ $this->threading = false;
+
+ $searcher = new rcube_imap_search($this->options, $this->conn);
+
+ // set limit to not exceed the client's request timeout
+ $searcher->set_timelimit(60);
+
+ // continue existing incomplete search
+ if (!empty($this->search_set) && $this->search_set->incomplete && $search == $this->search_string) {
+ $searcher->set_results($this->search_set);
+ }
+
+ // execute the search
+ $results = $searcher->exec(
+ $folder,
+ $search,
+ $charset ? $charset : $this->default_charset,
+ $sort_field && $this->get_capability('SORT') ? $sort_field : null,
+ $this->threading
+ );
+ }
+ else if (!$results) {
+ $folder = is_array($folder) ? $folder[0] : $folder;
+ $search = is_array($search) ? $search[$folder] : $search;
+ $results = $this->search_index($folder, $search, $charset, $sort_field);
+ }
+
+ $sorted = $this->threading || $this->search_sorted || $plugin['search_sorted'] ? true : false;
+
+ $this->set_search_set(array($search, $results, $charset, $sort_field, $sorted));
+
+ return $results;
}
@@ -1408,19 +1585,26 @@
*/
public function search_once($folder = null, $str = 'ALL')
{
- if (!$str) {
- return 'ALL';
- }
-
- if (!strlen($folder)) {
- $folder = $this->folder;
- }
-
if (!$this->check_connection()) {
return new rcube_result_index();
}
- $index = $this->conn->search($folder, $str, true);
+ if (!$str) {
+ $str = 'ALL';
+ }
+
+ // multi-folder search
+ if (is_array($folder) && count($folder) > 1) {
+ $searcher = new rcube_imap_search($this->options, $this->conn);
+ $index = $searcher->exec($folder, $str, $this->default_charset);
+ }
+ else {
+ $folder = is_array($folder) ? $folder[0] : $folder;
+ if (!strlen($folder)) {
+ $folder = $this->folder;
+ }
+ $index = $this->conn->search($folder, $str, true);
+ }
return $index;
}
@@ -1465,7 +1649,7 @@
// but I've seen that Courier doesn't support UTF-8)
if ($threads->is_error() && $charset && $charset != 'US-ASCII') {
$threads = $this->conn->thread($folder, $this->threading,
- $this->convert_criteria($criteria, $charset), true, 'US-ASCII');
+ self::convert_criteria($criteria, $charset), true, 'US-ASCII');
}
return $threads;
@@ -1479,7 +1663,7 @@
// but I've seen Courier with disabled UTF-8 support)
if ($messages->is_error() && $charset && $charset != 'US-ASCII') {
$messages = $this->conn->sort($folder, $sort_field,
- $this->convert_criteria($criteria, $charset), true, 'US-ASCII');
+ self::convert_criteria($criteria, $charset), true, 'US-ASCII');
}
if (!$messages->is_error()) {
@@ -1494,7 +1678,7 @@
// Error, try with US-ASCII (some servers may support only US-ASCII)
if ($messages->is_error() && $charset && $charset != 'US-ASCII') {
$messages = $this->conn->search($folder,
- $this->convert_criteria($criteria, $charset), true);
+ self::convert_criteria($criteria, $charset), true);
}
$this->search_sorted = false;
@@ -1512,7 +1696,7 @@
*
* @return string Search string
*/
- protected function convert_criteria($str, $charset, $dest_charset='US-ASCII')
+ public static 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)) {
@@ -1521,12 +1705,15 @@
$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 === false) {
+
+ if ($string === false || !strlen($string)) {
continue;
}
+
$res .= substr($str, $last, $m[1] - $last - 1) . rcube_imap_generic::escape($string);
$last = $m[0] + $string_offset - 1;
}
+
if ($last < strlen($str)) {
$res .= substr($str, $last, strlen($str)-$last);
}
@@ -1548,10 +1735,28 @@
public function refresh_search()
{
if (!empty($this->search_string)) {
- $this->search('', $this->search_string, $this->search_charset, $this->search_sort_field);
+ $this->search(
+ is_object($this->search_set) ? $this->search_set->get_parameters('MAILBOX') : '',
+ $this->search_string,
+ $this->search_charset,
+ $this->search_sort_field
+ );
}
return $this->get_search_set();
+ }
+
+ /**
+ * Flag certain result subsets as 'incomplete'.
+ * For subsequent refresh_search() calls to only refresh the updated parts.
+ */
+ protected function set_search_dirty($folder)
+ {
+ if ($this->search_set && is_a($this->search_set, 'rcube_result_multifolder')) {
+ if ($subset = $this->search_set->get_set($folder)) {
+ $subset->incomplete = $this->search_set->incomplete = true;
+ }
+ }
}
@@ -1566,6 +1771,11 @@
*/
public function get_message_headers($uid, $folder = null, $force = false)
{
+ // decode combined UID-folder identifier
+ if (preg_match('/^\d+-.+/', $uid)) {
+ list($uid, $folder) = explode('-', $uid, 2);
+ }
+
if (!strlen($folder)) {
$folder = $this->folder;
}
@@ -1580,6 +1790,9 @@
else {
$headers = $this->conn->fetchHeader(
$folder, $uid, true, true, $this->get_fetch_headers());
+
+ if (is_object($headers))
+ $headers->folder = $folder;
}
return $headers;
@@ -1599,6 +1812,11 @@
{
if (!strlen($folder)) {
$folder = $this->folder;
+ }
+
+ // decode combined UID-folder identifier
+ if (preg_match('/^\d+-.+/', $uid)) {
+ list($uid, $folder) = explode('-', $uid, 2);
}
// Check internal cache
@@ -1644,7 +1862,7 @@
$this->struct_charset = $this->structure_charset($structure);
}
- $headers->ctype = strtolower($headers->ctype);
+ $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
@@ -1739,6 +1957,16 @@
for ($i=1; $i<count($part); $i++) {
if (!is_array($part[$i])) {
$struct->ctype_secondary = strtolower($part[$i]);
+
+ // read content type parameters
+ if (is_array($part[$i+1])) {
+ $struct->ctype_parameters = array();
+ for ($j=0; $j<count($part[$i+1]); $j+=2) {
+ $param = strtolower($part[$i+1][$j]);
+ $struct->ctype_parameters[$param] = $part[$i+1][$j+1];
+ }
+ }
+
break;
}
}
@@ -2121,7 +2349,7 @@
// convert charset (if text or message part)
if ($body && preg_match('/^(text|message)$/', $o_part->ctype_primary)) {
// Remove NULL characters if any (#1486189)
- if (strpos($body, "\x00") !== false) {
+ if ($formatted && strpos($body, "\x00") !== false) {
$body = str_replace("\x00", '', $body);
}
@@ -2146,36 +2374,38 @@
/**
* Returns the whole message source as string (or saves to a file)
*
- * @param int $uid Message UID
- * @param resource $fp File pointer to save the message
+ * @param int $uid Message UID
+ * @param resource $fp File pointer to save the message
+ * @param string $part Optional message part ID
*
* @return string Message source string
*/
- public function get_raw_body($uid, $fp=null)
+ public function get_raw_body($uid, $fp=null, $part = null)
{
if (!$this->check_connection()) {
return null;
}
return $this->conn->handlePartBody($this->folder, $uid,
- true, null, null, false, $fp);
+ true, $part, null, false, $fp);
}
/**
* Returns the message headers as string
*
- * @param int $uid Message UID
+ * @param int $uid Message UID
+ * @param string $part Optional message part ID
*
* @return string Message headers string
*/
- public function get_raw_headers($uid)
+ public function get_raw_headers($uid, $part = null)
{
if (!$this->check_connection()) {
return null;
}
- return $this->conn->fetchPartHeader($this->folder, $uid, true);
+ return $this->conn->fetchPartHeader($this->folder, $uid, true, $part);
}
@@ -2247,6 +2477,8 @@
$this->clear_message_cache($folder, $all_mode ? null : explode(',', $uids));
}
}
+
+ $this->set_search_dirty($folder);
}
return $result;
@@ -2294,6 +2526,17 @@
if ($saved) {
// increase messagecount of the target folder
$this->set_messagecount($folder, 'ALL', 1);
+
+ $this->plugins->exec_hook('message_saved', array(
+ 'folder' => $folder,
+ 'message' => $message,
+ 'headers' => $headers,
+ 'is_file' => $is_file,
+ 'flags' => $flags,
+ 'date' => $date,
+ 'binary' => $binary,
+ 'result' => $saved,
+ ));
}
return $saved;
@@ -2330,19 +2573,7 @@
return false;
}
- // make sure folder exists
- if ($to_mbox != 'INBOX' && !$this->folder_exists($to_mbox)) {
- if (in_array($to_mbox, $this->default_folders)) {
- if (!$this->create_folder($to_mbox, true)) {
- return false;
- }
- }
- else {
- return false;
- }
- }
-
- $config = rcube::get_instance()->config;
+ $config = rcube::get_instance()->config;
$to_trash = $to_mbox == $config->get('trash_mbox');
// flag messages as read before moving them
@@ -2357,6 +2588,9 @@
if ($moved) {
$this->clear_messagecount($from_mbox);
$this->clear_messagecount($to_mbox);
+
+ $this->set_search_dirty($from_mbox);
+ $this->set_search_dirty($to_mbox);
}
// moving failed
else if ($to_trash && $config->get('delete_always', false)) {
@@ -2373,8 +2607,8 @@
if ($this->search_threads || $all_mode) {
$this->refresh_search();
}
- else {
- $this->search_set->filter(explode(',', $uids));
+ else if (!$this->search_set->incomplete) {
+ $this->search_set->filter(explode(',', $uids), $this->folder);
}
}
@@ -2411,18 +2645,6 @@
if (!$this->check_connection()) {
return false;
- }
-
- // make sure folder exists
- if ($to_mbox != 'INBOX' && !$this->folder_exists($to_mbox)) {
- if (in_array($to_mbox, $this->default_folders)) {
- if (!$this->create_folder($to_mbox, true)) {
- return false;
- }
- }
- else {
- return false;
- }
}
// copy messages
@@ -2473,13 +2695,15 @@
// unset threads internal cache
unset($this->icache['threads']);
+ $this->set_search_dirty($folder);
+
// remove message ids from search set
if ($this->search_set && $folder == $this->folder) {
// threads are too complicated to just remove messages from set
if ($this->search_threads || $all_mode) {
$this->refresh_search();
}
- else {
+ else if (!$this->search_set->incomplete) {
$this->search_set->filter(explode(',', $uids));
}
}
@@ -2579,7 +2803,7 @@
}
// Give plugins a chance to provide a list of folders
- $data = rcube::get_instance()->plugins->exec_hook('storage_folders',
+ $data = $this->plugins->exec_hook('storage_folders',
array('root' => $root, 'name' => $name, 'filter' => $filter, 'mode' => 'LSUB'));
if (isset($data['folders'])) {
@@ -2711,7 +2935,7 @@
}
// Give plugins a chance to provide a list of folders
- $data = rcube::get_instance()->plugins->exec_hook('storage_folders',
+ $data = $this->plugins->exec_hook('storage_folders',
array('root' => $root, 'name' => $name, 'filter' => $filter, 'mode' => 'LIST'));
if (isset($data['folders'])) {
@@ -2792,7 +3016,7 @@
* @param array $result Reference to folders list
* @param string $type Listing type (ext-subscribed, subscribed or all)
*/
- private function list_folders_update(&$result, $type = null)
+ protected function list_folders_update(&$result, $type = null)
{
$namespace = $this->get_namespace();
$search = array();
@@ -2843,12 +3067,21 @@
/**
* Filter the given list of folders according to access rights
+ *
+ * For performance reasons we assume user has full rights
+ * on all personal folders.
*/
protected function filter_rights($a_folders, $rights)
{
$regex = '/('.$rights.')/';
+
foreach ($a_folders as $idx => $folder) {
+ if ($this->folder_namespace($folder) == 'personal') {
+ continue;
+ }
+
$myrights = join('', (array)$this->my_rights($folder));
+
if ($myrights !== null && !preg_match($regex, $myrights)) {
unset($a_folders[$idx]);
}
@@ -2860,14 +3093,15 @@
/**
* Get mailbox quota information
- * added by Nuny
+ *
+ * @param string $folder Folder name
*
* @return mixed Quota info or False if not supported
*/
- public function get_quota()
+ public function get_quota($folder = null)
{
if ($this->get_capability('QUOTA') && $this->check_connection()) {
- return $this->conn->getQuota();
+ return $this->conn->getQuota($folder);
}
return false;
@@ -2931,16 +3165,27 @@
*
* @param string $folder New folder name
* @param boolean $subscribe True if the new folder should be subscribed
+ * @param string $type Optional folder type (junk, trash, drafts, sent, archive)
*
* @return boolean True on success
*/
- public function create_folder($folder, $subscribe=false)
+ public function create_folder($folder, $subscribe = false, $type = null)
{
if (!$this->check_connection()) {
return false;
}
- $result = $this->conn->createFolder($folder);
+ $result = $this->conn->createFolder($folder, $type ? array("\\" . ucfirst($type)) : null);
+
+ // it's quite often situation that we're trying to create and subscribe
+ // a folder that already exist, but is unsubscribed
+ if (!$result) {
+ if ($this->get_response_code() == rcube_storage::ALREADYEXISTS
+ || preg_match('/already exists/i', $this->get_error_str())
+ ) {
+ $result = true;
+ }
+ }
// try to subscribe it
if ($result) {
@@ -3065,19 +3310,86 @@
/**
- * Create all folders specified as default
+ * Detect special folder associations stored in storage backend
*/
- public function create_default_folders()
+ public function get_special_folders($forced = false)
{
- // create default folders if they do not exist
- foreach ($this->default_folders as $folder) {
- if (!$this->folder_exists($folder)) {
- $this->create_folder($folder, true);
- }
- else if (!$this->folder_exists($folder, true)) {
- $this->subscribe($folder);
+ $result = parent::get_special_folders();
+
+ if (isset($this->icache['special-use'])) {
+ return array_merge($result, $this->icache['special-use']);
+ }
+
+ if (!$forced || !$this->get_capability('SPECIAL-USE')) {
+ return $result;
+ }
+
+ if (!$this->check_connection()) {
+ return $result;
+ }
+
+ $types = array_map(function($value) { return "\\" . ucfirst($value); }, rcube_storage::$folder_types);
+ $special = array();
+
+ // request \Subscribed flag in LIST response as performance improvement for folder_exists()
+ $folders = $this->conn->listMailboxes('', '*', array('SUBSCRIBED'), array('SPECIAL-USE'));
+
+ if (!empty($folders)) {
+ foreach ($folders as $folder) {
+ if ($flags = $this->conn->data['LIST'][$folder]) {
+ foreach ($types as $type) {
+ if (in_array($type, $flags)) {
+ $type = strtolower(substr($type, 1));
+ $special[$type] = $folder;
+ }
+ }
+ }
}
}
+
+ $this->icache['special-use'] = $special;
+ unset($this->icache['special-folders']);
+
+ return array_merge($result, $special);
+ }
+
+
+ /**
+ * Set special folder associations stored in storage backend
+ */
+ public function set_special_folders($specials)
+ {
+ if (!$this->get_capability('SPECIAL-USE') || !$this->get_capability('METADATA')) {
+ return false;
+ }
+
+ if (!$this->check_connection()) {
+ return false;
+ }
+
+ $folders = $this->get_special_folders(true);
+ $old = (array) $this->icache['special-use'];
+
+ foreach ($specials as $type => $folder) {
+ if (in_array($type, rcube_storage::$folder_types)) {
+ $old_folder = $old[$type];
+ if ($old_folder !== $folder) {
+ // unset old-folder metadata
+ if ($old_folder !== null) {
+ $this->delete_metadata($old_folder, array('/private/specialuse'));
+ }
+ // set new folder metadata
+ if ($folder) {
+ $this->set_metadata($folder, array('/private/specialuse' => "\\" . ucfirst($type)));
+ }
+ }
+ }
+ }
+
+ $this->icache['special-use'] = $specials;
+ unset($this->icache['special-folders']);
+
+ return true;
}
@@ -3089,13 +3401,13 @@
*
* @return boolean TRUE or FALSE
*/
- public function folder_exists($folder, $subscription=false)
+ public function folder_exists($folder, $subscription = false)
{
if ($folder == 'INBOX') {
return true;
}
- $key = $subscription ? 'subscribed' : 'existing';
+ $key = $subscription ? 'subscribed' : 'existing';
if (is_array($this->icache[$key]) && in_array($folder, $this->icache[$key])) {
return true;
@@ -3106,10 +3418,24 @@
}
if ($subscription) {
- $a_folders = $this->conn->listSubscribed('', $folder);
+ // It's possible we already called LIST command, check LIST data
+ if (!empty($this->conn->data['LIST']) && !empty($this->conn->data['LIST'][$folder])
+ && in_array('\\Subscribed', $this->conn->data['LIST'][$folder])
+ ) {
+ $a_folders = array($folder);
+ }
+ else {
+ $a_folders = $this->conn->listSubscribed('', $folder);
+ }
}
else {
- $a_folders = $this->conn->listMailboxes('', $folder);
+ // It's possible we already called LIST command, check LIST data
+ if (!empty($this->conn->data['LIST']) && isset($this->conn->data['LIST'][$folder])) {
+ $a_folders = array($folder);
+ }
+ else {
+ $a_folders = $this->conn->listMailboxes('', $folder);
+ }
}
if (is_array($a_folders) && in_array($folder, $a_folders)) {
@@ -3320,7 +3646,7 @@
$options['name'] = $folder;
$options['attributes'] = $this->folder_attributes($folder, true);
$options['namespace'] = $this->folder_namespace($folder);
- $options['special'] = in_array($folder, $this->default_folders);
+ $options['special'] = $this->is_special_folder($folder);
// Set 'noselect' flag
if (is_array($options['attributes'])) {
@@ -3637,8 +3963,16 @@
// @TODO: Honor MAXSIZE and DEPTH options
foreach ($queries as $attrib => $entry) {
- if ($result = $this->conn->getAnnotation($folder, $entry, $attrib)) {
- $res = array_merge_recursive($res, $result);
+ $result = $this->conn->getAnnotation($folder, $entry, $attrib);
+
+ // an error, invalidate any previous getAnnotation() results
+ if (!is_array($result)) {
+ return null;
+ }
+ else {
+ foreach ($result as $fldr => $data) {
+ $res[$fldr] = array_merge((array) $res[$fldr], $data);
+ }
}
}
}
@@ -3855,58 +4189,80 @@
*/
public function sort_folder_list($a_folders, $skip_default = false)
{
- $a_out = $a_defaults = $folders = array();
+ $specials = array_merge(array('INBOX'), array_values($this->get_special_folders()));
+ $folders = array();
- $delimiter = $this->get_hierarchy_delimiter();
-
- // find default folders and skip folders starting with '.'
+ // convert names to UTF-8 and skip folders starting with '.'
foreach ($a_folders as $folder) {
- if ($folder[0] == '.') {
+ if ($folder[0] != '.') {
+ // for better performance skip encoding conversion
+ // if the string does not look like UTF7-IMAP
+ $folders[$folder] = strpos($folder, '&') === false ? $folder : rcube_charset::convert($folder, 'UTF7-IMAP');
+ }
+ }
+
+ // sort folders
+ // asort($folders, SORT_LOCALE_STRING) is not properly sorting case sensitive names
+ uasort($folders, array($this, 'sort_folder_comparator'));
+
+ $folders = array_keys($folders);
+
+ if ($skip_default) {
+ return $folders;
+ }
+
+ // force the type of folder name variable (#1485527)
+ $folders = array_map('strval', $folders);
+ $out = array();
+
+ // finally we must put special folders on top and rebuild the list
+ // to move their subfolders where they belong...
+ $specials = array_unique(array_intersect($specials, $folders));
+ $folders = array_merge($specials, array_diff($folders, $specials));
+
+ $this->sort_folder_specials(null, $folders, $specials, $out);
+
+ return $out;
+ }
+
+ /**
+ * Recursive function to put subfolders of special folders in place
+ */
+ protected function sort_folder_specials($folder, &$list, &$specials, &$out)
+ {
+ while (list($key, $name) = each($list)) {
+ if ($folder === null || strpos($name, $folder.$this->delimiter) === 0) {
+ $out[] = $name;
+ unset($list[$key]);
+
+ if (!empty($specials) && ($found = array_search($name, $specials)) !== false) {
+ unset($specials[$found]);
+ $this->sort_folder_specials($name, $list, $specials, $out);
+ }
+ }
+ }
+
+ reset($list);
+ }
+
+ /**
+ * Callback for uasort() that implements correct
+ * locale-aware case-sensitive sorting
+ */
+ protected function sort_folder_comparator($str1, $str2)
+ {
+ $path1 = explode($this->delimiter, $str1);
+ $path2 = explode($this->delimiter, $str2);
+
+ foreach ($path1 as $idx => $folder1) {
+ $folder2 = $path2[$idx];
+
+ if ($folder1 === $folder2) {
continue;
}
- if (!$skip_default && ($p = array_search($folder, $this->default_folders)) !== false && !$a_defaults[$p]) {
- $a_defaults[$p] = $folder;
- }
- else {
- $folders[$folder] = rcube_charset::convert($folder, 'UTF7-IMAP');
- }
+ return strcoll($folder1, $folder2);
}
-
- // sort folders and place defaults on the top
- asort($folders, SORT_LOCALE_STRING);
- ksort($a_defaults);
- $folders = array_merge($a_defaults, array_keys($folders));
-
- // 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)
- $a_out[] = (string) $folder;
- unset($folders[$key]);
- $this->rsort($folder, $delimiter, $folders, $a_out);
- }
-
- return $a_out;
- }
-
-
- /**
- * Recursive method for sorting folders
- */
- protected function rsort($folder, $delimiter, &$list, &$out)
- {
- while (list($key, $name) = each($list)) {
- if (strpos($name, $folder.$delimiter) === 0) {
- // set the type of folder name variable (#1485527)
- $out[] = (string) $name;
- unset($list[$key]);
- $this->rsort($name, $delimiter, $list, $out);
- }
- }
- reset($list);
}
--
Gitblit v1.9.1