Add support for RFC 5258 (LIST-EXTENDED)
authorMichael M Slusarz <slusarz@curecanti.org>
Tue, 24 Nov 2009 03:14:09 +0000 (20:14 -0700)
committerMichael M Slusarz <slusarz@curecanti.org>
Tue, 24 Nov 2009 03:14:13 +0000 (20:14 -0700)
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/package.xml

index e2f8ba2..5ed7910 100644 (file)
@@ -80,8 +80,9 @@ class Horde_Imap_Client
 
     /* Constants for listMailboxes() */
     const MBOX_SUBSCRIBED = 1;
-    const MBOX_UNSUBSCRIBED = 2;
-    const MBOX_ALL = 3;
+    const MBOX_SUBSCRIBED_EXISTS = 2;
+    const MBOX_UNSUBSCRIBED = 3;
+    const MBOX_ALL = 4;
 
     /* Constants for status() */
     const STATUS_MESSAGES = 1;
index f314c05..e6af0c7 100644 (file)
@@ -811,13 +811,13 @@ abstract class Horde_Imap_Client_Base
     /**
      * Obtain a list of mailboxes matching a pattern.
      *
-     * @todo RFC 5258 extensions
-     *
-     * @param string $pattern  The mailbox search pattern (see RFC 3501
-     *                         [6.3.8] for the format). Either in UTF7-IMAP or
+     * @param mixed $pattern   The mailbox search pattern(s) (see RFC 3501
+     *                         [6.3.8] for the format). Either a string or an
+     *                         array of strings. Either in UTF7-IMAP or
      *                         UTF-8.
      * @param integer $mode    Which mailboxes to return.  Either
      *                         Horde_Imap_Client::MBOX_SUBSCRIBED,
+     *                         Horde_Imap_Client::MBOX_SUBSCRIBED_EXISTS,
      *                         Horde_Imap_Client::MBOX_UNSUBSCRIBED, or
      *                         Horde_Imap_Client::MBOX_ALL.
      * @param array $options   Additional options:
@@ -826,26 +826,43 @@ abstract class Horde_Imap_Client_Base
      *                the 'attributes' key. The attributes will be returned
      *                in an array with each attribute in lowercase.
      *                DEFAULT: Do not return this information.
-     * 'utf8' - (boolean) True to return mailbox names in UTF-8.
-     *          DEFAULT: Names are returned in UTF7-IMAP.
+     * 'children' - (boolean) Tell server to return children attribute
+     *              information. Requires the LIST-EXTENDED extension. Server
+     *              MAY return this autribute without this option, but it
+     *              is not guaranteed.
+     *              DEFAULT: false
      * 'delimiter' - (boolean) If true, return delimiter information under
      *               the 'delimiter' key.
      *               DEFAULT: Do not return this information.
      * 'flat' - (boolean) If true, return a flat list of mailbox names only.
      *          Overrides both the 'attributes' and 'delimiter' options.
      *          DEFAULT: Do not return flat list.
+     * 'recursivematch' - (boolean) Force the server to return information
+     *                    about parent mailboxes that don't match other
+     *                    selection options, but have some submailboxes that
+     *                    do. Information about children is returned in the
+     *                    CHILDINFO extended data item ('extended'). Requires
+     *                    the LIST-EXTENDED extension.
+     *                    DEFAULT: false
+     * 'remote' - (boolean) Tell server to return mailboxes that reside on
+     *            another server. Requires the LIST-EXTENDED extension.
+     *            DEFAULT: false
      * 'sort' - (boolean) If true, return a sorted list of mailboxes?
      *          DEFAULT: Do not sort the list.
      * 'sort_delimiter' - (string) If 'sort' is true, this is the delimiter
      *                    used to sort the mailboxes.
      *                    DEFAULT: '.'
+     * 'utf8' - (boolean) True to return mailbox names in UTF-8.
+     *          DEFAULT: Names are returned in UTF7-IMAP.
      * </pre>
      *
      * @return array  If 'flat' option is true, the array values are the list
      *                of mailboxes.  Otherwise, the array values are arrays
      *                with the following keys: 'mailbox', 'attributes' (only
-     *                if 'attributes' option is true), and 'delimiter' (only
-     *                if 'delimiter' option is true).
+     *                if 'attributes' option is true), 'delimiter' (only
+     *                if 'delimiter' option is true), and 'extended' (only
+     *                if 'recursivematch' option is true and LIST-EXTENDED
+     *                extension is supported on the server).
      * @throws Horde_Imap_Client_Exception
      */
     public function listMailboxes($pattern, $mode = Horde_Imap_Client::MBOX_ALL,
@@ -863,9 +880,9 @@ abstract class Horde_Imap_Client_Base
     /**
      * Obtain a list of mailboxes matching a pattern.
      *
-     * @param string $pattern  The mailbox search pattern (UTF7-IMAP).
-     * @param integer $mode    Which mailboxes to return.
-     * @param array $options   Additional options.
+     * @param mixed $pattern  The mailbox search pattern(s) (UTF7-IMAP).
+     * @param integer $mode   Which mailboxes to return.
+     * @param array $options  Additional options.
      *
      * @return array  See self::listMailboxes().
      * @throws Horde_Imap_Client_Exception
index e89ecb8..1356ee6 100644 (file)
@@ -421,9 +421,9 @@ class Horde_Imap_Client_Cclient extends Horde_Imap_Client_Base
     /**
      * Obtain a list of mailboxes matching a pattern.
      *
-     * @param string $pattern  The mailbox search pattern.
-     * @param integer $mode    Which mailboxes to return.
-     * @param array $options   Additional options.
+     * @param mixed $pattern  The mailbox search pattern(s).
+     * @param integer $mode   Which mailboxes to return.
+     * @param array $options  Additional options.
      * <pre>
      * For the 'attributes' option, this driver will return only these
      * attributes:
@@ -449,23 +449,33 @@ class Horde_Imap_Client_Cclient extends Horde_Imap_Client_Base
 
         case Horde_Imap_Client::MBOX_SUBSCRIBED:
         case Horde_Imap_Client::MBOX_UNSUBSCRIBED:
+        case Horde_Imap_Client::MBOX_SUBSCRIBED_EXISTS:
             $sub = $this->_getMailboxList($pattern, Horde_Imap_Client::MBOX_SUBSCRIBED);
+            if ($mode == Horde_Imap_Client::MBOX_SUBSCRIBED_EXISTS) {
+                $mboxes = $this->_getMailboxList($pattern, Horde_Imap_Client::MBOX_ALL);
+                $sub = array_intersect($sub, $mboxes);
+            }
+
             if (!empty($options['flat'])) {
                 if (!empty($options['utf8'])) {
                     $sub = array_map(array('Horde_Imap_Client_Utf7imap', 'Utf7ImapToUtf8'), $sub);
                 }
-                if ($mode == Horde_Imap_Client::MBOX_SUBSCRIBED) {
-                    return $sub;
+
+                if (($mode == Horde_Imap_Client::MBOX_SUBSCRIBED) ||
+                    ($mode == Horde_Imap_Client::MBOX_SUBSCRIBED_EXISTS)) {
+                    return array_values($sub);
                 }
 
                 $mboxes = $this->_getMailboxList($pattern, Horde_Imap_Client::MBOX_ALL);
+
                 if (!empty($options['utf8'])) {
-                    $sub = array_map(array('Horde_Imap_Client_Utf7imap', 'Utf7ImapToUtf8'), $sub);
+                    $mboxes = array_map(array('Horde_Imap_Client_Utf7imap', 'Utf7ImapToUtf8'), $mboxes);
                 }
                 return array_values(array_diff($mboxes, $sub));
             }
             $sub = array_flip($sub);
             $check = true;
+            break;
         }
 
         $attr = array(
@@ -519,15 +529,25 @@ class Horde_Imap_Client_Cclient extends Horde_Imap_Client_Base
     /**
      * Obtain a list of mailboxes matching a pattern.
      *
-     * @param string $pattern  The mailbox search pattern.
-     * @param integer $mode    Which mailboxes to return.  Either
-     *                         Horde_Imap_Client::MBOX_SUBSCRIBED or
-     *                         Horde_Imap_Client::MBOX_ALL.
+     * @param mixed $pattern  The mailbox search patterns.
+     * @param integer $mode   Which mailboxes to return.  Either
+     *                        Horde_Imap_Client::MBOX_SUBSCRIBED or
+     *                        Horde_Imap_Client::MBOX_ALL.
      *
      * @return array  A list of mailboxes in UTF7-IMAP format.
      */
     protected function _getMailboxList($pattern, $mode)
     {
+        if (is_array($pattern)) {
+            $res = array();
+            foreach ($pattern as $val) {
+                if (strlen($val)) {
+                    $res = array_merge($res, $this->_getMailboxList($val, $mode);
+                }
+            }
+            return array_unique($res);
+        }
+
         $mboxes = array();
 
         $old_error = error_reporting(0);
index 724f779..f78c225 100644 (file)
@@ -31,6 +31,7 @@
  *   RFC 5182 - SEARCHRES
  *   RFC 5255 - LANGUAGE/I18NLEVEL
  *   RFC 5256 - THREAD/SORT
+ *   RFC 5258 - LIST-EXTENDED
  *   RFC 5267 - ESORT
  *   RFC 5464 - METADATA
  *
@@ -49,7 +50,6 @@
  *   RFC 4469/5550 - CATENATE
  *   RFC 4978 - COMPRESS=DEFLATE
  *              See: http://bugs.php.net/bug.php?id=48725
- *   RFC 3348/5258 - LIST-EXTENDED
  *   RFC 5257 - ANNOTATE
  *   RFC 5259 - CONVERT
  *   RFC 5267 - CONTEXT
@@ -933,9 +933,9 @@ class Horde_Imap_Client_Socket extends Horde_Imap_Client_Base
     /**
      * Obtain a list of mailboxes matching a pattern.
      *
-     * @param string $pattern  The mailbox search pattern.
-     * @param integer $mode    Which mailboxes to return.
-     * @param array $options   Additional options.
+     * @param mixed $pattern  The mailbox search pattern(s).
+     * @param integer $mode   Which mailboxes to return.
+     * @param array $options  Additional options.
      *
      * @return array  See self::listMailboxes().
      * @throws Horde_Imap_Client_Exception
@@ -947,8 +947,12 @@ class Horde_Imap_Client_Socket extends Horde_Imap_Client_Base
         // Get the list of subscribed/unsubscribed mailboxes. Since LSUB is
         // not guaranteed to have correct attributes, we must use LIST to
         // ensure we receive the correct information.
-        if ($mode != Horde_Imap_Client::MBOX_ALL) {
+        // TODO: Use LSUB for MBOX_SUBSCRIBED if no other options are
+        // set (RFC 5258 3.1)
+        if (($mode != Horde_Imap_Client::MBOX_ALL) &&
+            !$this->queryCapability('LIST-EXTENDED')) {
             $subscribed = $this->_getMailboxList($pattern, Horde_Imap_Client::MBOX_SUBSCRIBED, array('flat' => true));
+
             // If mode is subscribed, and 'flat' option is true, we can
             // return now.
             if (($mode == Horde_Imap_Client::MBOX_SUBSCRIBED) && !empty($options['flat'])) {
@@ -962,9 +966,9 @@ class Horde_Imap_Client_Socket extends Horde_Imap_Client_Base
     }
 
     /**
-     * Obtain a list of mailboxes matching a pattern.
+     * Obtain a list of mailboxes.
      *
-     * @param string $pattern    The mailbox search pattern.
+     * @param mixed $pattern     The mailbox search pattern(s).
      * @param integer $mode      Which mailboxes to return.
      * @param array $options     Additional options.
      * @param array $subscribed  A list of subscribed mailboxes.
@@ -981,14 +985,63 @@ class Horde_Imap_Client_Socket extends Horde_Imap_Client_Base
         $t = &$this->_temp;
         $t['mailboxlist'] = array(
             'check' => $check,
-            'subscribed' => $check ? array_flip($subscribed) : null,
-            'options' => $options
+            'options' => $options,
+            'subexist' => ($mode == Horde_Imap_Client::MBOX_SUBSCRIBED_EXISTS),
+            'subscribed' => ($check ? array_flip($subscribed) : null)
         );
         $t['listresponse'] = array();
 
-        $this->_sendLine((($mode == Horde_Imap_Client::MBOX_SUBSCRIBED) ? 'LSUB' : 'LIST') . ' "" ' . $this->utils->escape($pattern));
+        if ($this->queryCapability('LIST-EXTENDED')) {
+            $cmd = 'LIST';
+
+            $return_opts = $select_opts = array();
+
+            if (($mode == Horde_Imap_Client::MBOX_SUBSCRIBED) ||
+                ($mode == Horde_Imap_Client::MBOX_SUBSCRIBED_EXISTS)) {
+                $select_opts[] = 'SUBSCRIBED';
+                $return_opts[] = 'SUBSCRIBED';
+            }
+
+            if (!empty($options['remote'])) {
+                $select_opts[] = 'REMOTE';
+            }
+
+            if (!empty($options['recursivematch'])) {
+                $select_opts[] = 'RECURSIVEMATCH';
+            }
+
+            if (!empty($select_opts)) {
+                $cmd .= ' (' . implode(' ', $select_opts) . ')';
+            }
+
+            $cmd .= ' "" ';
+
+            if (is_array($pattern)) {
+                $cmd .= '(';
+                foreach ($pattern as $val) {
+                    $cmd .= $this->utils->escape($pattern) . ' ';
+                }
+                $cmd = rtrim($cmd) . ')';
+            } else {
+                $cmd .= $this->utils->escape($pattern);
+            }
+
+            if (!empty($options['children'])) {
+                $return_opts[] = 'CHILDREN';
+            }
 
-        return (empty($options['flat'])) ? $t['listresponse'] : array_values($t['listresponse']);
+            if (!empty($return_opts)) {
+                $cmd .= ' RETURN (' . implode(' ', $return_opts) . ')';
+            }
+        } else {
+           $cmd = (($mode == Horde_Imap_Client::MBOX_SUBSCRIBED) ? 'LSUB' : 'LIST') . ' "" ' . $this->utils->escape($pattern);
+        }
+
+        $this->_sendLine($cmd);
+
+        return empty($options['flat'])
+            ? $t['listresponse']
+            : array_values($t['listresponse']);
     }
 
     /**
@@ -1020,14 +1073,25 @@ class Horde_Imap_Client_Socket extends Horde_Imap_Client_Base
             $mbox = Horde_Imap_Client_Utf7imap::Utf7ImapToUtf8($mbox);
         }
 
+        if ($ml['subexist'] ||
+            (empty($mlo['flat']) && !empty($mlo['attributes']))) {
+            $attr = array_map('strtolower', $data[1]);
+            if ($ml['subexist'] && in_array('\\nonexistent', $attr)) {
+                return;
+            }
+        }
+
         if (empty($mlo['flat'])) {
             $tmp = array('mailbox' => $mbox);
             if (!empty($mlo['attributes'])) {
-                $tmp['attributes'] = array_map('strtolower', $data[1]);
+                $tmp['attributes'] = $attr;
             }
             if (!empty($mlo['delimiter'])) {
                 $tmp['delimiter'] = $data[2];
             }
+            if (isset($data[4])) {
+                $tmp['extended'] = $data[4];
+            }
             $lr[$mbox] = $tmp;
         } else {
             $lr[] = $mbox;
index 843a350..9d1164f 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 Horde_Imap_Client_Utils::createUrl().
+ <notes>* Add support for RFC 5258 (LIST-EXTENDED).
+ * Add Horde_Imap_Client_Utils::createUrl().
  * Support SORT=DISPLAY extension.
  * Added search and thread (message list) caching.
  * Added PHP socket based POP3 driver.