From 3412e50b54e3daac8745234e21ab6e72be0ed165 Mon Sep 17 00:00:00 2001 From: Thomas Bruederli <thomas@roundcube.net> Date: Wed, 04 Jun 2014 11:20:33 -0400 Subject: [PATCH] Fix attachment menu structure and aria-attributes --- program/lib/Roundcube/rcube_imap.php | 316 +++++++++++++++++++++++++++++++++++++-------------- 1 files changed, 227 insertions(+), 89 deletions(-) diff --git a/program/lib/Roundcube/rcube_imap.php b/program/lib/Roundcube/rcube_imap.php index 794f25a..4204354 100644 --- a/program/lib/Roundcube/rcube_imap.php +++ b/program/lib/Roundcube/rcube_imap.php @@ -332,6 +332,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); + } } @@ -951,35 +955,56 @@ $sort_field = $this->sort_field; $search_set = $this->search_set; - $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)); - } - } - - // do sorting and paging + // prepare paging $cnt = $search_set->count(); $from = ($page-1) * $page_size; $to = $from + $page_size; + $slice_length = min($page_size, $cnt - $from); - // sort headers - if (!$this->threading && !empty($a_msg_headers)) { - $a_msg_headers = $this->conn->sortHeaders($a_msg_headers, $sort_field, $this->sort_order); + // 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(); + } - // store (sorted) message index - $search_set->set_message_index($a_msg_headers, $sort_field, $this->sort_order); + // 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; + } - // only return the requested part of the set - $slice_length = min($page_size, $cnt - $from); - $a_msg_headers = array_slice(array_values($a_msg_headers), $from, $slice_length); + // ... 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); @@ -1467,17 +1492,13 @@ * @param string $str 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) { if (!$str) { $str = 'ALL'; - } - - if (empty($folder)) { - $folder = $this->folder; } // multi-folder search @@ -1492,6 +1513,16 @@ $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 && $str == $this->search_string) { + $searcher->set_results($this->search_set); + } + + // execute the search $results = $searcher->exec( $folder, $str, @@ -1502,11 +1533,16 @@ } else { $folder = is_array($folder) ? $folder[0] : $folder; + if (!strlen($folder)) { + $folder = $this->folder; + } $results = $this->search_index($folder, $str, $charset, $sort_field); } $this->set_search_set(array($str, $results, $charset, $sort_field, $this->threading || $this->search_sorted ? true : false)); + + return $results; } @@ -1520,19 +1556,26 @@ */ public function search_once($folder = null, $str = 'ALL') { - if (!$str) { - $str = '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; } @@ -1660,11 +1703,28 @@ public function refresh_search() { if (!empty($this->search_string)) { - // FIXME: make this work with saved multi-folder searches - $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; + } + } } @@ -1679,13 +1739,13 @@ */ public function get_message_headers($uid, $folder = null, $force = false) { - if (!strlen($folder)) { - $folder = $this->folder; + // decode combined UID-folder identifier + if (preg_match('/^\d+-.+/', $uid)) { + list($uid, $folder) = explode('-', $uid, 2); } - // decode combined UID-folder identifier - if (preg_match('/^\d+-[^,]+$/', $uid)) { - list($uid, $folder) = explode('-', $uid); + if (!strlen($folder)) { + $folder = $this->folder; } // get cached headers @@ -1698,6 +1758,9 @@ else { $headers = $this->conn->fetchHeader( $folder, $uid, true, true, $this->get_fetch_headers()); + + if (is_object($headers)) + $headers->folder = $folder; } return $headers; @@ -1720,8 +1783,8 @@ } // decode combined UID-folder identifier - if (preg_match('/^\d+-[^,]+$/', $uid)) { - list($uid, $folder) = explode('-', $uid); + if (preg_match('/^\d+-.+/', $uid)) { + list($uid, $folder) = explode('-', $uid, 2); } // Check internal cache @@ -2370,6 +2433,8 @@ $this->clear_message_cache($folder, $all_mode ? null : explode(',', $uids)); } } + + $this->set_search_dirty($folder); } return $result; @@ -2417,6 +2482,17 @@ if ($saved) { // increase messagecount of the target folder $this->set_messagecount($folder, 'ALL', 1); + + rcube::get_instance()->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; @@ -2453,19 +2529,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 @@ -2480,6 +2544,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)) { @@ -2496,7 +2563,7 @@ if ($this->search_threads || $all_mode) { $this->refresh_search(); } - else { + else if (!$this->search_set->incomplete) { $this->search_set->filter(explode(',', $uids), $this->folder); } } @@ -2534,18 +2601,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 @@ -2596,13 +2651,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)); } } @@ -3063,16 +3120,17 @@ * * @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); // try to subscribe it if ($result) { @@ -3197,19 +3255,84 @@ /** - * 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')); + + 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; } @@ -3221,13 +3344,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; @@ -3238,10 +3361,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)) { @@ -3452,7 +3589,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'])) { @@ -3990,6 +4127,7 @@ $a_out = $a_defaults = $folders = array(); $delimiter = $this->get_hierarchy_delimiter(); + $specials = array_merge(array('INBOX'), array_values($this->get_special_folders())); // find default folders and skip folders starting with '.' foreach ($a_folders as $folder) { @@ -3997,7 +4135,7 @@ continue; } - if (!$skip_default && ($p = array_search($folder, $this->default_folders)) !== false && !$a_defaults[$p]) { + if (!$skip_default && ($p = array_search($folder, $specials)) !== false && !$a_defaults[$p]) { $a_defaults[$p] = $folder; } else { -- Gitblit v1.9.1