Ticket #8936: Add sequence sorting to search().
authorMichael M Slusarz <slusarz@curecanti.org>
Fri, 26 Mar 2010 22:19:05 +0000 (16:19 -0600)
committerMichael M Slusarz <slusarz@curecanti.org>
Fri, 26 Mar 2010 23:11:36 +0000 (17:11 -0600)
framework/Imap_Client/lib/Horde/Imap/Client.php
framework/Imap_Client/lib/Horde/Imap/Client/Base.php
framework/Imap_Client/lib/Horde/Imap/Client/Cclient.php
framework/Imap_Client/lib/Horde/Imap/Client/Socket.php
framework/Imap_Client/lib/Horde/Imap/Client/Socket/Pop3.php
framework/Imap_Client/package.xml

index 968b385..00a212d 100644 (file)
@@ -118,6 +118,9 @@ class Horde_Imap_Client
     /* Sort criteria defined in draft-ietf-morg-sortdisplay-02 */
     const SORT_DISPLAYFROM = 10;
     const SORT_DISPLAYTO = 11;
+    /* SORT_SEQUENCE does a simple numerical sort on the returned
+     * UIDs/sequence numbers. */
+    const SORT_SEQUENCE = 12;
 
     const SORT_RESULTS_COUNT = 1;
     const SORT_RESULTS_MATCH = 2;
index 56cb701..58088c6 100644 (file)
@@ -1364,6 +1364,7 @@ abstract class Horde_Imap_Client_Base
      * Horde_Imap_Client::SORT_CC
      * Horde_Imap_Client::SORT_DATE
      * Horde_Imap_Client::SORT_FROM
+     * Horde_Imap_Client::SORT_SEQUENCE
      * Horde_Imap_Client::SORT_SIZE
      * Horde_Imap_Client::SORT_SUBJECT
      * Horde_Imap_Client::SORT_TO
@@ -1378,7 +1379,7 @@ abstract class Horde_Imap_Client_Base
      *          entire list to be sorted in reverse order, use the 'reverse'
      *          option instead. If this option is set, the 'results' option
      *          is ignored.
-     *          DEFAULT: Arrival sort (Horde_Imap_Client::SORT_ARRIVAL)
+     *          DEFAULT: None.
      * </pre>
      *
      * @return array  An array with the following keys:
@@ -2803,7 +2804,7 @@ abstract class Horde_Imap_Client_Base
             $search = new Horde_Imap_Client_Search_Query();
             $search->sequence($ids, $seq);
         }
