Changeset 5557 in subversion


Ignore:
Timestamp:
Dec 7, 2011 3:44:48 AM (18 months ago)
Author:
alec
Message:
  • Fixed issues with big memory allocation of IMAP results, improved a lot of rcube_imap class
Location:
trunk/roundcubemail
Files:
2 added
8 edited

Legend:

Unmodified
Added
Removed
  • trunk/roundcubemail/CHANGELOG

    r5544 r5557  
    22=========================== 
    33 
     4- Fix issues with big memory allocation of IMAP results 
    45- Replace prompt() with jQuery UI dialog (#1485135) 
    56- Fix navigation in messages search results 
  • trunk/roundcubemail/program/include/rcube_imap.php

    r5525 r5557  
    432432     * 
    433433     * @param  string  IMAP Search query 
    434      * @param  array   List of message ids or NULL if empty 
     434     * @param  rcube_result_index|rcube_result_thread  Result set 
    435435     * @param  string  Charset of search string 
    436436     * @param  string  Sorting field 
    437437     * @param  string  True if set is sorted (SORT was used for searching) 
    438438     */ 
    439     function set_search_set($str=null, $msgs=null, $charset=null, $sort_field=null, $threads=false, $sorted=false) 
    440     { 
    441         if (is_array($str) && $msgs == null) 
    442             list($str, $msgs, $charset, $sort_field, $threads, $sorted) = $str; 
    443         if ($msgs === false) 
    444             $msgs = array(); 
    445         else if ($msgs != null && !is_array($msgs)) 
    446             $msgs = explode(',', $msgs); 
     439    function set_search_set($str=null, $msgs=null, $charset=null, $sort_field=null, $sorted=false) 
     440    { 
     441        if (is_array($str) && $msgs === null) 
     442            list($str, $msgs, $charset, $sort_field, $sorted) = $str; 
    447443 
    448444        $this->search_string     = $str; 
     
    450446        $this->search_charset    = $charset; 
    451447        $this->search_sort_field = $sort_field; 
    452         $this->search_threads    = $threads; 
    453448        $this->search_sorted     = $sorted; 
     449        $this->search_threads    = is_a($this->search_set, 'rcube_result_thread'); 
    454450    } 
    455451 
     
    457453    /** 
    458454     * Return the saved search set as hash array 
     455     * 
     456     * @param bool $clone Clone result object 
     457     * 
    459458     * @return array Search set 
    460459     */ 
    461460    function get_search_set() 
    462461    { 
    463         return array($this->search_string, 
     462        return array( 
     463            $this->search_string, 
    464464                $this->search_set, 
    465465                $this->search_charset, 
    466466                $this->search_sort_field, 
    467                 $this->search_threads, 
    468467                $this->search_sorted, 
    469468            ); 
     
    690689        // count search set 
    691690        if ($this->search_string && $mailbox == $this->mailbox && ($mode == 'ALL' || $mode == 'THREADS') && !$force) { 
    692             if ($this->search_threads) 
    693                 return $mode == 'ALL' ? count((array)$this->search_set['depth']) : count((array)$this->search_set['tree']); 
     691            if ($mode == 'ALL') 
     692                return $this->search_set->countMessages(); 
    694693            else 
    695                 return count((array)$this->search_set); 
     694                return $this->search_set->count(); 
    696695        } 
    697696 
     
    706705 
    707706        if ($mode == 'THREADS') { 
    708             $res   = $this->_threadcount($mailbox, $msg_count); 
    709             $count = $res['count']; 
     707            $res   = $this->fetch_threads($mailbox, $force); 
     708            $count = $res->count(); 
    710709 
    711710            if ($status) { 
    712                 $this->set_folder_stats($mailbox, 'cnt', $res['msgcount']); 
    713                 $this->set_folder_stats($mailbox, 'maxuid', $res['maxuid'] ? $this->id2uid($res['maxuid'], $mailbox) : 0); 
     711                $msg_count = $res->countMessages(); 
     712                $this->set_folder_stats($mailbox, 'cnt', $msg_count); 
     713                $this->set_folder_stats($mailbox, 'maxuid', $msg_count ? $this->id2uid($msg_count, $mailbox) : 0); 
    714714            } 
    715715        } 
     
    722722            $search_str = "ALL UNDELETED"; 
    723723            $keys       = array('COUNT'); 
    724             $need_uid   = false; 
    725724 
    726725            if ($mode == 'UNSEEN') { 
     
    733732                if ($status) { 
    734733                    $keys[]   = 'MAX'; 
    735                     $need_uid = true; 
    736734                } 
    737735            } 
     736 
     737            // @TODO: if $force==false && $mode == 'ALL' we could try to use cache index here 
    738738 
    739739            // get message count using (E)SEARCH 
    740740            // not very performant but more precise (using UNDELETED) 
    741             $index = $this->conn->search($mailbox, $search_str, $need_uid, $keys); 
    742  
    743             $count = is_array($index) ? $index['COUNT'] : 0; 
     741            $index = $this->conn->search($mailbox, $search_str, true, $keys); 
     742            $count = $index->count(); 
    744743 
    745744            if ($mode == 'ALL') { 
    746                 if ($this->messages_caching) { 
    747                     // Save additional info required by cache status check 
    748                     $this->icache['undeleted_idx'] = array($mailbox, $index['ALL'], $index['COUNT']); 
    749                 } 
     745                // Cache index data, will be used in message_index_direct() 
     746                $this->icache['undeleted_idx'] = $index; 
     747 
    750748                if ($status) { 
    751749                    $this->set_folder_stats($mailbox, 'cnt', $count); 
    752                     $this->set_folder_stats($mailbox, 'maxuid', is_array($index) ? $index['MAX'] : 0); 
     750                    $this->set_folder_stats($mailbox, 'maxuid', $index->max()); 
    753751                } 
    754752            } 
     
    776774 
    777775    /** 
    778      * Private method for getting nr of threads 
    779      * 
    780      * @param string $mailbox   Folder name 
    781      * 
    782      * @returns array Array containing items: 'count' - threads count, 
    783      *                'msgcount' = messages count, 'maxuid' = max. UID in the set 
    784      * @access  private 
    785      */ 
    786     private function _threadcount($mailbox) 
    787     { 
    788         $result = array(); 
    789  
    790         if (!empty($this->icache['threads'])) { 
    791             $dcount = count($this->icache['threads']['depth']); 
    792             $result = array( 
    793                 'count'    => count($this->icache['threads']['tree']), 
    794                 'msgcount' => $dcount, 
    795                 'maxuid'   => $dcount ? max(array_keys($this->icache['threads']['depth'])) : 0, 
    796             ); 
    797         } 
    798         else if (is_array($result = $this->fetch_threads($mailbox))) { 
    799             $dcount = count($result[1]); 
    800             $result = array( 
    801                 'count'    => count($result[0]), 
    802                 'msgcount' => $dcount, 
    803                 'maxuid'   => $dcount ? max(array_keys($result[1])) : 0, 
    804             ); 
    805         } 
    806  
    807         return $result; 
    808     } 
    809  
    810  
    811     /** 
    812776     * Public method for listing headers 
    813777     * convert mailbox name with root dir first 
     
    818782     * @param   string   $sort_order Sort order [ASC|DESC] 
    819783     * @param   int      $slice      Number of slice items to extract from result array 
     784     * 
    820785     * @return  array    Indexed array with message header objects 
    821786     * @access  public 
    822787     */ 
    823     function list_headers($mailbox='', $page=NULL, $sort_field=NULL, $sort_order=NULL, $slice=0) 
     788    public function list_headers($mailbox='', $page=NULL, $sort_field=NULL, $sort_order=NULL, $slice=0) 
    824789    { 
    825790        if (!strlen($mailbox)) { 
     
    845810    private function _list_headers($mailbox='', $page=NULL, $sort_field=NULL, $sort_order=NULL, $slice=0) 
    846811    { 
    847         if (!strlen($mailbox)) 
     812        if (!strlen($mailbox)) { 
    848813            return array(); 
     814        } 
     815 
     816        $this->set_sort_order($sort_field, $sort_order); 
     817        $page = $page ? $page : $this->list_page; 
    849818 
    850819        // use saved message set 
    851         if ($this->search_string && $mailbox == $this->mailbox) 
    852             return $this->_list_header_set($mailbox, $page, $sort_field, $sort_order, $slice); 
    853  
    854         if ($this->threading) 
    855             return $this->_list_thread_headers($mailbox, $page, $sort_field, $sort_order, $slice); 
    856  
    857         $this->_set_sort_order($sort_field, $sort_order); 
    858  
    859         $page = $page ? $page : $this->list_page; 
    860  
    861         // Use messages cache 
    862         if ($mcache = $this->get_mcache_engine()) { 
    863             $msg_index = $mcache->get_index($mailbox, $this->sort_field, $this->sort_order); 
    864  
    865             if (empty($msg_index)) 
    866                 return array(); 
    867  
    868             $from      = ($page-1) * $this->page_size; 
    869             $to        = $from + $this->page_size; 
    870             $msg_index = array_values($msg_index); // UIDs 
    871             $is_uid    = true; 
    872             $sorted    = true; 
    873  
    874             if ($from || $to) 
    875                 $msg_index = array_slice($msg_index, $from, $to - $from); 
    876  
    877             if ($slice) 
    878                 $msg_index = array_slice($msg_index, -$slice, $slice); 
    879  
    880             $a_msg_headers = $mcache->get_messages($mailbox, $msg_index); 
    881         } 
    882         // retrieve headers from IMAP 
    883         // use message index sort as default sorting (for better performance) 
    884         else if (!$this->sort_field) { 
    885             if ($this->skip_deleted) { 
    886                 // @TODO: this could be cached 
    887                 if ($msg_index = $this->_search_index($mailbox, 'ALL UNDELETED')) { 
    888                     list($begin, $end) = $this->_get_message_range(count($msg_index), $page); 
    889                     $msg_index = array_slice($msg_index, $begin, $end-$begin); 
    890                 } 
    891             } 
    892             else if ($max = $this->conn->countMessages($mailbox)) { 
    893                 list($begin, $end) = $this->_get_message_range($max, $page); 
    894                 $msg_index = range($begin+1, $end); 
    895             } 
    896             else 
    897                 $msg_index = array(); 
    898  
    899             if ($slice && $msg_index) 
    900                 $msg_index = array_slice($msg_index, ($this->sort_order == 'DESC' ? 0 : -$slice), $slice); 
    901  
    902             // fetch reqested headers from server 
    903             if ($msg_index) 
    904                 $a_msg_headers = $this->fetch_headers($mailbox, $msg_index); 
    905         } 
    906         // use SORT command 
    907         else if ($this->get_capability('SORT') && 
    908             // Courier-IMAP provides SORT capability but allows to disable it by admin (#1486959) 
    909             ($msg_index = $this->conn->sort($mailbox, $this->sort_field, 
    910                 $this->skip_deleted ? 'UNDELETED' : '', true)) !== false 
    911         ) { 
    912             if (!empty($msg_index)) { 
    913                 list($begin, $end) = $this->_get_message_range(count($msg_index), $page); 
    914                 $msg_index = array_slice($msg_index, $begin, $end-$begin); 
    915                 $is_uid    = true; 
    916  
    917                 if ($slice) 
    918                     $msg_index = array_slice($msg_index, ($this->sort_order == 'DESC' ? 0 : -$slice), $slice); 
    919  
    920                 // fetch reqested headers from server 
    921                 $a_msg_headers = $this->fetch_headers($mailbox, $msg_index, true); 
    922             } 
    923         } 
    924         // fetch specified header for all messages and sort 
    925         else if ($msg_index = $this->conn->fetchHeaderIndex($mailbox, "1:*", 
    926             $this->sort_field, $this->skip_deleted) 
    927         ) { 
    928             asort($msg_index); // ASC 
    929             $msg_index = array_keys($msg_index); 
    930             list($begin, $end) = $this->_get_message_range(count($msg_index), $page); 
    931             $msg_index = array_slice($msg_index, $begin, $end-$begin); 
    932  
    933             if ($slice) 
    934                 $msg_index = array_slice($msg_index, ($this->sort_order == 'DESC' ? 0 : -$slice), $slice); 
    935  
    936             // fetch reqested headers from server 
    937             $a_msg_headers = $this->fetch_headers($mailbox, $msg_index); 
    938         } 
    939  
    940         // return empty array if no messages found 
    941         if (!is_array($a_msg_headers) || empty($a_msg_headers)) 
     820        if ($this->search_string && $mailbox == $this->mailbox) { 
     821            return $this->_list_header_set($mailbox, $page, $slice); 
     822        } 
     823 
     824        if ($this->threading) { 
     825            return $this->_list_thread_headers($mailbox, $page, $slice); 
     826        } 
     827 
     828        // get UIDs of all messages in the folder, sorted 
     829        $index = $this->message_index($mailbox, $this->sort_field, $this->sort_order); 
     830 
     831        if ($index->isEmpty()) { 
    942832            return array(); 
    943  
    944         // use this class for message sorting 
    945         $sorter = new rcube_header_sorter(); 
    946         $sorter->set_index($msg_index, $is_uid); 
    947         $sorter->sort_headers($a_msg_headers); 
    948  
    949         if ($this->sort_order == 'DESC' && !$sorted) 
    950             $a_msg_headers = array_reverse($a_msg_headers); 
     833        } 
     834 
     835        $from = ($page-1) * $this->page_size; 
     836        $to   = $from + $this->page_size; 
     837 
     838        $index->slice($from, $to - $from); 
     839 
     840        if ($slice) 
     841            $index->slice(-$slice, $slice); 
     842 
     843        // fetch reqested messages headers 
     844        $a_index = $index->get(); 
     845        $a_msg_headers = $this->fetch_headers($mailbox, $a_index); 
    951846 
    952847        return array_values($a_msg_headers); 
     
    959854     * @param   string   $mailbox    Mailbox/folder name 
    960855     * @param   int      $page       Current page to list 
    961      * @param   string   $sort_field Header field to sort by 
    962      * @param   string   $sort_order Sort order [ASC|DESC] 
    963856     * @param   int      $slice      Number of slice items to extract from result array 
    964857     * 
     
    966859     * @see     rcube_imap::list_headers 
    967860     */ 
    968     private function _list_thread_headers($mailbox, $page=NULL, $sort_field=NULL, $sort_order=NULL, $slice=0) 
    969     { 
    970         $this->_set_sort_order($sort_field, $sort_order); 
    971  
    972         $page   = $page ? $page : $this->list_page; 
    973         $mcache = $this->get_mcache_engine(); 
    974  
     861    private function _list_thread_headers($mailbox, $page, $slice=0) 
     862    { 
    975863        // get all threads (not sorted) 
    976         if ($mcache) 
    977             list ($thread_tree, $msg_depth, $has_children) = $mcache->get_thread($mailbox); 
     864        if ($mcache = $this->get_mcache_engine()) 
     865            $threads = $mcache->get_thread($mailbox); 
    978866        else 
    979             list ($thread_tree, $msg_depth, $has_children) = $this->fetch_threads($mailbox); 
    980  
    981         if (empty($thread_tree)) 
    982             return array(); 
    983  
    984         $msg_index = $this->sort_threads($mailbox, $thread_tree); 
    985  
    986         return $this->_fetch_thread_headers($mailbox, 
    987             $thread_tree, $msg_depth, $has_children, $msg_index, $page, $slice); 
     867            $threads = $this->fetch_threads($mailbox); 
     868 
     869        return $this->_fetch_thread_headers($mailbox, $threads, $page, $slice); 
    988870    } 
    989871 
     
    995877     * @param  bool   $force    Use IMAP server, no cache 
    996878     * 
    997      * @return  array    Array with thread data 
     879     * @return rcube_imap_thread Thread data object 
    998880     */ 
    999881    function fetch_threads($mailbox, $force = false) 
     
    1007889            // get all threads 
    1008890            $result = $this->conn->thread($mailbox, $this->threading, 
    1009                 $this->skip_deleted ? 'UNDELETED' : ''); 
     891                $this->skip_deleted ? 'UNDELETED' : '', true); 
    1010892 
    1011893            // add to internal (fast) cache 
    1012             $this->icache['threads'] = array(); 
    1013             $this->icache['threads']['tree'] = is_array($result) ? $result[0] : array(); 
    1014             $this->icache['threads']['depth'] = is_array($result) ? $result[1] : array(); 
    1015             $this->icache['threads']['has_children'] = is_array($result) ? $result[2] : array(); 
    1016         } 
    1017  
    1018         return array( 
    1019             $this->icache['threads']['tree'], 
    1020             $this->icache['threads']['depth'], 
    1021             $this->icache['threads']['has_children'], 
    1022         ); 
     894            $this->icache['threads'] = $result; 
     895        } 
     896 
     897        return $this->icache['threads']; 
    1023898    } 
    1024899 
     
    1027902     * Private method for fetching threaded messages headers 
    1028903     * 
    1029      * @param string  $mailbox      Mailbox name 
    1030      * @param array   $thread_tree  Thread tree data 
    1031      * @param array   $msg_depth    Thread depth data 
    1032      * @param array   $has_children Thread children data 
    1033      * @param array   $msg_index    Messages index 
    1034      * @param int     $page         List page number 
    1035      * @param int     $slice        Number of threads to slice 
     904     * @param string              $mailbox    Mailbox name 
     905     * @param rcube_result_thread $threads    Threads data object 
     906     * @param int                 $page       List page number 
     907     * @param int                 $slice      Number of threads to slice 
    1036908     * 
    1037909     * @return array  Messages headers 
    1038910     * @access  private 
    1039911     */ 
    1040     private function _fetch_thread_headers($mailbox, $thread_tree, $msg_depth, $has_children, $msg_index, $page, $slice=0) 
    1041     { 
    1042         // now get IDs for current page 
    1043         list($begin, $end) = $this->_get_message_range(count($msg_index), $page); 
    1044         $msg_index = array_slice($msg_index, $begin, $end-$begin); 
     912    private function _fetch_thread_headers($mailbox, $threads, $page, $slice=0) 
     913    { 
     914        // Sort thread structure 
     915        $this->sort_threads($threads); 
     916 
     917        $from = ($page-1) * $this->page_size; 
     918        $to   = $from + $this->page_size; 
     919 
     920        $threads->slice($from, $to - $from); 
    1045921 
    1046922        if ($slice) 
    1047             $msg_index = array_slice($msg_index, ($this->sort_order == 'DESC' ? 0 : -$slice), $slice); 
    1048  
    1049         if ($this->sort_order == 'DESC') 
    1050             $msg_index = array_reverse($msg_index); 
    1051  
    1052         // flatten threads array 
    1053         // @TODO: fetch children only in expanded mode (?) 
    1054         $all_ids = array(); 
    1055         foreach ($msg_index as $root) { 
    1056             $all_ids[] = $root; 
    1057             if (!empty($thread_tree[$root])) 
    1058                 $all_ids = array_merge($all_ids, array_keys_recursive($thread_tree[$root])); 
    1059         } 
     923            $threads->slice(-$slice, $slice); 
     924 
     925        // Get UIDs of all messages in all threads 
     926        $a_index = $threads->get(); 
    1060927 
    1061928        // fetch reqested headers from server 
    1062         $a_msg_headers = $this->fetch_headers($mailbox, $all_ids); 
    1063  
    1064         // return empty array if no messages found 
    1065         if (!is_array($a_msg_headers) || empty($a_msg_headers)) 
    1066             return array(); 
    1067  
    1068         // use this class for message sorting 
    1069         $sorter = new rcube_header_sorter(); 
    1070         $sorter->set_index($all_ids); 
    1071         $sorter->sort_headers($a_msg_headers); 
     929        $a_msg_headers = $this->fetch_headers($mailbox, $a_index); 
     930 
     931        unset($a_index); 
    1072932 
    1073933        // Set depth, has_children and unread_children fields in headers 
    1074         $this->_set_thread_flags($a_msg_headers, $msg_depth, $has_children); 
     934        $this->_set_thread_flags($a_msg_headers, $threads); 
    1075935 
    1076936        return array_values($a_msg_headers); 
     
    1082942     * depth, has_children and unread_children 
    1083943     * 
    1084      * @param  array  $headers      Reference to headers array indexed by message ID 
    1085      * @param  array  $msg_depth    Array of messages depth indexed by message ID 
    1086      * @param  array  $msg_children Array of messages children flags indexed by message ID 
    1087      * @return array   Message headers array indexed by message ID 
     944     * @param  array             $headers Reference to headers array indexed by message UID 
     945     * @param  rcube_imap_result $threads Threads data object 
     946     * 
     947     * @return array Message headers array indexed by message UID 
    1088948     * @access private 
    1089949     */ 
    1090     private function _set_thread_flags(&$headers, $msg_depth, $msg_children) 
     950    private function _set_thread_flags(&$headers, $threads) 
    1091951    { 
    1092952        $parents = array(); 
    1093953 
    1094         foreach ($headers as $idx => $header) { 
    1095             $id = $header->id; 
    1096             $depth = $msg_depth[$id]; 
     954        list ($msg_depth, $msg_children) = $threads->getThreadData(); 
     955 
     956        foreach ($headers as $uid => $header) { 
     957            $depth = $msg_depth[$uid]; 
    1097958            $parents = array_slice($parents, 0, $depth); 
    1098959 
    1099960            if (!empty($parents)) { 
    1100                 $headers[$idx]->parent_uid = end($parents); 
     961                $headers[$uid]->parent_uid = end($parents); 
    1101962                if (empty($header->flags['SEEN'])) 
    1102963                    $headers[$parents[0]]->unread_children++; 
    1103964            } 
    1104             array_push($parents, $header->uid); 
    1105  
    1106             $headers[$idx]->depth = $depth; 
    1107             $headers[$idx]->has_children = $msg_children[$id]; 
     965            array_push($parents, $uid); 
     966 
     967            $headers[$uid]->depth = $depth; 
     968            $headers[$uid]->has_children = $msg_children[$uid]; 
    1108969        } 
    1109970    } 
     
    1112973    /** 
    1113974     * Private method for listing a set of message headers (search results) 
     975     * 
     976     * @param   string   $mailbox  Mailbox/folder name 
     977     * @param   int      $page     Current page to list 
     978     * @param   int      $slice    Number of slice items to extract from result array 
     979     * 
     980     * @return  array    Indexed array with message header objects 
     981     * @access  private 
     982     */ 
     983    private function _list_header_set($mailbox, $page, $slice=0) 
     984    { 
     985        if (!strlen($mailbox) || empty($this->search_set) || $this->search_set->isEmpty()) { 
     986            return array(); 
     987        } 
     988 
     989        // use saved messages from searching 
     990        if ($this->threading) { 
     991            return $this->_list_thread_header_set($mailbox, $page, $slice); 
     992        } 
     993 
     994        // search set is threaded, we need a new one 
     995        if ($this->search_threads) { 
     996            $this->search('', $this->search_string, $this->search_charset, $this->sort_field); 
     997        } 
     998 
     999        $index = clone $this->search_set; 
     1000        $from  = ($page-1) * $this->page_size; 
     1001        $to    = $from + $this->page_size; 
     1002 
     1003        // return empty array if no messages found 
     1004        if ($index->isEmpty()) 
     1005            return array(); 
     1006 
     1007        // quickest method (default sorting) 
     1008        if (!$this->search_sort_field && !$this->sort_field) { 
     1009            $got_index = true; 
     1010        } 
     1011        // sorted messages, so we can first slice array and then fetch only wanted headers 
     1012        else if ($this->search_sorted) { // SORT searching result 
     1013            $got_index = true; 
     1014            // reset search set if sorting field has been changed 
     1015            if ($this->sort_field && $this->search_sort_field != $this->sort_field) { 
     1016                $this->search('', $this->search_string, $this->search_charset, $this->sort_field); 
     1017 
     1018                $index = clone $this->search_set; 
     1019 
     1020                // return empty array if no messages found 
     1021                if ($index->isEmpty()) 
     1022                    return array(); 
     1023            } 
     1024        } 
     1025 
     1026        if ($got_index) { 
     1027            if ($this->sort_order != $index->getParameters('ORDER')) { 
     1028                $index->revert(); 
     1029            } 
     1030 
     1031            // get messages uids for one page 
     1032            $index->slice($from, $to-$from); 
     1033 
     1034            if ($slice) 
     1035                $index->slice(-$slice, $slice); 
     1036 
     1037            // fetch headers 
     1038            $a_index       = $index->get(); 
     1039            $a_msg_headers = $this->fetch_headers($mailbox, $a_index); 
     1040 
     1041            return array_values($a_msg_headers); 
     1042        } 
     1043 
     1044        // SEARCH result, need sorting 
     1045        $cnt = $index->count(); 
     1046 
     1047        // 300: experimantal value for best result 
     1048        if (($cnt > 300 && $cnt > $this->page_size) || !$this->sort_field) { 
     1049            // use memory less expensive (and quick) method for big result set 
     1050            $index = clone $this->message_index('', $this->sort_field, $this->sort_order); 
     1051            // get messages uids for one page... 
     1052            $index->slice($start_msg, min($cnt-$from, $this->page_size)); 
     1053 
     1054            if ($slice) 
     1055                $index->slice(-$slice, $slice); 
     1056 
     1057            // ...and fetch headers 
     1058            $a_index       = $index->get(); 
     1059            $a_msg_headers = $this->fetch_headers($mailbox, $a_index); 
     1060 
     1061            return array_values($a_msg_headers); 
     1062        } 
     1063        else { 
     1064            // for small result set we can fetch all messages headers 
     1065            $a_index       = $index->get(); 
     1066            $a_msg_headers = $this->fetch_headers($mailbox, $a_index, false); 
     1067 
     1068            // return empty array if no messages found 
     1069            if (!is_array($a_msg_headers) || empty($a_msg_headers)) 
     1070                return array(); 
     1071 
     1072            // if not already sorted 
     1073            $a_msg_headers = $this->conn->sortHeaders( 
     1074                $a_msg_headers, $this->sort_field, $this->sort_order); 
     1075 
     1076            // only return the requested part of the set 
     1077            $a_msg_headers = array_slice(array_values($a_msg_headers), 
     1078                $from, min($cnt-$to, $this->page_size)); 
     1079 
     1080            if ($slice) 
     1081                $a_msg_headers = array_slice($a_msg_headers, -$slice, $slice); 
     1082 
     1083            return $a_msg_headers; 
     1084        } 
     1085    } 
     1086 
     1087 
     1088    /** 
     1089     * Private method for listing a set of threaded message headers (search results) 
    11141090     * 
    11151091     * @param   string   $mailbox    Mailbox/folder name 
    11161092     * @param   int      $page       Current page to list 
    1117      * @param   string   $sort_field Header field to sort by 
    1118      * @param   string   $sort_order Sort order [ASC|DESC] 
    1119      * @param   int  $slice      Number of slice items to extract from result array 
     1093     * @param   int      $slice      Number of slice items to extract from result array 
     1094     * 
    11201095     * @return  array    Indexed array with message header objects 
    11211096     * @access  private 
    11221097     * @see     rcube_imap::list_header_set() 
    11231098     */ 
    1124     private function _list_header_set($mailbox, $page=NULL, $sort_field=NULL, $sort_order=NULL, $slice=0) 
    1125     { 
    1126         if (!strlen($mailbox) || empty($this->search_set)) 
     1099    private function _list_thread_header_set($mailbox, $page, $slice=0) 
     1100    { 
     1101        // update search_set if previous data was fetched with disabled threading 
     1102        if (!$this->search_threads) { 
     1103            if ($this->search_set->isEmpty()) 
     1104                return array(); 
     1105            $this->search('', $this->search_string, $this->search_charset, $this->sort_field); 
     1106        } 
     1107 
     1108        return $this->_fetch_thread_headers($mailbox, clone $this->search_set, $page, $slice); 
     1109    } 
     1110 
     1111 
     1112    /** 
     1113     * Fetches messages headers (by UID) 
     1114     * 
     1115     * @param  string  $mailbox  Mailbox name 
     1116     * @param  array   $msgs     Message UIDs 
     1117     * @param  bool    $sort     Enables result sorting by $msgs 
     1118     * @param  bool    $force    Disables cache use 
     1119     * 
     1120     * @return array Messages headers indexed by UID 
     1121     * @access private 
     1122     */ 
     1123    function fetch_headers($mailbox, $msgs, $sort = true, $force = false) 
     1124    { 
     1125        if (empty($msgs)) 
    11271126            return array(); 
    11281127 
    1129         // use saved messages from searching 
    1130         if ($this->threading) 
    1131             return $this->_list_thread_header_set($mailbox, $page, $sort_field, $sort_order, $slice); 
    1132  
    1133         // search set is threaded, we need a new one 
    1134         if ($this->search_threads) { 
    1135             if (empty($this->search_set['tree'])) 
    1136                 return array(); 
    1137             $this->search('', $this->search_string, $this->search_charset, $sort_field); 
    1138         } 
    1139  
    1140         $msgs = $this->search_set; 
    1141         $a_msg_headers = array(); 
    1142         $page = $page ? $page : $this->list_page; 
    1143         $start_msg = ($page-1) * $this->page_size; 
    1144  
    1145         $this->_set_sort_order($sort_field, $sort_order); 
    1146  
    1147         // quickest method (default sorting) 
    1148         if (!$this->search_sort_field && !$this->sort_field) { 
    1149             if ($sort_order == 'DESC') 
    1150                 $msgs = array_reverse($msgs); 
    1151  
    1152             // get messages uids for one page 
    1153             $msgs = array_slice(array_values($msgs), $start_msg, min(count($msgs)-$start_msg, $this->page_size)); 
    1154  
    1155             if ($slice) 
    1156                 $msgs = array_slice($msgs, -$slice, $slice); 
    1157  
    1158             // fetch headers 
    1159             $a_msg_headers = $this->fetch_headers($mailbox, $msgs); 
    1160  
    1161             // I didn't found in RFC that FETCH always returns messages sorted by index 
     1128        if (!$force && ($mcache = $this->get_mcache_engine())) { 
     1129            $headers = $mcache->get_messages($mailbox, $msgs); 
     1130        } 
     1131        else { 
     1132            // fetch reqested headers from server 
     1133            $headers = $this->conn->fetchHeaders( 
     1134                $mailbox, $msgs, true, false, $this->get_fetch_headers()); 
     1135        } 
     1136 
     1137        if (empty($headers)) 
     1138            return array(); 
     1139 
     1140        foreach ($headers as $h) { 
     1141            $a_msg_headers[$h->uid] = $h; 
     1142        } 
     1143 
     1144        if ($sort) { 
     1145            // use this class for message sorting 
    11621146            $sorter = new rcube_header_sorter(); 
    11631147            $sorter->set_index($msgs); 
    11641148            $sorter->sort_headers($a_msg_headers); 
    1165  
    1166             return array_values($a_msg_headers); 
    1167         } 
    1168  
    1169         // sorted messages, so we can first slice array and then fetch only wanted headers 
    1170         if ($this->search_sorted) { // SORT searching result 
    1171             // reset search set if sorting field has been changed 
    1172             if ($this->sort_field && $this->search_sort_field != $this->sort_field) 
    1173                 $msgs = $this->search('', $this->search_string, $this->search_charset, $this->sort_field); 
    1174  
    1175             // return empty array if no messages found 
    1176             if (empty($msgs)) 
    1177                 return array(); 
    1178  
    1179             if ($sort_order == 'DESC') 
    1180                 $msgs = array_reverse($msgs); 
    1181  
    1182             // get messages uids for one page 
    1183             $msgs = array_slice(array_values($msgs), $start_msg, min(count($msgs)-$start_msg, $this->page_size)); 
    1184  
    1185             if ($slice) 
    1186                 $msgs = array_slice($msgs, -$slice, $slice); 
    1187  
    1188             // fetch headers 
    1189             $a_msg_headers = $this->fetch_headers($mailbox, $msgs); 
    1190  
    1191             $sorter = new rcube_header_sorter(); 
    1192             $sorter->set_index($msgs); 
    1193             $sorter->sort_headers($a_msg_headers); 
    1194  
    1195             return array_values($a_msg_headers); 
    1196         } 
    1197         else { // SEARCH result, need sorting 
    1198             $cnt = count($msgs); 
    1199             // 300: experimantal value for best result 
    1200             if (($cnt > 300 && $cnt > $this->page_size) || !$this->sort_field) { 
    1201                 // use memory less expensive (and quick) method for big result set 
    1202                 $a_index = $this->message_index('', $this->sort_field, $this->sort_order); 
    1203                 // get messages uids for one page... 
    1204                 $msgs = array_slice($a_index, $start_msg, min($cnt-$start_msg, $this->page_size)); 
    1205                 if ($slice) 
    1206                     $msgs = array_slice($msgs, -$slice, $slice); 
    1207                 // ...and fetch headers 
    1208                 $a_msg_headers = $this->fetch_headers($mailbox, $msgs); 
    1209  
    1210  
    1211                 // return empty array if no messages found 
    1212                 if (!is_array($a_msg_headers) || empty($a_msg_headers)) 
    1213                     return array(); 
    1214  
    1215                 $sorter = new rcube_header_sorter(); 
    1216                 $sorter->set_index($msgs); 
    1217                 $sorter->sort_headers($a_msg_headers); 
    1218  
    1219                 return array_values($a_msg_headers); 
    1220             } 
    1221             else { 
    1222                 // for small result set we can fetch all messages headers 
    1223                 $a_msg_headers = $this->fetch_headers($mailbox, $msgs); 
    1224  
    1225                 // return empty array if no messages found 
    1226                 if (!is_array($a_msg_headers) || empty($a_msg_headers)) 
    1227                     return array(); 
    1228  
    1229                 // if not already sorted 
    1230                 $a_msg_headers = $this->conn->sortHeaders( 
    1231                     $a_msg_headers, $this->sort_field, $this->sort_order); 
    1232  
    1233                 // only return the requested part of the set 
    1234                 $a_msg_headers = array_slice(array_values($a_msg_headers), 
    1235                     $start_msg, min($cnt-$start_msg, $this->page_size)); 
    1236  
    1237                 if ($slice) 
    1238                     $a_msg_headers = array_slice($a_msg_headers, -$slice, $slice); 
    1239  
    1240                 return $a_msg_headers; 
    1241             } 
    1242         } 
    1243     } 
    1244  
    1245  
    1246     /** 
    1247      * Private method for listing a set of threaded message headers (search results) 
    1248      * 
    1249      * @param   string   $mailbox    Mailbox/folder name 
    1250      * @param   int      $page       Current page to list 
    1251      * @param   string   $sort_field Header field to sort by 
    1252      * @param   string   $sort_order Sort order [ASC|DESC] 
    1253      * @param   int      $slice      Number of slice items to extract from result array 
    1254      * @return  array    Indexed array with message header objects 
    1255      * @access  private 
    1256      * @see     rcube_imap::list_header_set() 
    1257      */ 
    1258     private function _list_thread_header_set($mailbox, $page=NULL, $sort_field=NULL, $sort_order=NULL, $slice=0) 
    1259     { 
    1260         // update search_set if previous data was fetched with disabled threading 
    1261         if (!$this->search_threads) { 
    1262             if (empty($this->search_set)) 
    1263                 return array(); 
    1264             $this->search('', $this->search_string, $this->search_charset, $sort_field); 
    1265         } 
    1266  
    1267         // empty result 
    1268         if (empty($this->search_set['tree'])) 
    1269             return array(); 
    1270  
    1271         $thread_tree = $this->search_set['tree']; 
    1272         $msg_depth = $this->search_set['depth']; 
    1273         $has_children = $this->search_set['children']; 
    1274         $a_msg_headers = array(); 
    1275  
    1276         $page = $page ? $page : $this->list_page; 
    1277         $start_msg = ($page-1) * $this->page_size; 
    1278  
    1279         $this->_set_sort_order($sort_field, $sort_order); 
    1280  
    1281         $msg_index = $this->sort_threads($mailbox, $thread_tree, array_keys($msg_depth)); 
    1282  
    1283         return $this->_fetch_thread_headers($mailbox, 
    1284             $thread_tree, $msg_depth, $has_children, $msg_index, $page, $slice=0); 
    1285     } 
    1286  
    1287  
    1288     /** 
    1289      * Helper function to get first and last index of the requested set 
    1290      * 
    1291      * @param  int     $max  Messages count 
    1292      * @param  mixed   $page Page number to show, or string 'all' 
    1293      * @return array   Array with two values: first index, last index 
    1294      * @access private 
    1295      */ 
    1296     private function _get_message_range($max, $page) 
    1297     { 
    1298         $start_msg = ($page-1) * $this->page_size; 
    1299  
    1300         if ($page=='all') { 
    1301             $begin  = 0; 
    1302             $end    = $max; 
    1303         } 
    1304         else if ($this->sort_order=='DESC') { 
    1305             $begin  = $max - $this->page_size - $start_msg; 
    1306             $end    = $max - $start_msg; 
    1307         } 
    1308         else { 
    1309             $begin  = $start_msg; 
    1310             $end    = $start_msg + $this->page_size; 
    1311         } 
    1312  
    1313         if ($begin < 0) $begin = 0; 
    1314         if ($end < 0) $end = $max; 
    1315         if ($end > $max) $end = $max; 
    1316  
    1317         return array($begin, $end); 
    1318     } 
    1319  
    1320  
    1321     /** 
    1322      * Fetches messages headers 
    1323      * 
    1324      * @param  string  $mailbox  Mailbox name 
    1325      * @param  array   $msgs     Messages sequence numbers 
    1326      * @param  bool    $is_uid   Enable if $msgs numbers are UIDs 
    1327      * @param  bool    $force    Disables cache use 
    1328      * 
    1329      * @return array Messages headers indexed by UID 
    1330      * @access private 
    1331      */ 
    1332     function fetch_headers($mailbox, $msgs, $is_uid = false, $force = false) 
    1333     { 
    1334         if (empty($msgs)) 
    1335             return array(); 
    1336  
    1337         if (!$force && ($mcache = $this->get_mcache_engine())) { 
    1338             return $mcache->get_messages($mailbox, $msgs, $is_uid); 
    1339         } 
    1340  
    1341         // fetch reqested headers from server 
    1342         $index = $this->conn->fetchHeaders( 
    1343             $mailbox, $msgs, $is_uid, false, $this->get_fetch_headers()); 
    1344  
    1345         if (empty($index)) 
    1346             return array(); 
    1347  
    1348         foreach ($index as $headers) { 
    1349             $a_msg_headers[$headers->uid] = $headers; 
    13501149        } 
    13511150 
     
    13631162     * @return int   Folder status 
    13641163     */ 
    1365     function mailbox_status($mailbox = null) 
     1164    public function mailbox_status($mailbox = null) 
    13661165    { 
    13671166        if (!strlen($mailbox)) { 
     
    14261225 
    14271226    /** 
    1428      * Return sorted array of message IDs (not UIDs) 
     1227     * Return sorted list of message UIDs 
    14291228     * 
    14301229     * @param string $mailbox    Mailbox to get index from 
    14311230     * @param string $sort_field Sort column 
    14321231     * @param string $sort_order Sort order [ASC, DESC] 
    1433      * @return array Indexed array with message IDs 
    1434      */ 
    1435     function message_index($mailbox='', $sort_field=NULL, $sort_order=NULL) 
     1232     * 
     1233     * @return rcube_result_index|rcube_result_thread List of messages (UIDs) 
     1234     */ 
     1235    public function message_index($mailbox='', $sort_field=NULL, $sort_order=NULL) 
    14361236    { 
    14371237        if ($this->threading) 
    14381238            return $this->thread_index($mailbox, $sort_field, $sort_order); 
    14391239 
    1440         $this->_set_sort_order($sort_field, $sort_order); 
     1240        $this->set_sort_order($sort_field, $sort_order); 
    14411241 
    14421242        if (!strlen($mailbox)) { 
    14431243            $mailbox = $this->mailbox; 
    14441244        } 
    1445         $key = "{$mailbox}:{$this->sort_field}:{$this->sort_order}:{$this->search_string}.msgi"; 
    14461245 
    14471246        // we have a saved search result, get index from there 
    1448         if (!isset($this->icache[$key]) && $this->search_string 
    1449             && !$this->search_threads && $mailbox == $this->mailbox) { 
     1247        if ($this->search_string) { 
     1248            if ($this->search_threads) { 
     1249                $this->search($mailbox, $this->search_string, $this->search_charset, $this->sort_field); 
     1250            } 
     1251 
    14501252            // use message index sort as default sorting 
    1451             if (!$this->sort_field) { 
    1452                 $msgs = $this->search_set; 
    1453  
    1454                 if ($this->search_sort_field != 'date') 
    1455                     sort($msgs); 
    1456  
    1457                 if ($this->sort_order == 'DESC') 
    1458                     $this->icache[$key] = array_reverse($msgs); 
    1459                 else 
    1460                     $this->icache[$key] = $msgs; 
    1461             } 
    1462             // sort with SORT command 
    1463             else if ($this->search_sorted) { 
    1464                 if ($this->sort_field && $this->search_sort_field != $this->sort_field) 
    1465                     $this->search('', $this->search_string, $this->search_charset, $this->sort_field); 
    1466  
    1467                 if ($this->sort_order == 'DESC') 
    1468                     $this->icache[$key] = array_reverse($this->search_set); 
    1469                 else 
    1470                     $this->icache[$key] = $this->search_set; 
     1253            if (!$this->sort_field || $this->search_sorted) { 
     1254                if ($this->sort_field && $this->search_sort_field != $this->sort_field) { 
     1255                    $this->search($mailbox, $this->search_string, $this->search_charset, $this->sort_field); 
     1256                } 
     1257                $index = $this->search_set; 
    14711258            } 
    14721259            else { 
    1473                 $a_index = $this->conn->fetchHeaderIndex($mailbox, 
    1474                     join(',', $this->search_set), $this->sort_field, $this->skip_deleted); 
    1475  
    1476                 if (is_array($a_index)) { 
    1477                     if ($this->sort_order=="ASC") 
    1478                         asort($a_index); 
    1479                     else if ($this->sort_order=="DESC") 
    1480                         arsort($a_index); 
    1481  
    1482                     $this->icache[$key] = array_keys($a_index); 
    1483                 } 
    1484                 else { 
    1485                     $this->icache[$key] = array(); 
    1486                 } 
    1487             } 
    1488         } 
    1489  
    1490         // have stored it in RAM 
    1491         if (isset($this->icache[$key])) 
    1492             return $this->icache[$key]; 
     1260                $index = $this->conn->index($mailbox, $this->search_set->get(), 
     1261                    $this->sort_field, $this->skip_deleted, true, true); 
     1262            } 
     1263 
     1264            if ($this->sort_order != $index->getParameters('ORDER')) { 
     1265                $index->revert(); 
     1266            } 
     1267 
     1268            return $index; 
     1269        } 
    14931270 
    14941271        // check local cache 
    14951272        if ($mcache = $this->get_mcache_engine()) { 
    1496             $a_index = $mcache->get_index($mailbox, $this->sort_field, $this->sort_order); 
    1497             $this->icache[$key] = array_keys($a_index); 
     1273            $index = $mcache->get_index($mailbox, $this->sort_field, $this->sort_order); 
    14981274        } 
    14991275        // fetch from IMAP server 
    15001276        else { 
    1501             $this->icache[$key] = $this->message_index_direct( 
     1277            $index = $this->message_index_direct( 
    15021278                $mailbox, $this->sort_field, $this->sort_order); 
    15031279        } 
    15041280 
    1505         return $this->icache[$key]; 
    1506     } 
    1507  
    1508  
    1509     /** 
    1510      * Return sorted array of message IDs (not UIDs) directly from IMAP server. 
    1511      * Doesn't use cache and ignores current search settings. 
     1281        return $index; 
     1282    } 
     1283 
     1284 
     1285    /** 
     1286     * Return sorted list of message UIDs ignoring current search settings. 
     1287     * Doesn't uses cache by default. 
    15121288     * 
    15131289     * @param string $mailbox    Mailbox to get index from 
    15141290     * @param string $sort_field Sort column 
    15151291     * @param string $sort_order Sort order [ASC, DESC] 
    1516      * 
    1517      * @return array Indexed array with message IDs 
    1518      */ 
    1519     function message_index_direct($mailbox, $sort_field = null, $sort_order = null) 
    1520     { 
     1292     * @param bool   $skip_cache Disables cache usage 
     1293     * 
     1294     * @return rcube_result_index Sorted list of message UIDs 
     1295     */ 
     1296    public function message_index_direct($mailbox, $sort_field = null, $sort_order = null, $skip_cache = true) 
     1297    { 
     1298        if (!$skip_cache && ($mcache = $this->get_mcache_engine())) { 
     1299            $index = $mcache->get_index($mailbox, $sort_field, $sort_order); 
     1300        } 
    15211301        // use message index sort as default sorting 
    1522         if (!$sort_field) { 
    1523             if ($this->skip_deleted) { 
    1524                 $a_index = $this->conn->search($mailbox, 'ALL UNDELETED'); 
    1525                 // I didn't found that SEARCH should return sorted IDs 
    1526                 if (is_array($a_index)) 
    1527                     sort($a_index); 
    1528             } else if ($max = $this->_messagecount($mailbox, 'ALL', true, false)) { 
    1529                 $a_index = range(1, $max); 
    1530             } 
    1531  
    1532             if ($a_index !== false && $sort_order == 'DESC') 
    1533                 $a_index = array_reverse($a_index); 
     1302        else if (!$sort_field) { 
     1303            if ($this->skip_deleted && !empty($this->icache['undeleted_idx']) 
     1304                && $this->icache['undeleted_idx']->getParameters('MAILBOX') == $mailbox 
     1305            ) { 
     1306                $index = $this->icache['undeleted_idx']; 
     1307            } 
     1308            else { 
     1309                $index = $this->conn->search($mailbox, 
     1310                    'ALL' .($this->skip_deleted ? ' UNDELETED' : ''), true); 
     1311            } 
    15341312        } 
    15351313        // fetch complete message index 
    1536         else if ($this->get_capability('SORT') && 
    1537             ($a_index = $this->conn->sort($mailbox, 
    1538                 $sort_field, $this->skip_deleted ? 'UNDELETED' : '')) !== false 
    1539         ) { 
    1540             if ($sort_order == 'DESC') 
    1541                 $a_index = array_reverse($a_index); 
    1542         } 
    1543         else if ($a_index = $this->conn->fetchHeaderIndex( 
    1544             $mailbox, "1:*", $sort_field, $skip_deleted)) { 
    1545             if ($sort_order=="ASC") 
    1546                 asort($a_index); 
    1547             else if ($sort_order=="DESC") 
    1548                 arsort($a_index); 
    1549  
    1550             $a_index = array_keys($a_index); 
    1551         } 
    1552  
    1553         return $a_index !== false ? $a_index : array(); 
    1554     } 
    1555  
    1556  
    1557     /** 
    1558      * Return sorted array of threaded message IDs (not UIDs) 
     1314        else { 
     1315            if ($this->get_capability('SORT')) { 
     1316                $index = $this->conn->sort($mailbox, $sort_field, 
     1317                    $this->skip_deleted ? 'UNDELETED' : '', true); 
     1318            } 
     1319 
     1320            if (empty($index) || $index->isError()) { 
     1321                $index = $this->conn->index($mailbox, "1:*", $sort_field, 
     1322                    $this->skip_deleted, false, true); 
     1323            } 
     1324        } 
     1325 
     1326        if ($sort_order != $index->getParameters('ORDER')) { 
     1327            $index->revert(); 
     1328        } 
     1329 
     1330        return $index; 
     1331    } 
     1332 
     1333 
     1334    /** 
     1335     * Return index of threaded message UIDs 
    15591336     * 
    15601337     * @param string $mailbox    Mailbox to get index from 
    15611338     * @param string $sort_field Sort column 
    15621339     * @param string $sort_order Sort order [ASC, DESC] 
    1563      * @return array Indexed array with message IDs 
     1340     * 
     1341     * @return rcube_result_thread Message UIDs 
    15641342     */ 
    15651343    function thread_index($mailbox='', $sort_field=NULL, $sort_order=NULL) 
    15661344    { 
    1567         $this->_set_sort_order($sort_field, $sort_order); 
    1568  
    15691345        if (!strlen($mailbox)) { 
    15701346            $mailbox = $this->mailbox; 
    15711347        } 
    1572         $key = "{$mailbox}:{$this->sort_field}:{$this->sort_order}:{$this->search_string}.thi"; 
    15731348 
    15741349        // we have a saved search result, get index from there 
    1575         if (!isset($this->icache[$key]) && $this->search_string 
    1576             && $this->search_threads && $mailbox == $this->mailbox) { 
    1577             // use message IDs for better performance 
    1578             $ids = array_keys_recursive($this->search_set['tree']); 
    1579             $this->icache[$key] = $this->_flatten_threads($mailbox, $this->search_set['tree'], $ids); 
    1580         } 
    1581  
    1582         // have stored it in RAM 
    1583         if (isset($this->icache[$key])) 
    1584             return $this->icache[$key]; 
    1585  
    1586         // get all threads (default sort order) 
    1587         list ($thread_tree) = $this->fetch_threads($mailbox); 
    1588  
    1589         $this->icache[$key] = $this->_flatten_threads($mailbox, $thread_tree); 
    1590  
    1591         return $this->icache[$key]; 
    1592     } 
    1593  
    1594  
    1595     /** 
    1596      * Return array of threaded messages (all, not only roots) 
    1597      * 
    1598      * @param string $mailbox     Mailbox to get index from 
    1599      * @param array  $thread_tree Threaded messages array (see fetch_threads()) 
    1600      * @param array  $ids         Message IDs if we know what we need (e.g. search result) 
    1601      *                            for better performance 
    1602      * @return array Indexed array with message IDs 
    1603      * 
    1604      * @access private 
    1605      */ 
    1606     private function _flatten_threads($mailbox, $thread_tree, $ids=null) 
    1607     { 
    1608         if (empty($thread_tree)) 
    1609             return array(); 
    1610  
    1611         $msg_index = $this->sort_threads($mailbox, $thread_tree, $ids); 
    1612  
    1613         if ($this->sort_order == 'DESC') 
    1614             $msg_index = array_reverse($msg_index); 
    1615  
    1616         // flatten threads array 
    1617         $all_ids = array(); 
    1618         foreach ($msg_index as $root) { 
    1619             $all_ids[] = $root; 
    1620             if (!empty($thread_tree[$root])) { 
    1621                 foreach (array_keys_recursive($thread_tree[$root]) as $val) 
    1622                     $all_ids[] = $val; 
    1623             } 
    1624         } 
    1625  
    1626         return $all_ids; 
     1350        if ($this->search_string && $this->search_threads && $mailbox == $this->mailbox) { 
     1351            $threads = $this->search_set; 
     1352        } 
     1353        else { 
     1354            // get all threads (default sort order) 
     1355            $threads = $this->fetch_threads($mailbox); 
     1356        } 
     1357 
     1358        $this->set_sort_order($sort_field, $sort_order); 
     1359        $this->sort_threads($threads); 
     1360 
     1361        return $threads; 
     1362    } 
     1363 
     1364 
     1365    /** 
     1366     * Sort threaded result, using THREAD=REFS method 
     1367     * 
     1368     * @param rcube_result_thread $threads  Threads result set 
     1369     */ 
     1370    private function sort_threads($threads) 
     1371    { 
     1372        if ($threads->isEmpty()) { 
     1373            return; 
     1374        } 
     1375 
     1376        // THREAD=ORDEREDSUBJECT: sorting by sent date of root message 
     1377        // THREAD=REFERENCES:     sorting by sent date of root message 
     1378        // THREAD=REFS:           sorting by the most recent date in each thread 
     1379 
     1380        if ($this->sort_field && ($this->sort_field != 'date' || $this->get_capability('THREAD') != 'REFS')) { 
     1381            $index = $this->message_index_direct($this->mailbox, $this->sort_field, $this->sort_order, false); 
     1382 
     1383            if (!$index->isEmpty()) { 
     1384                $threads->sort($index); 
     1385            } 
     1386        } 
     1387        else { 
     1388            if ($this->sort_order != $threads->getParameters('ORDER')) { 
     1389                $threads->revert(); 
     1390            } 
     1391        } 
    16271392    } 
    16281393 
     
    16351400     * @param  string  $charset    Search charset 
    16361401     * @param  string  $sort_field Header field to sort by 
    1637      * @return array   search results as list of message IDs 
    16381402     * @access public 
    16391403     */ 
    1640     function search($mailbox='', $str=NULL, $charset=NULL, $sort_field=NULL) 
     1404    function search($mailbox='', $str='ALL', $charset=NULL, $sort_field=NULL) 
    16411405    { 
    16421406        if (!$str) 
    1643             return false; 
     1407            $str = 'ALL'; 
    16441408 
    16451409        if (!strlen($mailbox)) { 
     
    16491413        $results = $this->_search_index($mailbox, $str, $charset, $sort_field); 
    16501414 
    1651         $this->set_search_set($str, $results, $charset, $sort_field, (bool)$this->threading, 
     1415        $this->set_search_set($str, $results, $charset, $sort_field, 
    16521416            $this->threading || $this->search_sorted ? true : false); 
    1653  
    1654         return $results; 
    16551417    } 
    16561418 
     
    16641426     * @param string $sort_field Sorting field 
    16651427     * 
    1666      * @return array   search results as list of message ids 
     1428     * @return rcube_result_index|rcube_result_thread  Search results (UIDs) 
    16671429     * @see rcube_imap::search() 
    16681430     */ 
     
    16751437 
    16761438        if ($this->threading) { 
    1677             $a_messages = $this->conn->thread($mailbox, $this->threading, $criteria, $charset); 
     1439            $threads = $this->conn->thread($mailbox, $this->threading, $criteria, true, $charset); 
    16781440 
    16791441            // Error, try with US-ASCII (RFC5256: SORT/THREAD must support US-ASCII and UTF-8, 
    16801442            // but I've seen that Courier doesn't support UTF-8) 
    1681             if ($a_messages === false && $charset && $charset != 'US-ASCII') 
    1682                 $a_messages = $this->conn->thread($mailbox, $this->threading, 
    1683                     $this->convert_criteria($criteria, $charset), 'US-ASCII'); 
    1684  
    1685             if ($a_messages !== false) { 
    1686                 list ($thread_tree, $msg_depth, $has_children) = $a_messages; 
    1687                 $a_messages = array( 
    1688                     'tree' => $thread_tree, 
    1689                     'depth'=> $msg_depth, 
    1690                     'children' => $has_children 
    1691                 ); 
    1692             } 
    1693  
    1694             return $a_messages; 
     1443            if ($threads->isError() && $charset && $charset != 'US-ASCII') 
     1444                $threads = $this->conn->thread($mailbox, $this->threading, 
     1445                    $this->convert_criteria($criteria, $charset), true, 'US-ASCII'); 
     1446 
     1447            return $threads; 
    16951448        } 
    16961449 
    16971450        if ($sort_field && $this->get_capability('SORT')) { 
    1698             $charset = $charset ? $charset : $this->default_charset; 
    1699             $a_messages = $this->conn->sort($mailbox, $sort_field, $criteria, false, $charset); 
     1451            $charset  = $charset ? $charset : $this->default_charset; 
     1452            $messages = $this->conn->sort($mailbox, $sort_field, $criteria, true, $charset); 
    17001453 
    17011454            // Error, try with US-ASCII (RFC5256: SORT/THREAD must support US-ASCII and UTF-8, 
    17021455            // but I've seen Courier with disabled UTF-8 support) 
    1703             if ($a_messages === false && $charset && $charset != 'US-ASCII') 
    1704                 $a_messages = $this->conn->sort($mailbox, $sort_field, 
    1705                     $this->convert_criteria($criteria, $charset), false, 'US-ASCII'); 
    1706  
    1707             if ($a_messages !== false) { 
     1456            if ($messages->isError() && $charset && $charset != 'US-ASCII') 
     1457                $messages = $this->conn->sort($mailbox, $sort_field, 
     1458                    $this->convert_criteria($criteria, $charset), true, 'US-ASCII'); 
     1459 
     1460            if (!$messages->isError()) { 
    17081461                $this->search_sorted = true; 
    1709                 return $a_messages; 
    1710             } 
    1711         } 
    1712  
    1713         if ($orig_criteria == 'ALL') { 
    1714             $max = $this->_messagecount($mailbox, 'ALL', true, false); 
    1715             $a_messages = $max ? range(1, $max) : array(); 
    1716         } 
    1717         else { 
    1718             $a_messages = $this->conn->search($mailbox, 
    1719                 ($charset ? "CHARSET $charset " : '') . $criteria); 
    1720  
    1721             // Error, try with US-ASCII (some servers may support only US-ASCII) 
    1722             if ($a_messages === false && $charset && $charset != 'US-ASCII') 
    1723                 $a_messages = $this->conn->search($mailbox, 
    1724                     'CHARSET US-ASCII ' . $this->convert_criteria($criteria, $charset)); 
    1725  
    1726             // I didn't found that SEARCH should return sorted IDs 
    1727             if (is_array($a_messages) && !$this->sort_field) 
    1728                 sort($a_messages); 
    1729         } 
     1462                return $messages; 
     1463            } 
     1464        } 
     1465 
     1466        $messages = $this->conn->search($mailbox, 
     1467            ($charset ? "CHARSET $charset " : '') . $criteria, true); 
     1468 
     1469        // Error, try with US-ASCII (some servers may support only US-ASCII) 
     1470        if ($messages->isError() && $charset && $charset != 'US-ASCII') 
     1471            $messages = $this->conn->search($mailbox, 
     1472                'CHARSET US-ASCII ' . $this->convert_criteria($criteria, $charset), true); 
    17301473 
    17311474        $this->search_sorted = false; 
    17321475 
    1733         return $a_messages; 
     1476        return $messages; 
    17341477    } 
    17351478 
     
    17431486     * @param  boolean $ret_uid True if UIDs should be returned 
    17441487     * 
    1745      * @return array   Search results as list of message IDs or UIDs 
    1746      */ 
    1747     function search_once($mailbox='', $str=NULL, $ret_uid=false) 
     1488     * @return rcube_result_index  Search result (UIDs) 
     1489     */ 
     1490    function search_once($mailbox='', $str='ALL') 
    17481491    { 
    17491492        if (!$str) 
    1750             return false; 
     1493            return 'ALL'; 
    17511494 
    17521495        if (!strlen($mailbox)) { 
     
    17541497        } 
    17551498 
    1756         return $this->conn->search($mailbox, $str, $ret_uid); 
     1499        $index = $this->conn->search($mailbox, $str, true); 
     1500 
     1501        return $index; 
    17571502    } 
    17581503 
     
    17921537 
    17931538    /** 
    1794      * Sort thread 
    1795      * 
    1796      * @param string $mailbox     Mailbox name 
    1797      * @param  array $thread_tree Unsorted thread tree (rcube_imap_generic::thread() result) 
    1798      * @param  array $ids         Message IDs if we know what we need (e.g. search result) 
    1799      * 
    1800      * @return array Sorted roots IDs 
    1801      */ 
    1802     function sort_threads($mailbox, $thread_tree, $ids = null) 
    1803     { 
    1804         // THREAD=ORDEREDSUBJECT: sorting by sent date of root message 
    1805         // THREAD=REFERENCES:     sorting by sent date of root message 
    1806         // THREAD=REFS:           sorting by the most recent date in each thread 
    1807  
    1808         // default sorting 
    1809         if (!$this->sort_field || ($this->sort_field == 'date' && $this->threading == 'REFS')) { 
    1810             return array_keys((array)$thread_tree); 
    1811         } 
    1812         // here we'll implement REFS sorting 
    1813         else { 
    1814             if ($mcache = $this->get_mcache_engine()) { 
    1815                 $a_index = $mcache->get_index($mailbox, $this->sort_field, 'ASC'); 
    1816                 if (is_array($a_index)) { 
    1817                     $a_index = array_keys($a_index); 
    1818                     // now we must remove IDs that doesn't exist in $ids 
    1819                     if (!empty($ids)) 
    1820                         $a_index = array_intersect($a_index, $ids); 
    1821                 } 
    1822             } 
    1823             // use SORT command 
    1824             else if ($this->get_capability('SORT') && 
    1825                 ($a_index = $this->conn->sort($mailbox, $this->sort_field, 
    1826                     !empty($ids) ? $ids : ($this->skip_deleted ? 'UNDELETED' : ''))) !== false 
    1827             ) { 
    1828                 // do nothing 
    1829             } 
    1830             else { 
    1831                 // fetch specified headers for all messages and sort them 
    1832                 $a_index = $this->conn->fetchHeaderIndex($mailbox, !empty($ids) ? $ids : "1:*", 
    1833                     $this->sort_field, $this->skip_deleted); 
    1834  
    1835                 // return unsorted tree if we've got no index data 
    1836                 if (!empty($a_index)) { 
    1837                     asort($a_index); // ASC 
    1838                     $a_index = array_values($a_index); 
    1839                 } 
    1840             } 
    1841  
    1842             if (empty($a_index)) 
    1843                 return array_keys((array)$thread_tree); 
    1844  
    1845             return $this->_sort_thread_refs($thread_tree, $a_index); 
    1846         } 
    1847     } 
    1848  
    1849  
    1850     /** 
    1851      * THREAD=REFS sorting implementation 
    1852      * 
    1853      * @param  array $tree   Thread tree array (message identifiers as keys) 
    1854      * @param  array $index  Array of sorted message identifiers 
    1855      * 
    1856      * @return array   Array of sorted roots messages 
    1857      */ 
    1858     private function _sort_thread_refs($tree, $index) 
    1859     { 
    1860         if (empty($tree)) 
    1861             return array(); 
    1862  
    1863         $index = array_combine(array_values($index), $index); 
    1864  
    1865         // assign roots 
    1866         foreach ($tree as $idx => $val) { 
    1867             $index[$idx] = $idx; 
    1868             if (!empty($val)) { 
    1869                 $idx_arr = array_keys_recursive($tree[$idx]); 
    1870                 foreach ($idx_arr as $subidx) 
    1871                     $index[$subidx] = $idx; 
    1872             } 
    1873         } 
    1874  
    1875         $index = array_values($index); 
    1876  
    1877         // create sorted array of roots 
    1878         $msg_index = array(); 
    1879         if ($this->sort_order != 'DESC') { 
    1880             foreach ($index as $idx) 
    1881                 if (!isset($msg_index[$idx])) 
    1882                     $msg_index[$idx] = $idx; 
    1883             $msg_index = array_values($msg_index); 
    1884         } 
    1885         else { 
    1886             for ($x=count($index)-1; $x>=0; $x--) 
    1887                 if (!isset($msg_index[$index[$x]])) 
    1888                     $msg_index[$index[$x]] = $index[$x]; 
    1889             $msg_index = array_reverse($msg_index); 
    1890         } 
    1891  
    1892         return $msg_index; 
    1893     } 
    1894  
    1895  
    1896     /** 
    18971539     * Refresh saved search set 
    18981540     * 
     
    19011543    function refresh_search() 
    19021544    { 
    1903         if (!empty($this->search_string)) 
    1904             $this->search_set = $this->search('', $this->search_string, $this->search_charset, 
    1905                 $this->search_sort_field, $this->search_threads, $this->search_sorted); 
     1545        if (!empty($this->search_string)) { 
     1546            $this->search('', $this->search_string, $this->search_charset, $this->search_sort_field); 
     1547        } 
    19061548 
    19071549        return $this->get_search_set(); 
     
    19101552 
    19111553    /** 
    1912      * Check if the given message ID is part of the current search set 
    1913      * 
    1914      * @param string $msgid Message id 
     1554     * Check if the given message UID is part of the current search set 
     1555     * 
     1556     * @param string $msgid Message UID 
     1557     * 
    19151558     * @return boolean True on match or if no search request is stored 
    19161559     */ 
    1917     function in_searchset($msgid) 
     1560    function in_searchset($uid) 
    19181561    { 
    19191562        if (!empty($this->search_string)) { 
    1920             if ($this->search_threads) 
    1921                 return isset($this->search_set['depth']["$msgid"]); 
    1922             else 
    1923                 return in_array("$msgid", (array)$this->search_set, true); 
    1924         } 
    1925         else 
    1926             return true; 
     1563            return $this->search_set->exists($uid); 
     1564        } 
     1565        return true; 
    19271566    } 
    19281567 
     
    19821621        // message doesn't exist? 
    19831622        if (empty($headers)) 
    1984             return null;  
     1623            return null; 
    19851624 
    19861625        // structure might be cached 
     
    26572296                if ($this->search_threads || $all_mode) 
    26582297                    $this->refresh_search(); 
    2659                 else { 
    2660                     $a_uids = explode(',', $uids); 
    2661                     foreach ($a_uids as $uid) 
    2662                         $a_mids[] = $this->uid2id($uid, $from_mbox); 
    2663                     $this->search_set = array_diff($this->search_set, $a_mids); 
    2664                 } 
    2665                 unset($a_mids); 
    2666                 unset($a_uids); 
     2298                else 
     2299                    $this->search_set->filter(explode(',', $uids)); 
    26672300            } 
    26682301 
     
    27572390                if ($this->search_threads || $all_mode) 
    27582391                    $this->refresh_search(); 
    2759                 else { 
    2760                     $a_uids = explode(',', $uids); 
    2761                     foreach ($a_uids as $uid) 
    2762                         $a_mids[] = $this->uid2id($uid, $mailbox); 
    2763                     $this->search_set = array_diff($this->search_set, $a_mids); 
    2764                     unset($a_uids); 
    2765                     unset($a_mids); 
    2766                 } 
     2392                else 
     2393                    $this->search_set->filter(explode(',', $uids)); 
    27672394            } 
    27682395 
     
    28832510            } 
    28842511            // get UIDs from current search set 
    2885             // @TODO: skip fetchUIDs() and work with IDs instead of UIDs (?) 
    28862512            else { 
    2887                 if ($this->search_threads) 
    2888                     $uids = $this->conn->fetchUIDs($mailbox, array_keys($this->search_set['depth'])); 
    2889                 else 
    2890                     $uids = $this->conn->fetchUIDs($mailbox, $this->search_set); 
    2891  
    2892                 // save ID-to-UID mapping in local cache 
    2893                 if (is_array($uids)) 
    2894                     foreach ($uids as $id => $uid) 
    2895                         $this->uid_id_map[$mailbox][$uid] = $id; 
    2896  
    2897                 $uids = join(',', $uids); 
     2513                $uids = join(',', $this->search_set->get()); 
    28982514            } 
    28992515        } 
     
    29082524        return array($uids, (bool) $all); 
    29092525    } 
    2910  
    2911  
    2912     /** 
    2913      * Translate UID to message ID 
    2914      * 
    2915      * @param int    $uid     Message UID 
    2916      * @param string $mailbox Mailbox name 
    2917      * 
    2918      * @return int   Message ID 
    2919      */ 
    2920     function get_id($uid, $mailbox=null) 
    2921     { 
    2922         if (!strlen($mailbox)) { 
    2923             $mailbox = $this->mailbox; 
    2924         } 
    2925  
    2926         return $this->uid2id($uid, $mailbox); 
    2927     } 
    2928  
    2929  
    2930     /** 
    2931      * Translate message number to UID 
    2932      * 
    2933      * @param int    $id      Message ID 
    2934      * @param string $mailbox Mailbox name 
    2935      * 
    2936      * @return int   Message UID 
    2937      */ 
    2938     function get_uid($id, $mailbox=null) 
    2939     { 
    2940         if (!strlen($mailbox)) { 
    2941             $mailbox = $this->mailbox; 
    2942         } 
    2943  
    2944         return $this->id2uid($id, $mailbox); 
    2945     } 
    2946  
    29472526 
    29482527 
     
    35933172 
    35943173        // add (E)SEARCH result for ALL UNDELETED query 
    3595         if (!empty($this->icache['undeleted_idx']) && $this->icache['undeleted_idx'][0] == $mailbox) { 
    3596             $data['ALL_UNDELETED']   = $this->icache['undeleted_idx'][1]; 
    3597             $data['COUNT_UNDELETED'] = $this->icache['undeleted_idx'][2]; 
     3174        if (!empty($this->icache['undeleted_idx']) 
     3175            && $this->icache['undeleted_idx']->getParameters('MAILBOX') == $mailbox 
     3176        ) { 
     3177            $data['UNDELETED'] = $this->icache['undeleted_idx']; 
    35983178        } 
    35993179 
     
    42693849 
    42703850 
    4271     /** 
    4272      * Convert body charset to RCMAIL_CHARSET according to the ctype_parameters 
    4273      * 
    4274      * @param string $body        Part body to decode 
    4275      * @param string $ctype_param Charset to convert from 
    4276      * @return string Content converted to internal charset 
    4277      */ 
    4278     function charset_decode($body, $ctype_param) 
    4279     { 
    4280         if (is_array($ctype_param) && !empty($ctype_param['charset'])) 
    4281             return rcube_charset_convert($body, $ctype_param['charset']); 
    4282  
    4283         // defaults to what is specified in the class header 
    4284         return rcube_charset_convert($body,  $this->default_charset); 
    4285     } 
    4286  
    4287  
    42883851    /* -------------------------------- 
    42893852     *         private methods 
     
    42973860     * @access private 
    42983861     */ 
    4299     private function _set_sort_order($sort_field, $sort_order) 
     3862    private function set_sort_order($sort_field, $sort_order) 
    43003863    { 
    43013864        if ($sort_field != null) 
     
    43673930 
    43683931    /** 
    4369      * Finds message sequence ID for specified UID 
    4370      * 
    4371      * @param int    $uid      Message UID 
     3932     * Find UID of the specified message sequence ID 
     3933     * 
     3934     * @param int    $id       Message (sequence) ID 
    43723935     * @param string $mailbox  Mailbox name 
    4373      * @param bool   $force    True to skip cache 
    4374      * 
    4375      * @return int Message (sequence) ID 
    4376      */ 
    4377     function uid2id($uid, $mailbox = null, $force = false) 
     3936     * 
     3937     * @return int Message UID 
     3938     */ 
     3939    function id2uid($id, $mailbox = null) 
    43783940    { 
    43793941        if (!strlen($mailbox)) { 
     
    43813943        } 
    43823944 
    4383         if (!empty($this->uid_id_map[$mailbox][$uid])) { 
    4384             return $this->uid_id_map[$mailbox][$uid]; 
    4385         } 
    4386  
    4387         if (!$force && ($mcache = $this->get_mcache_engine())) 
    4388             $id = $mcache->uid2id($mailbox, $uid); 
    4389  
    4390         if (empty($id)) 
    4391             $id = $this->conn->UID2ID($mailbox, $uid); 
    4392  
    4393         $this->uid_id_map[$mailbox][$uid] = $id; 
    4394  
    4395         return $id; 
    4396     } 
    4397  
    4398  
    4399     /** 
    4400      * Find UID of the specified message sequence ID 
    4401      * 
    4402      * @param int    $id       Message (sequence) ID 
    4403      * @param string $mailbox  Mailbox name 
    4404      * @param bool   $force    True to skip cache 
    4405      * 
    4406      * @return int Message UID 
    4407      */ 
    4408     function id2uid($id, $mailbox = null, $force = false) 
    4409     { 
    4410         if (!strlen($mailbox)) { 
    4411             $mailbox = $this->mailbox; 
    4412         } 
    4413  
    44143945        if ($uid = array_search($id, (array)$this->uid_id_map[$mailbox])) { 
    44153946            return $uid; 
    44163947        } 
    44173948 
    4418         if (!$force && ($mcache = $this->get_mcache_engine())) 
    4419             $uid = $mcache->id2uid($mailbox, $id); 
    4420  
    4421         if (empty($uid)) 
    4422             $uid = $this->conn->ID2UID($mailbox, $id); 
     3949        $uid = $this->conn->ID2UID($mailbox, $id); 
    44233950 
    44243951        $this->uid_id_map[$mailbox][$uid] = $id; 
     
    47064233class rcube_header_sorter 
    47074234{ 
    4708     private $seqs = array(); 
    47094235    private $uids = array(); 
    47104236 
     
    47134239     * Set the predetermined sort order. 
    47144240     * 
    4715      * @param array $index  Numerically indexed array of IMAP ID or UIDs 
    4716      * @param bool  $is_uid Set to true if $index contains UIDs 
    4717      */ 
    4718     function set_index($index, $is_uid = false) 
     4241     * @param array $index  Numerically indexed array of IMAP UIDs 
     4242     */ 
     4243    function set_index($index) 
    47194244    { 
    47204245        $index = array_flip($index); 
    47214246 
    4722         if ($is_uid) 
    4723             $this->uids = $index; 
    4724         else 
    4725             $this->seqs = $index; 
     4247        $this->uids = $index; 
    47264248    } 
    47274249 
     
    47334255    function sort_headers(&$headers) 
    47344256    { 
    4735         if (!empty($this->uids)) 
    4736             uksort($headers, array($this, "compare_uids")); 
    4737         else 
    4738             uasort($headers, array($this, "compare_seqnums")); 
    4739     } 
    4740  
    4741     /** 
    4742      * Sort method called by uasort() 
    4743      * 
    4744      * @param rcube_mail_header $a 
    4745      * @param rcube_mail_header $b 
    4746      */ 
    4747     function compare_seqnums($a, $b) 
    4748     { 
    4749         // First get the sequence number from the header object (the 'id' field). 
    4750         $seqa = $a->id; 
    4751         $seqb = $b->id; 
    4752  
    4753         // then find each sequence number in my ordered list 
    4754         $posa = isset($this->seqs[$seqa]) ? intval($this->seqs[$seqa]) : -1; 
    4755         $posb = isset($this->seqs[$seqb]) ? intval($this->seqs[$seqb]) : -1; 
    4756  
    4757         // return the relative position as the comparison value 
    4758         return $posa - $posb; 
     4257        uksort($headers, array($this, "compare_uids")); 
    47594258    } 
    47604259 
  • trunk/roundcubemail/program/include/rcube_imap_cache.php

    r5357 r5557  
    109109 
    110110    /** 
    111      * Return (sorted) messages index. 
     111     * Return (sorted) messages index (UIDs). 
    112112     * If index doesn't exist or is invalid, will be updated. 
    113113     * 
     
    123123        if (empty($this->icache[$mailbox])) 
    124124            $this->icache[$mailbox] = array(); 
    125  
     125console('cache::get_index'); 
    126126        $sort_order = strtoupper($sort_order) == 'ASC' ? 'ASC' : 'DESC'; 
    127127 
     
    129129        if (array_key_exists('index', $this->icache[$mailbox])) { 
    130130            // The index was fetched from database already, but not validated yet 
    131             if (!array_key_exists('result', $this->icache[$mailbox]['index'])) { 
     131            if (!array_key_exists('object', $this->icache[$mailbox]['index'])) { 
    132132                $index = $this->icache[$mailbox]['index']; 
    133133            } 
    134134            // We've got a valid index 
    135             else if ($sort_field == 'ANY' || $this->icache[$mailbox]['index']['sort_field'] == $sort_field 
    136             ) { 
    137                 if ($this->icache[$mailbox]['index']['sort_order'] == $sort_order) 
    138                     return $this->icache[$mailbox]['index']['result']; 
    139                 else 
    140                     return array_reverse($this->icache[$mailbox]['index']['result'], true); 
     135            else if ($sort_field == 'ANY' || $this->icache[$mailbox]['index']['sort_field'] == $sort_field) { 
     136                $result = $this->icache[$mailbox]['index']['object']; 
     137                if ($result->getParameters('ORDER') != $sort_order) { 
     138                    $result->revert(); 
     139                } 
     140                return $result; 
    141141            } 
    142142        } 
     
    174174                $is_valid = $this->validate($mailbox, $index, $exists); 
    175175            } 
    176  
     176console("valid:".$is_valid); 
    177177            if ($is_valid) { 
    178                 // build index, assign sequence IDs to unique IDs 
    179                 $data = array_combine($index['seq'], $index['uid']); 
     178                $data = $index['object']; 
    180179                // revert the order if needed 
    181                 if ($index['sort_order'] != $sort_order) 
    182                     $data = array_reverse($data, true); 
     180                if ($data->getParameters('ORDER') != $sort_order) { 
     181                    $data->revert(); 
     182                } 
    183183            } 
    184184        } 
     
    202202 
    203203            // insert/update 
    204             $this->add_index_row($mailbox, $sort_field, $sort_order, $data, $mbox_data, 
    205                 $exists, $index['modseq']); 
     204            $this->add_index_row($mailbox, $sort_field, $data, $mbox_data, $exists, $index['modseq']); 
    206205        } 
    207206 
    208207        $this->icache[$mailbox]['index'] = array( 
    209             'result'     => $data, 
     208            'object'     => $data, 
    210209            'sort_field' => $sort_field, 
    211             'sort_order' => $sort_order, 
    212210            'modseq'     => !empty($index['modseq']) ? $index['modseq'] : $mbox_data['HIGHESTMODSEQ'] 
    213211        ); 
     
    234232        // Seek in internal cache 
    235233        if (array_key_exists('thread', $this->icache[$mailbox])) { 
    236             return array( 
    237                 $this->icache[$mailbox]['thread']['tree'], 
    238                 $this->icache[$mailbox]['thread']['depth'], 
    239                 $this->icache[$mailbox]['thread']['children'], 
    240             ); 
     234            return $this->icache[$mailbox]['thread']['object']; 
    241235        } 
    242236 
     
    251245        } 
    252246 
    253         $data = null; 
    254  
    255247        // Entry exist, check cache status 
    256248        if (!empty($index)) { 
     
    270262            if ($mbox_data['EXISTS']) { 
    271263                // get all threads (default sort order) 
    272                 list ($thread_tree, $msg_depth, $has_children) = $this->imap->fetch_threads($mailbox, true); 
    273             } 
    274  
    275             $index = array( 
    276                 'tree'     => !empty($thread_tree) ? $thread_tree : array(), 
    277                 'depth'    => !empty($msg_depth) ? $msg_depth : array(), 
    278                 'children' => !empty($has_children) ? $has_children : array(), 
    279             ); 
     264                $threads = $this->imap->fetch_threads($mailbox, true); 
     265            } 
     266            else { 
     267                $threads = new rcube_imap_result($mailbox, ''); 
     268            } 
     269 
     270            $index['object'] = $threads; 
    280271 
    281272            // insert/update 
    282             $this->add_thread_row($mailbox, $index, $mbox_data, $exists); 
     273            $this->add_thread_row($mailbox, $threads, $mbox_data, $exists); 
    283274        } 
    284275 
    285276        $this->icache[$mailbox]['thread'] = $index; 
    286277 
    287         return array($index['tree'], $index['depth'], $index['children']); 
     278        return $index['object']; 
    288279    } 
    289280 
     
    293284     * 
    294285     * @param string $mailbox  Folder name 
    295      * @param array  $msgs     Message sequence numbers 
    296      * @param bool   $is_uid   True if $msgs contains message UIDs 
     286     * @param array  $msgs     Message UIDs 
    297287     * 
    298288     * @return array The list of messages (rcube_mail_header) indexed by UID 
    299289     */ 
    300     function get_messages($mailbox, $msgs = array(), $is_uid = true) 
     290    function get_messages($mailbox, $msgs = array()) 
    301291    { 
    302292        if (empty($msgs)) { 
    303293            return array(); 
    304         } 
    305  
    306         // @TODO: it would be nice if we could work with UIDs only 
    307         // then index would be not needed. For now we need it to 
    308         // map id to uid here and to update message id for cached message 
    309  
    310         // Convert IDs to UIDs 
    311         $index = $this->get_index($mailbox, 'ANY'); 
    312         if (!$is_uid) { 
    313             foreach ($msgs as $idx => $msgid) 
    314                 if ($uid = $index[$msgid]) 
    315                     $msgs[$idx] = $uid; 
    316294        } 
    317295 
     
    335313            $result[$uid]->body = null; 
    336314 
    337             // update message ID according to index data 
    338             if (!empty($index) && ($id = array_search($uid, $index))) 
    339                 $result[$uid]->id = $id; 
    340  
    341315            if (!empty($result[$uid])) { 
    342316                unset($msgs[$uid]); 
     
    346320        // Fetch not found messages from IMAP server 
    347321        if (!empty($msgs)) { 
    348             $messages = $this->imap->fetch_headers($mailbox, array_keys($msgs), true, true); 
     322            $messages = $this->imap->fetch_headers($mailbox, array_keys($msgs), false, true); 
    349323 
    350324            // Insert to DB and add to result list 
     
    392366            $message = $this->build_message($sql_arr); 
    393367            $found   = true; 
    394  
    395             // update message ID according to index data 
    396             $index = $this->get_index($mailbox, 'ANY'); 
    397             if (!empty($index) && ($id = array_search($uid, $index))) 
    398                 $message->id = $id; 
    399368        } 
    400369 
     
    618587 
    619588    /** 
    620      * @param string $mailbox Folder name 
    621      * @param int    $id      Message (sequence) ID 
    622      * 
    623      * @return int Message UID 
    624      */ 
    625     function id2uid($mailbox, $id) 
    626     { 
    627         if (!empty($this->icache['pending_index_update'])) 
    628             return null; 
    629  
    630         // get index if it exists 
    631         $index = $this->get_index($mailbox, 'ANY', null, true); 
    632  
    633         return $index[$id]; 
    634     } 
    635  
    636  
    637     /** 
    638      * @param string $mailbox Folder name 
    639      * @param int    $uid     Message UID 
    640      * 
    641      * @return int Message (sequence) ID 
    642      */ 
    643     function uid2id($mailbox, $uid) 
    644     { 
    645         if (!empty($this->icache['pending_index_update'])) 
    646             return null; 
    647  
    648         // get index if it exists 
    649         $index = $this->get_index($mailbox, 'ANY', null, true); 
    650  
    651         return array_search($uid, (array)$index); 
    652     } 
    653  
    654     /** 
    655589     * Fetches index data from database 
    656590     */ 
    657591    private function get_index_row($mailbox) 
    658592    { 
     593console('cache::get_index_row'); 
    659594        // Get index from DB 
    660595        $sql_result = $this->db->query( 
     
    666601 
    667602        if ($sql_arr = $this->db->fetch_assoc($sql_result)) { 
    668             $data = explode('@', $sql_arr['data']); 
     603            $data  = explode('@', $sql_arr['data']); 
     604            $index = @unserialize($data[0]); 
     605            unset($data[0]); 
     606 
     607            if (empty($index)) { 
     608                $index = new rcube_result_index($mailbox); 
     609            } 
    669610 
    670611            return array( 
    671612                'valid'      => $sql_arr['valid'], 
    672                 'seq'        => explode(',', $data[0]), 
    673                 'uid'        => explode(',', $data[1]), 
    674                 'sort_field' => $data[2], 
    675                 'sort_order' => $data[3], 
    676                 'deleted'    => $data[4], 
    677                 'validity'   => $data[5], 
    678                 'uidnext'    => $data[6], 
    679                 'modseq'     => $data[7], 
     613                'object'     => $index, 
     614                'sort_field' => $data[1], 
     615                'deleted'    => $data[2], 
     616                'validity'   => $data[3], 
     617                'uidnext'    => $data[4], 
     618                'modseq'     => $data[5], 
    680619            ); 
    681620        } 
     
    699638 
    700639        if ($sql_arr = $this->db->fetch_assoc($sql_result)) { 
    701             $data = explode('@', $sql_arr['data']); 
    702  
    703             // Uncompress data, see add_thread_row() 
    704   //          $data[0] = str_replace(array('*', '^', '#'), array(';a:0:{}', 'i:', ';a:1:'), $data[0]); 
    705             $data[0] = unserialize($data[0]); 
    706  
    707             // build 'depth' and 'children' arrays 
    708             $depth = $children = array(); 
    709             $this->build_thread_data($data[0], $depth, $children); 
     640            $data   = explode('@', $sql_arr['data']); 
     641            $thread = @unserialize($data[0]); 
     642            unset($data[0]); 
     643 
     644            if (empty($thread)) { 
     645                $thread = new rcube_imap_result($mailbox); 
     646            } 
    710647 
    711648            return array( 
    712                 'tree'     => $data[0], 
    713                 'depth'    => $depth, 
    714                 'children' => $children, 
     649                'object'   => $thread, 
    715650                'deleted'  => $data[1], 
    716651                'validity' => $data[2], 
     
    726661     * Saves index data into database 
    727662     */ 
    728     private function add_index_row($mailbox, $sort_field, $sort_order, 
    729         $data = array(), $mbox_data = array(), $exists = false, $modseq = null) 
     663    private function add_index_row($mailbox, $sort_field, 
     664        $data, $mbox_data = array(), $exists = false, $modseq = null) 
    730665    { 
    731666        $data = array( 
    732             implode(',', array_keys($data)), 
    733             implode(',', array_values($data)), 
     667            serialize($data), 
    734668            $sort_field, 
    735             $sort_order, 
    736669            (int) $this->skip_deleted, 
    737670            (int) $mbox_data['UIDVALIDITY'], 
     
    760693     * Saves thread data into database 
    761694     */ 
    762     private function add_thread_row($mailbox, $data = array(), $mbox_data = array(), $exists = false) 
    763     { 
    764         $tree = serialize($data['tree']); 
    765         // This significantly reduces data length 
    766 //        $tree = str_replace(array(';a:0:{}', 'i:', ';a:1:'), array('*', '^', '#'), $tree); 
    767  
     695    private function add_thread_row($mailbox, $data, $mbox_data = array(), $exists = false) 
     696    { 
    768697        $data = array( 
    769             $tree, 
     698            serialize($data), 
    770699            (int) $this->skip_deleted, 
    771700            (int) $mbox_data['UIDVALIDITY'], 
     
    795724    private function validate($mailbox, $index, &$exists = true) 
    796725    { 
    797         $is_thread = isset($index['tree']); 
     726        $object    = $index['object']; 
     727        $is_thread = is_a($object, 'rcube_result_thread'); 
    798728 
    799729        // Get mailbox data (UIDVALIDITY, counters, etc.) for status check 
     
    815745        // Folder is empty but cache isn't 
    816746        if (empty($mbox_data['EXISTS'])) { 
    817             if (!empty($index['seq']) || !empty($index['tree'])) { 
     747            if (!$object->isEmpty()) { 
    818748                $this->clear($mailbox); 
    819749                $exists = false; 
     
    822752        } 
    823753        // Folder is not empty but cache is 
    824         else if (empty($index['seq']) && empty($index['tree'])) { 
     754        else if ($object->isEmpty()) { 
    825755            unset($this->icache[$mailbox][$is_thread ? 'thread' : 'index']); 
    826756            return false; 
     
    829759        // Validation flag 
    830760        if (!$is_thread && empty($index['valid'])) { 
    831             unset($this->icache[$mailbox][$is_thread ? 'thread' : 'index']); 
     761            unset($this->icache[$mailbox]['index']); 
    832762            return false; 
    833763        } 
     
    854784        if ($is_thread) { 
    855785            // check messages number... 
    856             if (!$this->skip_deleted && $mbox_data['EXISTS'] != @max(array_keys($index['depth']))) { 
     786            if (!$this->skip_deleted && $mbox_data['EXISTS'] != $object->countMessages()) { 
    857787                return false; 
    858788            } 
     
    863793        if (!empty($this->skip_deleted)) { 
    864794            // compare counts if available 
    865             if ($mbox_data['COUNT_UNDELETED'] != null 
    866                 && $mbox_data['COUNT_UNDELETED'] != count($index['uid'])) { 
     795            if (!empty($mbox_data['UNDELETED']) 
     796                && $mbox_data['UNDELETED']->count() != $object->count() 
     797            ) { 
    867798                return false; 
    868799            } 
    869800            // compare UID sets 
    870             if ($mbox_data['ALL_UNDELETED'] != null) { 
    871                 $uids_new = rcube_imap_generic::uncompressMessageSet($mbox_data['ALL_UNDELETED']); 
    872                 $uids_old = $index['uid']; 
     801            if (!empty($mbox_data['UNDELETED'])) { 
     802                $uids_new = $mbox_data['UNDELETED']->get(); 
     803                $uids_old = $object->get(); 
    873804 
    874805                if (count($uids_new) != count($uids_old)) { 
     
    885816                // get all undeleted messages excluding cached UIDs 
    886817                $ids = $this->imap->search_once($mailbox, 'ALL UNDELETED NOT UID '. 
    887                     rcube_imap_generic::compressMessageSet($index['uid'])); 
    888  
    889                 if (!empty($ids)) { 
     818                    rcube_imap_generic::compressMessageSet($object->get())); 
     819 
     820                if (!$ids->isEmpty()) { 
    890821                    return false; 
    891822                } 
     
    894825        else { 
    895826            // check messages number... 
    896             if ($mbox_data['EXISTS'] != max($index['seq'])) { 
     827            if ($mbox_data['EXISTS'] != $object->count()) { 
    897828                return false; 
    898829            } 
    899830            // ... and max UID 
    900             if (max($index['uid']) != $this->imap->id2uid($mbox_data['EXISTS'], $mailbox, true)) { 
     831            if ($object->max() != $this->imap->id2uid($mbox_data['EXISTS'], $mailbox, true)) { 
    901832                return false; 
    902833            } 
     
    1061992 
    1062993        $sort_field = $index['sort_field']; 
    1063         $sort_order = $index['sort_order']; 
     994        $sort_order = $index['object']->getParameters('ORDER'); 
    1064995        $exists     = true; 
    1065996 
     
    10701001        } 
    10711002        else { 
    1072             $data = array_combine($index['seq'], $index['uid']); 
     1003            $data = $index['object']; 
    10731004        } 
    10741005 
    10751006        // update index and/or HIGHESTMODSEQ value 
    1076         $this->add_index_row($mailbox, $sort_field, $sort_order, $data, $mbox_data, $exists); 
     1007        $this->add_index_row($mailbox, $sort_field, $data, $mbox_data, $exists); 
    10771008 
    10781009        // update internal cache for get_index() 
    1079         $this->icache[$mailbox]['index']['result'] = $data; 
     1010        $this->icache[$mailbox]['index']['object'] = $data; 
    10801011    } 
    10811012 
     
    11041035 
    11051036    /** 
    1106      * Creates 'depth' and 'children' arrays from stored thread 'tree' data. 
    1107      */ 
    1108     private function build_thread_data($data, &$depth, &$children, $level = 0) 
    1109     { 
    1110         foreach ((array)$data as $key => $val) { 
    1111             $children[$key] = !empty($val); 
    1112             $depth[$key] = $level; 
    1113             if (!empty($val)) 
    1114                 $this->build_thread_data($val, $depth, $children, $level + 1); 
    1115         } 
    1116     } 
    1117  
    1118  
    1119     /** 
    11201037     * Saves message stored in internal cache 
    11211038     */ 
     
    11711088    private function get_index_data($mailbox, $sort_field, $sort_order, $mbox_data = array()) 
    11721089    { 
    1173         $data = array(); 
    1174  
    11751090        if (empty($mbox_data)) { 
    11761091            $mbox_data = $this->imap->mailbox_data($mailbox); 
    11771092        } 
    11781093 
    1179         // Prevent infinite loop. 
    1180         // It happens when rcube_imap::message_index_direct() is called. 
    1181         // There id2uid() is called which will again call get_index() and so on. 
    1182         if (!$sort_field && !$this->skip_deleted) 
    1183             $this->icache['pending_index_update'] = true; 
    1184  
    11851094        if ($mbox_data['EXISTS']) { 
    11861095            // fetch sorted sequence numbers 
    1187             $data_seq = $this->imap->message_index_direct($mailbox, $sort_field, $sort_order); 
    1188             // fetch UIDs 
    1189             if (!empty($data_seq)) { 
    1190                 // Seek in internal cache 
    1191                 if (array_key_exists('index', (array)$this->icache[$mailbox]) 
    1192                     && array_key_exists('result', (array)$this->icache[$mailbox]['index']) 
    1193                 ) 
    1194                     $data_uid = $this->icache[$mailbox]['index']['result']; 
    1195                 else 
    1196                     $data_uid = $this->imap->conn->fetchUIDs($mailbox, $data_seq); 
    1197  
    1198                 // build index 
    1199                 if (!empty($data_uid)) { 
    1200                     foreach ($data_seq as $seq) 
    1201                         if ($uid = $data_uid[$seq]) 
    1202                             $data[$seq] = $uid; 
    1203                 } 
    1204             } 
    1205         } 
    1206  
    1207         // Reset internal flags 
    1208         $this->icache['pending_index_update'] = false; 
    1209  
    1210         return $data; 
     1096            $index = $this->imap->message_index_direct($mailbox, $sort_field, $sort_order); 
     1097        } 
     1098        else { 
     1099            $index = new rcube_result_index($mailbox, '* SORT'); 
     1100        } 
     1101 
     1102        return $index; 
    12111103    } 
    12121104} 
  • trunk/roundcubemail/program/include/rcube_imap_generic.php

    r5400 r5557  
    2626 
    2727*/ 
    28  
    2928 
    3029/** 
     
    12191218        // Invoke SEARCH as a fallback 
    12201219        $index = $this->search($mailbox, 'ALL UNSEEN', false, array('COUNT')); 
    1221         if (is_array($index)) { 
    1222             return (int) $index['COUNT']; 
     1220        if (!$index->isError()) { 
     1221            return $index->count(); 
    12231222        } 
    12241223 
     
    12941293    } 
    12951294 
    1296     function sort($mailbox, $field, $add='', $is_uid=FALSE, $encoding = 'US-ASCII') 
    1297     { 
     1295    /** 
     1296     * Executes SORT command 
     1297     * 
     1298     * @param string $mailbox    Mailbox name 
     1299     * @param string $field      Field to sort by (ARRIVAL, CC, DATE, FROM, SIZE, SUBJECT, TO) 
     1300     * @param string $add        Searching criteria 
     1301     * @param bool   $return_uid Enables UID SORT usage 
     1302     * @param string $encoding   Character set 
     1303     * 
     1304     * @return rcube_result_index Response data 
     1305     */ 
     1306    function sort($mailbox, $field, $add='', $return_uid=false, $encoding = 'US-ASCII') 
     1307    { 
     1308        require_once dirname(__FILE__) . '/rcube_result_index.php'; 
     1309 
    12981310        $field = strtoupper($field); 
    12991311        if ($field == 'INTERNALDATE') { 
     
    13051317 
    13061318        if (!$fields[$field]) { 
    1307             return false; 
     1319            return new rcube_result_index($mailbox); 
    13081320        } 
    13091321 
    13101322        if (!$this->select($mailbox)) { 
    1311             return false; 
     1323            return new rcube_result_index($mailbox); 
    13121324        } 
    13131325 
     
    13161328            $add = $this->compressMessageSet($add); 
    13171329 
    1318         list($code, $response) = $this->execute($is_uid ? 'UID SORT' : 'SORT', 
     1330        list($code, $response) = $this->execute($return_uid ? 'UID SORT' : 'SORT', 
    13191331            array("($field)", $encoding, 'ALL' . (!empty($add) ? ' '.$add : ''))); 
    13201332 
    1321         if ($code == self::ERROR_OK) { 
    1322             // remove prefix and unilateral untagged server responses 
    1323             $response = substr($response, stripos($response, '* SORT') + 7); 
    1324             if ($pos = strpos($response, '*')) { 
    1325                 $response = substr($response, 0, $pos); 
    1326             } 
    1327             return preg_split('/[\s\r\n]+/', $response, -1, PREG_SPLIT_NO_EMPTY); 
    1328         } 
    1329  
    1330         return false; 
    1331     } 
    1332  
    1333     function fetchHeaderIndex($mailbox, $message_set, $index_field='', $skip_deleted=true, $uidfetch=false) 
     1333        if ($code != self::ERROR_OK) { 
     1334            $response = null; 
     1335        } 
     1336 
     1337        return new rcube_result_index($mailbox, $response); 
     1338    } 
     1339 
     1340    /** 
     1341     * Simulates SORT command by using FETCH and sorting. 
     1342     * 
     1343     * @param string       $mailbox      Mailbox name 
     1344     * @param string|array $message_set  Searching criteria (list of messages to return) 
     1345     * @param string       $index_field  Field to sort by (ARRIVAL, CC, DATE, FROM, SIZE, SUBJECT, TO) 
     1346     * @param bool         $skip_deleted Makes that DELETED messages will be skipped 
     1347     * @param bool         $uidfetch     Enables UID FETCH usage 
     1348     * @param bool         $return_uid   Enables returning UIDs instead of IDs 
     1349     * 
     1350     * @return rcube_result_index Response data 
     1351     */ 
     1352    function index($mailbox, $message_set, $index_field='', $skip_deleted=true, 
     1353        $uidfetch=false, $return_uid=false) 
     1354    { 
     1355        require_once dirname(__FILE__) . '/rcube_result_index.php'; 
     1356 
     1357        $msg_index = $this->fetchHeaderIndex($mailbox, $message_set, 
     1358            $index_field, $skip_deleted, $uidfetch, $return_uid); 
     1359 
     1360        if (!empty($msg_index)) { 
     1361            asort($msg_index); // ASC 
     1362            $msg_index = array_keys($msg_index); 
     1363            $msg_index = '* SEARCH ' . implode(' ', $msg_index); 
     1364        } 
     1365        else { 
     1366            $msg_index = is_array($msg_index) ? '* SEARCH' : null; 
     1367        } 
     1368 
     1369        return new rcube_result_index($mailbox, $msg_index); 
     1370    } 
     1371 
     1372    function fetchHeaderIndex($mailbox, $message_set, $index_field='', $skip_deleted=true, 
     1373        $uidfetch=false, $return_uid=false) 
    13341374    { 
    13351375        if (is_array($message_set)) { 
     
    13711411 
    13721412        // build FETCH command string 
    1373         $key     = $this->nextTag(); 
    1374         $cmd     = $uidfetch ? 'UID FETCH' : 'FETCH'; 
    1375         $deleted = $skip_deleted ? ' FLAGS' : ''; 
    1376  
    1377         if ($mode == 1 && $index_field == 'DATE') 
    1378             $request = " $cmd $message_set (INTERNALDATE BODY.PEEK[HEADER.FIELDS (DATE)]$deleted)"; 
    1379         else if ($mode == 1) 
    1380             $request = " $cmd $message_set (BODY.PEEK[HEADER.FIELDS ($index_field)]$deleted)"; 
     1413        $key    = $this->nextTag(); 
     1414        $cmd    = $uidfetch ? 'UID FETCH' : 'FETCH'; 
     1415        $fields = array(); 
     1416 
     1417        if ($return_uid) 
     1418            $fields[] = 'UID'; 
     1419        if ($skip_deleted) 
     1420            $fields[] = 'FLAGS'; 
     1421 
     1422        if ($mode == 1) { 
     1423            if ($index_field == 'DATE') 
     1424                $fields[] = 'INTERNALDATE'; 
     1425            $fields[] = "BODY.PEEK[HEADER.FIELDS ($index_field)]"; 
     1426        } 
    13811427        else if ($mode == 2) { 
    13821428            if ($index_field == 'SIZE') 
    1383                 $request = " $cmd $message_set (RFC822.SIZE$deleted)"; 
    1384             else 
    1385                 $request = " $cmd $message_set ($index_field$deleted)"; 
    1386         } else if ($mode == 3) 
    1387             $request = " $cmd $message_set (FLAGS)"; 
    1388         else // 4 
    1389             $request = " $cmd $message_set (INTERNALDATE$deleted)"; 
    1390  
    1391         $request = $key . $request; 
     1429                $fields[] = 'RFC822.SIZE'; 
     1430            else if (!$return_uid || $index_field != 'UID') 
     1431                $fields[] = $index_field; 
     1432        } 
     1433        else if ($mode == 3 && !$skip_deleted) 
     1434            $fields[] = 'FLAGS'; 
     1435        else if ($mode == 4) 
     1436            $fields[] = 'INTERNALDATE'; 
     1437 
     1438        $request = "$key $cmd $message_set (" . implode(' ', $fields) . ")"; 
    13921439 
    13931440        if (!$this->putLine($request)) { 
     
    14061453                $flags  = NULL; 
    14071454 
     1455                if ($return_uid) { 
     1456                    if (preg_match('/UID ([0-9]+)/', $line, $matches)) 
     1457                        $id = (int) $matches[1]; 
     1458                    else 
     1459                        continue; 
     1460                } 
    14081461                if ($skip_deleted && preg_match('/FLAGS \(([^)]+)\)/', $line, $matches)) { 
    14091462                    $flags = explode(' ', strtoupper($matches[1])); 
     
    15351588    { 
    15361589        if ($uid > 0) { 
    1537             $id_a = $this->search($mailbox, "UID $uid"); 
    1538             if (is_array($id_a) && count($id_a) == 1) { 
    1539                 return (int) $id_a[0]; 
     1590            $index = $this->search($mailbox, "UID $uid"); 
     1591 
     1592            if ($index->count() == 1) { 
     1593                $arr = $index->get(); 
     1594                return (int) $arr[0]; 
    15401595            } 
    15411596        } 
     
    15611616        } 
    15621617 
    1563         list($code, $response) = $this->execute('FETCH', array($id, '(UID)')); 
    1564  
    1565         if ($code == self::ERROR_OK && preg_match("/^\* $id FETCH \(UID (.*)\)/i", $response, $m)) { 
    1566             return (int) $m[1]; 
     1618        $index = $this->search($mailbox, $id, true); 
     1619 
     1620        if ($index->count() == 1) { 
     1621            $arr = $index->get(); 
     1622            return (int) $arr[0]; 
    15671623        } 
    15681624 
    15691625        return null; 
    1570     } 
    1571  
    1572     function fetchUIDs($mailbox, $message_set=null) 
    1573     { 
    1574         if (empty($message_set)) 
    1575             $message_set = '1:*'; 
    1576  
    1577         return $this->fetchHeaderIndex($mailbox, $message_set, 'UID', false); 
    15781626    } 
    15791627 
     
    19712019    } 
    19722020 
    1973     // Don't be tempted to change $str to pass by reference to speed this up - it will slow it down by about 
    1974     // 7 times instead :-) See comments on http://uk2.php.net/references and this article: 
    1975     // http://derickrethans.nl/files/phparch-php-variables-article.pdf 
    1976     private function parseThread($str, $begin, $end, $root, $parent, $depth, &$depthmap, &$haschildren) 
    1977     { 
    1978         $node = array(); 
    1979         if ($str[$begin] != '(') { 
    1980             $stop = $begin + strspn($str, '1234567890', $begin, $end - $begin); 
    1981             $msg = substr($str, $begin, $stop - $begin); 
    1982             if ($msg == 0) 
    1983                 return $node; 
    1984             if (is_null($root)) 
    1985                 $root = $msg; 
    1986             $depthmap[$msg] = $depth; 
    1987             $haschildren[$msg] = false; 
    1988             if (!is_null($parent)) 
    1989                 $haschildren[$parent] = true; 
    1990             if ($stop + 1 < $end) 
    1991                 $node[$msg] = $this->parseThread($str, $stop + 1, $end, $root, $msg, $depth + 1, $depthmap, $haschildren); 
    1992             else 
    1993                 $node[$msg] = array(); 
    1994         } else { 
    1995             $off = $begin; 
    1996             while ($off < $end) { 
    1997                 $start = $off; 
    1998                 $off++; 
    1999                 $n = 1; 
    2000                 while ($n > 0) { 
    2001                     $p = strpos($str, ')', $off); 
    2002                     if ($p === false) { 
    2003                         error_log("Mismatched brackets parsing IMAP THREAD response:"); 
    2004                         error_log(substr($str, ($begin < 10) ? 0 : ($begin - 10), $end - $begin + 20)); 
    2005                         error_log(str_repeat(' ', $off - (($begin < 10) ? 0 : ($begin - 10)))); 
    2006                         return $node; 
    2007                     } 
    2008                     $p1 = strpos($str, '(', $off); 
    2009                     if ($p1 !== false && $p1 < $p) { 
    2010                         $off = $p1 + 1; 
    2011                         $n++; 
    2012                     } else { 
    2013                         $off = $p + 1; 
    2014                         $n--; 
    2015                     } 
    2016                 } 
    2017                 $node += $this->parseThread($str, $start + 1, $off - 1, $root, $parent, $depth, $depthmap, $haschildren); 
    2018             } 
    2019         } 
    2020  
    2021         return $node; 
    2022     } 
    2023  
    2024     function thread($mailbox, $algorithm='REFERENCES', $criteria='', $encoding='US-ASCII') 
    2025     { 
     2021    /** 
     2022     * Executes THREAD command 
     2023     * 
     2024     * @param string $mailbox    Mailbox name 
     2025     * @param string $algorithm  Threading algorithm (ORDEREDSUBJECT, REFERENCES, REFS) 
     2026     * @param string $criteria   Searching criteria 
     2027     * @param bool   $return_uid Enables UIDs in result instead of sequence numbers 
     2028     * @param string $encoding   Character set 
     2029     * 
     2030     * @return rcube_result_thread Thread data 
     2031     */ 
     2032    function thread($mailbox, $algorithm='REFERENCES', $criteria='', $return_uid=false, $encoding='US-ASCII') 
     2033    { 
     2034        require_once dirname(__FILE__) . '/rcube_result_thread.php'; 
     2035 
    20262036        $old_sel = $this->selected; 
    20272037 
    20282038        if (!$this->select($mailbox)) { 
    2029             return false; 
     2039            return new rcube_result_thread($mailbox); 
    20302040        } 
    20312041 
    20322042        // return empty result when folder is empty and we're just after SELECT 
    20332043        if ($old_sel != $mailbox && !$this->data['EXISTS']) { 
    2034             return array(array(), array(), array()); 
     2044            return new rcube_result_thread($mailbox); 
    20352045        } 
    20362046 
     
    20402050        $data      = ''; 
    20412051 
    2042         list($code, $response) = $this->execute('THREAD', array( 
    2043             $algorithm, $encoding, $criteria)); 
    2044  
    2045         if ($code == self::ERROR_OK) { 
    2046             // remove prefix... 
    2047             $response = substr($response, stripos($response, '* THREAD') + 9); 
    2048             // ...unilateral untagged server responses 
    2049             if ($pos = strpos($response, '*')) { 
    2050                 $response = substr($response, 0, $pos); 
    2051             } 
    2052  
    2053             $response    = str_replace("\r\n", '', $response); 
    2054             $depthmap    = array(); 
    2055             $haschildren = array(); 
    2056  
    2057             $tree = $this->parseThread($response, 0, strlen($response), 
    2058                 null, null, 0, $depthmap, $haschildren); 
    2059  
    2060             return array($tree, $depthmap, $haschildren); 
    2061         } 
    2062  
    2063         return false; 
     2052        list($code, $response) = $this->execute($return_uid ? 'UID THREAD' : 'THREAD', 
     2053            array($algorithm, $encoding, $criteria)); 
     2054 
     2055        if ($code != self::ERROR_OK) { 
     2056            $response = null; 
     2057        } 
     2058 
     2059        return new rcube_result_thread($mailbox, $response); 
    20642060    } 
    20652061 
     
    20722068     * @param array  $items      Return items (MIN, MAX, COUNT, ALL) 
    20732069     * 
    2074      * @return array Message identifiers or item-value hash  
     2070     * @return rcube_result_index Result data 
    20752071     */ 
    20762072    function search($mailbox, $criteria, $return_uid=false, $items=array()) 
    20772073    { 
     2074        require_once dirname(__FILE__) . '/rcube_result_index.php'; 
     2075 
    20782076        $old_sel = $this->selected; 
    20792077 
    20802078        if (!$this->select($mailbox)) { 
    2081             return false; 
     2079            return new rcube_result_index($mailbox); 
    20822080        } 
    20832081 
    20842082        // return empty result when folder is empty and we're just after SELECT 
    20852083        if ($old_sel != $mailbox && !$this->data['EXISTS']) { 
    2086             if (!empty($items)) 
    2087                 return array_combine($items, array_fill(0, count($items), 0)); 
    2088             else 
    2089                 return array(); 
     2084            return new rcube_result_index($mailbox, '* SEARCH'); 
     2085        } 
     2086 
     2087        // If ESEARCH is supported always use ALL 
     2088        // but not when items are specified or using simple id2uid search 
     2089        if (empty($items) && ((int) $criteria != $criteria)) { 
     2090            $items = array('ALL'); 
    20902091        } 
    20912092 
     
    20982099            $params .= 'RETURN (' . implode(' ', $items) . ')'; 
    20992100        } 
     2101 
    21002102        if (!empty($criteria)) { 
    21012103            $modseq = stripos($criteria, 'MODSEQ') !== false; 
     
    21092111            array($params)); 
    21102112 
    2111         if ($code == self::ERROR_OK) { 
    2112             // remove prefix... 
    2113             $response = substr($response, stripos($response, 
    2114                 $esearch ? '* ESEARCH' : '* SEARCH') + ($esearch ? 10 : 9)); 
    2115             // ...and unilateral untagged server responses 
    2116             if ($pos = strpos($response, '*')) { 
    2117                 $response = rtrim(substr($response, 0, $pos)); 
    2118             } 
    2119  
    2120             // remove MODSEQ response 
    2121             if ($modseq) { 
    2122                 if (preg_match('/\(MODSEQ ([0-9]+)\)$/', $response, $m)) { 
    2123                     $response = substr($response, 0, -strlen($m[0])); 
    2124                 } 
    2125             } 
    2126  
    2127             if ($esearch) { 
    2128                 // Skip prefix: ... (TAG "A285") UID ... 
    2129                 $this->tokenizeResponse($response, $return_uid ? 2 : 1); 
    2130  
    2131                 $result = array(); 
    2132                 for ($i=0; $i<count($items); $i++) { 
    2133                     // If the SEARCH returns no matches, the server MUST NOT 
    2134                     // include the item result option in the ESEARCH response 
    2135                     if ($ret = $this->tokenizeResponse($response, 2)) { 
    2136                         list ($name, $value) = $ret; 
    2137                         $result[$name] = $value; 
    2138                     } 
    2139                 } 
    2140  
    2141                 return $result; 
    2142             } 
    2143             else { 
    2144                 $response = preg_split('/[\s\r\n]+/', $response, -1, PREG_SPLIT_NO_EMPTY); 
    2145  
    2146                 if (!empty($items)) { 
    2147                     $result = array(); 
    2148                     if (in_array('COUNT', $items)) { 
    2149                         $result['COUNT'] = count($response); 
    2150                     } 
    2151                     if (in_array('MIN', $items)) { 
    2152                         $result['MIN'] = !empty($response) ? min($response) : 0; 
    2153                     } 
    2154                     if (in_array('MAX', $items)) { 
    2155                         $result['MAX'] = !empty($response) ? max($response) : 0; 
    2156                     } 
    2157                     if (in_array('ALL', $items)) { 
    2158                         $result['ALL'] = $this->compressMessageSet($response, true); 
    2159                     } 
    2160  
    2161                     return $result; 
    2162                 } 
    2163                 else { 
    2164                     return $response; 
    2165                 } 
    2166             } 
    2167         } 
    2168  
    2169         return false; 
     2113        if ($code != self::ERROR_OK) { 
     2114            $response = null; 
     2115        } 
     2116 
     2117        return new rcube_result_index($mailbox, $response); 
    21702118    } 
    21712119 
  • trunk/roundcubemail/program/steps/mail/func.inc

    r5521 r5557  
    9494      $_SESSION['search_request'] = $search_request; 
    9595      $OUTPUT->set_env('search_request', $search_request); 
    96       } 
     96    } 
    9797 
    9898      $search_mods = $RCMAIL->config->get('search_mods', $SEARCH_MODS_DEFAULT); 
  • trunk/roundcubemail/program/steps/mail/pagenav.inc

    r4873 r5557  
    2020*/ 
    2121 
    22 $uid = get_input_value('_uid', RCUBE_INPUT_GET); 
     22$uid   = get_input_value('_uid', RCUBE_INPUT_GET); 
     23$index = $IMAP->message_index(null, $_SESSION['sort_col'], $_SESSION['sort_order']); 
     24$cnt   = $index->countMessages(); 
    2325 
    24 // Select mailbox first, for better performance 
    25 $mbox_name = $IMAP->get_mailbox_name(); 
    26 $IMAP->select_mailbox($mbox_name); 
    27  
    28 // Get messages count (only messages, no threads here) 
    29 $cnt  = $IMAP->messagecount(NULL, 'ALL'); 
    30  
    31 if ($_SESSION['sort_col'] == 'date' && $_SESSION['sort_order'] == 'DESC' 
    32     && empty($_REQUEST['_search']) && !$CONFIG['skip_deleted'] && !$IMAP->threading 
    33 ) { 
    34     // this assumes that we are sorted by date_DESC 
    35     $seq   = $IMAP->get_id($uid); 
    36     $index = $cnt - $seq; 
    37  
    38     $prev  = $IMAP->get_uid($seq + 1); 
    39     $first = $IMAP->get_uid($cnt); 
    40     $next  = $IMAP->get_uid($seq - 1); 
    41     $last  = $IMAP->get_uid(1); 
    42 } 
    43 else { 
    44     // Only if we use custom sorting 
    45     $a_msg_index = $IMAP->message_index(NULL, $_SESSION['sort_col'], $_SESSION['sort_order']); 
    46  
    47     $index = array_search($IMAP->get_id($uid), $a_msg_index); 
    48  
    49     $count = count($a_msg_index); 
    50     $prev  = isset($a_msg_index[$index-1]) ? $IMAP->get_uid($a_msg_index[$index-1]) : -1; 
    51     $first = $count > 1 ? $IMAP->get_uid($a_msg_index[0]) : -1; 
    52     $next  = isset($a_msg_index[$index+1]) ? $IMAP->get_uid($a_msg_index[$index+1]) : -1; 
    53     $last  = $count > 1 ? $IMAP->get_uid($a_msg_index[$count-1]) : -1; 
     26if ($cnt && ($pos = $index->exists($uid, true)) !== false) { 
     27    $prev  = $pos ? $index->getElement($pos-1) : 0; 
     28    $first = $pos ? $index->getElement('FIRST') : 0; 
     29    $next  = $pos < $cnt-1 ? $index->getElement($pos+1) : 0; 
     30    $last  = $pos < $cnt-1 ? $index->getElement('LAST') : 0; 
    5431} 
    5532 
    5633// Set UIDs and activate navigation buttons 
    57 if ($prev > 0) { 
     34if ($prev) { 
    5835    $OUTPUT->set_env('prev_uid', $prev); 
    5936    $OUTPUT->command('enable_command', 'previousmessage', 'firstmessage', true); 
    6037} 
    61 if ($next > 0) { 
     38if ($next) { 
    6239    $OUTPUT->set_env('next_uid', $next); 
    6340    $OUTPUT->command('enable_command', 'nextmessage', 'lastmessage', true); 
    6441} 
    65 if ($first > 0) 
     42if ($first) 
    6643    $OUTPUT->set_env('first_uid', $first); 
    67 if ($last > 0) 
     44if ($last) 
    6845    $OUTPUT->set_env('last_uid', $last); 
    6946 
     
    7451$OUTPUT->command('set_rowcount', rcube_label(array( 
    7552    'name' => 'messagenrof', 
    76     'vars' => array('nr'  => $index+1, 'count' => $cnt) 
     53    'vars' => array('nr'  => $pos+1, 'count' => $cnt) 
    7754))); 
    7855 
  • trunk/roundcubemail/program/steps/mail/search.inc

    r5520 r5557  
    110110  $IMAP->search($mbox, $search_str, $imap_charset, $_SESSION['sort_col']); 
    111111 
    112 // Get the headers 
    113 $result_h = $IMAP->list_headers($mbox, 1, $_SESSION['sort_col'], $_SESSION['sort_order']); 
    114 $count = $IMAP->messagecount(NULL, $IMAP->threading ? 'THREADS' : 'ALL'); 
    115  
    116112// save search results in session 
    117113if (!is_array($_SESSION['search'])) 
     
    123119} 
    124120$_SESSION['search_request'] = $search_request; 
     121 
     122 
     123// Get the headers 
     124$result_h = $IMAP->list_headers($mbox, 1, $_SESSION['sort_col'], $_SESSION['sort_order']); 
     125$count = $IMAP->messagecount($mbox, $IMAP->threading ? 'THREADS' : 'ALL'); 
     126 
    125127 
    126128// Make sure we got the headers 
  • trunk/roundcubemail/program/steps/mail/sendmail.inc

    r5521 r5557  
    702702    // delete previous saved draft 
    703703    // @TODO: use message UID (remember to check UIDVALIDITY) to skip this SEARCH 
    704     $a_deleteid = $IMAP->search_once($CONFIG['drafts_mbox'], 
    705         'HEADER Message-ID '.$olddraftmessageid, true); 
    706  
    707     if (!empty($a_deleteid)) { 
    708       $deleted = $IMAP->delete_message($a_deleteid, $CONFIG['drafts_mbox']); 
     704    $delete_idx = $IMAP->search_once($CONFIG['drafts_mbox'], 
     705        'HEADER Message-ID '.$olddraftmessageid); 
     706 
     707    if ($del_uid = $delete_idx->getElement('FIRST')) { 
     708      $deleted = $IMAP->delete_message($del_uid, $CONFIG['drafts_mbox']); 
    709709 
    710710      // raise error if deletion of old draft failed 
     
    727727  // remember new draft-uid ($saved could be an UID or TRUE here) 
    728728  if (is_bool($saved)) { 
    729     $draftuids = $IMAP->search_once($CONFIG['drafts_mbox'], 'HEADER Message-ID '.$msgid, true); 
    730     $saved     = $draftuids[0]; 
     729    $draft_idx = $IMAP->search_once($CONFIG['drafts_mbox'], 'HEADER Message-ID '.$msgid); 
     730    $saved     = $draft_idx->getElement('FIRST'); 
    731731  } 
    732732  $COMPOSE['param']['draft_uid'] = $saved; 
Note: See TracChangeset for help on using the changeset viewer.