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 |  215 ++++++++++++++++++++++++++++++++++++++++++++++++-----
 1 files changed, 192 insertions(+), 23 deletions(-)

diff --git a/program/lib/Roundcube/rcube_imap.php b/program/lib/Roundcube/rcube_imap.php
index 628338a..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);
+        }
     }
 
 
@@ -945,6 +949,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);
@@ -1111,6 +1184,7 @@
         }
 
         foreach ($headers as $h) {
+            $h->folder = $folder;
             $a_msg_headers[$h->uid] = $h;
         }
 
@@ -1234,8 +1308,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))
             ) {
@@ -1413,7 +1492,7 @@
      * @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)
@@ -1422,14 +1501,48 @@
             $str = 'ALL';
         }
 
-        if (!strlen($folder)) {
-            $folder = $this->folder;
-        }
+        // multi-folder search
+        if (is_array($folder) && count($folder) > 1 && $str != 'ALL') {
+            new rcube_result_index; // trigger autoloader and make these classes available for threaded context
+            new rcube_result_thread;
 
-        $results = $this->search_index($folder, $str, $charset, $sort_field);
+            // 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 && $str == $this->search_string) {
+                $searcher->set_results($this->search_set);
+            }
+
+            // execute the search
+            $results = $searcher->exec(
+                $folder,
+                $str,
+                $charset ? $charset : $this->default_charset,
+                $sort_field && $this->get_capability('SORT') ? $sort_field : null,
+                $this->threading
+            );
+        }
+        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;
     }
 
 
@@ -1443,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;
     }
@@ -1500,7 +1620,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;
@@ -1514,7 +1634,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()) {
@@ -1529,7 +1649,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;
@@ -1547,7 +1667,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)) {
@@ -1583,10 +1703,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;
+            }
+        }
     }
 
 
@@ -1601,6 +1739,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;
         }
@@ -1615,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;
@@ -1634,6 +1780,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
@@ -2282,6 +2433,8 @@
                     $this->clear_message_cache($folder, $all_mode ? null : explode(',', $uids));
                 }
             }
+
+            $this->set_search_dirty($folder);
         }
 
         return $result;
@@ -2329,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;
@@ -2380,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)) {
@@ -2396,8 +2563,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);
                 }
             }
 
@@ -2484,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));
                 }
             }

--
Gitblit v1.9.1