-        $res = $this->search($this->_selected, $search, array('sort' => array(Horde_Imap_Client::SORT_ARRIVAL)));
+        $res = $this->search($this->_selected, $search, array('sort' => array(Horde_Imap_Client::SORT_SEQUENCE)));
         $ret = array('uids' => $res['sort']);
         if ($seq) {
             if (!$res['count']) {
index dfa46d3..186f4dc 100644 (file)
@@ -807,9 +807,15 @@ class Horde_Imap_Client_Cclient extends Horde_Imap_Client_Base
             return $this->_getSocket()->search($this->_selected, $query, $options);
         }
 
-        $old_error = error_reporting(0);
-        if (empty($options['sort'])) {
-            $res = imap_search($this->_stream, $options['_query']['query'], empty($options['sequence']) ? SE_UID : 0, $options['_query']['charset']);
+        $sort = empty($options['sort'])
+            ? null
+            : reset($options['sort']);
+
+        if (!$sort || ($sort == Horde_Imap_Client::SORT_SEQUENCE)) {
+            $res = @imap_search($this->_stream, $options['_query']['query'], empty($options['sequence']) ? SE_UID : 0, $options['_query']['charset']);
+            if ($sort && ($res !== false)) {
+                sort($res, SORT_NUMERIC);
+            }
         } else {
             $sort_criteria = array(
                 Horde_Imap_Client::SORT_ARRIVAL => SORTARRIVAL,
@@ -821,10 +827,9 @@ class Horde_Imap_Client_Cclient extends Horde_Imap_Client_Base
                 Horde_Imap_Client::SORT_TO => SORTTO
             );
 
-            $res = imap_sort($this->_stream, $sort_criteria[reset($options['sort'])], 0, empty($options['sequence']) ? SE_UID : 0, $options['_query']['query'], $options['_query']['charset']);
+            $res = @imap_sort($this->_stream, $sort_criteria[$sort], 0, empty($options['sequence']) ? SE_UID : 0, $options['_query']['query'], $options['_query']['charset']);
         }
         $res = ($res === false) ? array() : $res;
-        error_reporting($old_error);
 
         $ret = array();
         foreach ($options['results'] as $val) {
@@ -834,7 +839,7 @@ class Horde_Imap_Client_Cclient extends Horde_Imap_Client_Base
                 break;
 
             case Horde_Imap_Client::SORT_RESULTS_MATCH:
-                $ret[empty($options['sort']) ? 'match' : 'sort'] = $res;
+                $ret[$sort ? 'sort' : 'match'] = $res;
                 break;
 
             case Horde_Imap_Client::SORT_RESULTS_MAX:
index 430b769..648362a 100644 (file)
@@ -1641,6 +1641,9 @@ class Horde_Imap_Client_Socket extends Horde_Imap_Client_Base
             Horde_Imap_Client::SORT_DISPLAYTO => 'DISPLAYTO',
             Horde_Imap_Client::SORT_FROM => 'FROM',
             Horde_Imap_Client::SORT_REVERSE => 'REVERSE',
+            // This is a bogus entry to allow the sort options check to
+            // correctly work below.
+            Horde_Imap_Client::SORT_SEQUENCE => 'SEQUENCE',
             Horde_Imap_Client::SORT_SIZE => 'SIZE',
             Horde_Imap_Client::SORT_SUBJECT => 'SUBJECT',
             Horde_Imap_Client::SORT_TO => 'TO'
@@ -1655,22 +1658,40 @@ class Horde_Imap_Client_Socket extends Horde_Imap_Client_Base
         );
 
         // Check if the server supports server-side sorting (RFC 5256).
-        $esearch = $server_sort = $return_sort = false;
+        $esearch = $return_sort = $server_seq_sort = $server_sort = false;
         if (!empty($options['sort'])) {
-            $return_sort = true;
-            $server_sort = $this->queryCapability('SORT');
-
-            /* Make sure sort options are correct. If not, default to ARRIVAL
+            /* Make sure sort options are correct. If not, default to no
              * sort. */
             if (count(array_intersect($options['sort'], array_keys($sort_criteria))) === 0) {
-                $options['sort'] = array(Horde_Imap_Client::SORT_ARRIVAL);
-            }
+                unset($options['sort']);
+            } else {
+                $return_sort = true;
+
+                $server_sort =
+                    $this->queryCapability('SORT') &&
+                    /* Make sure server supports DISPLAYFROM & DISPLAYTO. */
+                    !((in_array(Horde_Imap_Client::SORT_DISPLAYFROM, $options['sort']) ||
+                       in_array(Horde_Imap_Client::SORT_DISPLAYTO, $options['sort'])) &&
+                      (!is_array($server_sort) || !in_array('DISPLAY', $server_sort)));
+
+                /* If doing a sequence sort, need to do this on the client
+                 * side. */
+                if ($server_sort &&
+                    in_array(Horde_Imap_Client::SORT_SEQUENCE, $options['sort'])) {
+                    $server_sort = false;
+
+                    /* Optimization: If doing only a sequence sort, just do a
+                     * simple search and sort UIDs/sequences on client side. */
+                    switch (count($options['sort'])) {
+                    case 1:
+                        $server_seq_sort = true;
+                        break;
 
-            /* Make sure server supports DISPLAYFROM & DISPLAYTO. */
-            if ((in_array(Horde_Imap_Client::SORT_DISPLAYFROM, $options['sort']) ||
-                 in_array(Horde_Imap_Client::SORT_DISPLAYTO, $options['sort'])) &&
-                (!is_array($server_sort) || !in_array('DISPLAY', $server_sort))) {
-                $server_sort = false;
+                    case 2:
+                        $server_seq_sort = (reset($options['sort']) == Horde_Imap_Client::SORT_REVERSE);
+                        break;
+                    }
+                }
             }
         }
 
@@ -1724,7 +1745,14 @@ class Horde_Imap_Client_Socket extends Horde_Imap_Client_Base
         $this->_sendLine($cmd . $options['_query']['charset'] . ' ' . $options['_query']['query']);
 
         if ($return_sort && !$server_sort) {
-            $sr = array_values($this->_clientSort($sr, $options));
+            if ($server_seq_sort) {
+                sort($sr, SORT_NUMERIC);
+                if (reset($options['sort']) == Horde_Imap_Client::SORT_REVERSE) {
+                    $sr = array_reverse($sr);
+                }
+            } else {
+                $sr = array_values($this->_clientSort($sr, $options));
+            }
         }
 
         $ret = array();
@@ -1840,9 +1868,14 @@ class Horde_Imap_Client_Socket extends Horde_Imap_Client_Base
         $criteria = array();
         foreach ($opts['sort'] as $val) {
             switch ($val) {
+            case Horde_Imap_Client::SORT_ARRIVAL:
+                $criteria[Horde_Imap_Client::FETCH_DATE] = true;
+                break;
+
             case Horde_Imap_Client::SORT_DATE:
                 $criteria[Horde_Imap_Client::FETCH_DATE] = true;
-                // Fall through
+                $criteria[Horde_Imap_Client::FETCH_ENVELOPE] = true;
+                break;
 
             case Horde_Imap_Client::SORT_CC:
             case Horde_Imap_Client::SORT_FROM:
@@ -1884,7 +1917,7 @@ class Horde_Imap_Client_Socket extends Horde_Imap_Client_Base
                 }
 
                 switch ($val) {
-                case Horde_Imap_Client::SORT_ARRIVAL:
+                case Horde_Imap_Client::SORT_SEQUENCE:
                     /* There is no requirement that IDs be returned in
                      * sequence order (see RFC 4549 [4.3.1]). So we must sort
                      * ourselves. */
@@ -1928,6 +1961,11 @@ class Horde_Imap_Client_Socket extends Horde_Imap_Client_Base
                     asort($sorted, SORT_LOCALE_STRING);
                     break;
 
+                case Horde_Imap_Client::SORT_ARRIVAL:
+                    $sorted = $this->_getSentDates($fetch_res, $slice, true);
+                    asort($sorted, SORT_NUMERIC);
+                    break;
+
                 case Horde_Imap_Client::SORT_DATE:
                     // Date sorting rules in RFC 5256 [2.2]
                     $sorted = $this->_getSentDates($fetch_res, $slice);
@@ -1986,24 +2024,24 @@ class Horde_Imap_Client_Socket extends Horde_Imap_Client_Base
      * Get the sent dates for purposes of SORT/THREAD sorting under RFC 5256
      * [2.2].
      *
-     * @param array $data  Data returned from fetch() that includes both the
-     *                     'envelope' and 'date' keys.
-     * @param array $ids   The IDs to process.
+     * @param array $data        Data returned from fetch() that includes both
+     *                           the 'envelope' and 'date' keys.
+     * @param array $ids         The IDs to process.
+     * @param boolean $internal  Only use internal date?
      *
      * @return array  A mapping of IDs -> UNIX timestamps.
      */
-    protected function _getSentDates($data, $ids)
+    protected function _getSentDates($data, $ids, $internal = false)
     {
         $dates = array();
 
         $tz = new DateTimeZone('UTC');
         foreach ($ids as $num) {
-            if (empty($data[$num]['envelope']['date'])) {
-                $dt = $data[$num]['date'];
-                $dt->setTimezone($tz);
-            } else {
-                $dt = new DateTime($data[$num]['envelope']['date'], $tz);
-            }
+            $dt = ($internal || empty($data[$num]['envelope']['date']))
+                // RFC 5256 [3] & 3501 [6.4.4]: disregard timezone when
+                // using internaldate.
+                ? $data[$num]['date']
+                : new DateTime($data[$num]['envelope']['date'], $tz);
             $dates[$num] = $dt->format('U');
         }
 
index 40fba7e..732b969 100644 (file)
@@ -645,13 +645,15 @@ class Horde_Imap_Client_Socket_Pop3 extends Horde_Imap_Client_Base
      */
     protected function _search($query, $options)
     {
-        // Only support a single query: an ALL search sorted by arrival.
+        $sort = empty($options['sort'])
+            ? null
+            : reset($options['sort']);
+
+        // Only support a single query: an ALL search sorted by sequence.
         if (($options['_query']['query'] != 'ALL') ||
-            (!empty($options['sort']) &&
+            ($sort &&
              ((count($options['sort']) > 1) ||
-              in_array(Horde_Imap_Client::SORT_ARRIVAL, $options['sort']) ||
-              in_array(Horde_Imap_Client::SORT_DISPLAYFROM, $options['sort']) ||
-              in_array(Horde_Imap_Client::SORT_DISPLAYTO, $options['sort'])))) {
+              ($sort != Horde_Imap_Client::SORT_SEQUENCE)))) {
             throw new Horde_Imap_Client_Exception('Server search not supported on POP3 server.', Horde_Imap_Client_Exception::POP3_NOTSUPPORTED);
         }
 
index b7d5b75..7dcb470 100644 (file)
@@ -31,7 +31,8 @@ http://pear.php.net/dtd/package-2.0.xsd">
   <api>alpha</api>
  </stability>
  <license uri="http://www.gnu.org/copyleft/lesser.html">LGPL</license>
- <notes>* Add ability to disable server capabilities.
+ <notes>* Add sequence sort (numeric sort by UID/sequence number).
+ * Add ability to disable server capabilities.
  * Add Horde_Imap_Client_Base::parseCacheId().
  * Added STATUS_LASTMODSEQ and STATUS_LASTMODSEQUIDS status() entries.
  * Add support for LIST-STATUS (draft-ietf-morg-status-in-list-01).