Overview

Packages

  • IMP
  • None

Classes

  • IMP
  • IMP_Ajax_Application
  • IMP_Ajax_Imple_ContactAutoCompleter
  • IMP_Ajax_Imple_PassphraseDialog
  • IMP_Ajax_Queue
  • IMP_Api
  • IMP_Auth
  • IMP_Block_Newmail
  • IMP_Block_Summary
  • IMP_Compose
  • IMP_Compose_Exception
  • IMP_Compose_Stationery
  • IMP_Contents
  • IMP_Crypt_Pgp
  • IMP_Crypt_Smime
  • IMP_Dimp
  • IMP_Exception
  • IMP_Factory_AuthImap
  • IMP_Factory_Compose
  • IMP_Factory_Contents
  • IMP_Factory_Flags
  • IMP_Factory_Identity
  • IMP_Factory_Imap
  • IMP_Factory_Imaptree
  • IMP_Factory_Mail
  • IMP_Factory_Mailbox
  • IMP_Factory_MailboxList
  • IMP_Factory_MimeViewer
  • IMP_Factory_Pgp
  • IMP_Factory_Quota
  • IMP_Factory_Search
  • IMP_Factory_Sentmail
  • IMP_Factory_Smime
  • IMP_Filter
  • IMP_Flag_Base
  • IMP_Flag_Imap
  • IMP_Flag_Imap_Answered
  • IMP_Flag_Imap_Deleted
  • IMP_Flag_Imap_Draft
  • IMP_Flag_Imap_Flagged
  • IMP_Flag_Imap_Forwarded
  • IMP_Flag_Imap_Junk
  • IMP_Flag_Imap_NotJunk
  • IMP_Flag_Imap_Seen
  • IMP_Flag_System_Attachment
  • IMP_Flag_System_Encrypted
  • IMP_Flag_System_HighPriority
  • IMP_Flag_System_List
  • IMP_Flag_System_LowPriority
  • IMP_Flag_System_Match_Address
  • IMP_Flag_System_Match_Flag
  • IMP_Flag_System_Match_Header
  • IMP_Flag_System_Personal
  • IMP_Flag_System_Signed
  • IMP_Flag_System_Unseen
  • IMP_Flag_User
  • IMP_Flags
  • IMP_Imap
  • IMP_Imap_Acl
  • IMP_Imap_Exception
  • IMP_Imap_PermanentFlags
  • IMP_Imap_Thread
  • IMP_Imap_Tree
  • IMP_Indices
  • IMP_Indices_Form
  • IMP_LoginTasks_SystemTask_GarbageCollection
  • IMP_LoginTasks_SystemTask_Upgrade
  • IMP_LoginTasks_SystemTask_UpgradeAuth
  • IMP_LoginTasks_Task_Autocreate
  • IMP_LoginTasks_Task_DeleteAttachmentsMonthly
  • IMP_LoginTasks_Task_DeleteSentmailMonthly
  • IMP_LoginTasks_Task_FilterOnLogin
  • IMP_LoginTasks_Task_PurgeSentmail
  • IMP_LoginTasks_Task_PurgeSpam
  • IMP_LoginTasks_Task_PurgeTrash
  • IMP_LoginTasks_Task_RecoverDraft
  • IMP_LoginTasks_Task_RenameSentmailMonthly
  • IMP_Mailbox
  • IMP_Mailbox_List
  • IMP_Mailbox_List_Track
  • IMP_Maillog
  • IMP_Menu_Dimp
  • IMP_Message
  • IMP_Mime_Status
  • IMP_Mime_Viewer_Alternative
  • IMP_Mime_Viewer_Appledouble
  • IMP_Mime_Viewer_Audio
  • IMP_Mime_Viewer_Enriched
  • IMP_Mime_Viewer_Externalbody
  • IMP_Mime_Viewer_Html
  • IMP_Mime_Viewer_Images
  • IMP_Mime_Viewer_Itip
  • IMP_Mime_Viewer_Mdn
  • IMP_Mime_Viewer_Partial
  • IMP_Mime_Viewer_Pdf
  • IMP_Mime_Viewer_Pgp
  • IMP_Mime_Viewer_Plain
  • IMP_Mime_Viewer_Related
  • IMP_Mime_Viewer_Rfc822
  • IMP_Mime_Viewer_Smil
  • IMP_Mime_Viewer_Smime
  • IMP_Mime_Viewer_Status
  • IMP_Mime_Viewer_Vcard
  • IMP_Mime_Viewer_Video
  • IMP_Mime_Viewer_Zip
  • IMP_Notification_Event_Status
  • IMP_Notification_Handler_Decorator_ImapAlerts
  • IMP_Notification_Handler_Decorator_NewmailNotify
  • IMP_Notification_Listener_AjaxStatus
  • Imp_Prefs_Identity
  • IMP_Prefs_Ui
  • IMP_Quota
  • IMP_Quota_Base
  • IMP_Quota_Command
  • IMP_Quota_Hook
  • IMP_Quota_Imap
  • IMP_Quota_Maildir
  • IMP_Quota_Mdaemon
  • IMP_Quota_Mercury32
  • IMP_Quota_Null
  • IMP_Quota_Sql
  • IMP_Search
  • IMP_Search_Element
  • IMP_Search_Element_Attachment
  • IMP_Search_Element_Autogenerated
  • IMP_Search_Element_Bulk
  • IMP_Search_Element_Contacts
  • IMP_Search_Element_Date
  • IMP_Search_Element_Flag
  • IMP_Search_Element_Header
  • IMP_Search_Element_Mailinglist
  • IMP_Search_Element_Or
  • IMP_Search_Element_Personal
  • IMP_Search_Element_Recipient
  • IMP_Search_Element_Size
  • IMP_Search_Element_Text
  • IMP_Search_Element_Within
  • IMP_Search_Filter
  • IMP_Search_Filter_Attachment
  • IMP_Search_Filter_Autogenerated
  • IMP_Search_Filter_Builtin
  • IMP_Search_Filter_Bulk
  • IMP_Search_Filter_Contacts
  • IMP_Search_Filter_Mailinglist
  • IMP_Search_Filter_Personal
  • IMP_Search_Query
  • IMP_Search_Vfolder
  • IMP_Search_Vfolder_Builtin
  • IMP_Search_Vfolder_Vinbox
  • IMP_Search_Vfolder_Vtrash
  • IMP_Sentmail
  • IMP_Sentmail_Base
  • IMP_Sentmail_Null
  • IMP_Sentmail_Sql
  • IMP_Spam
  • IMP_Test
  • IMP_Tree_Flist
  • IMP_Tree_Jquerymobile
  • IMP_Tree_Simplehtml
  • IMP_Ui_Compose
  • IMP_Ui_Editor
  • IMP_Ui_Folder
  • IMP_Ui_Headers
  • IMP_Ui_Imageview
  • IMP_Ui_Mailbox
  • IMP_Ui_Message
  • IMP_Ui_Mimp
  • IMP_Ui_Search
  • IMP_Views_Compose
  • IMP_Views_ListMessages
  • IMP_Views_ShowMessage
  • Overview
  • Package
  • Class
  • Tree
   1: <?php
   2: /**
   3:  * The IMP_Imap_Tree class provides a tree view of the mailboxes in an
   4:  * IMAP/POP3 repository.  It provides access functions to iterate through this
   5:  * tree and query information about individual mailboxes.
   6:  * In IMP, folders = IMAP mailboxes so the two terms are used interchangably.
   7:  *
   8:  * Copyright 2000-2012 Horde LLC (http://www.horde.org/)
   9:  *
  10:  * See the enclosed file COPYING for license information (GPL). If you
  11:  * did not receive this file, see http://www.horde.org/licenses/gpl.
  12:  *
  13:  * @author   Chuck Hagenbuch <chuck@horde.org>
  14:  * @author   Jon Parise <jon@horde.org>
  15:  * @author   Anil Madhavapeddy <avsm@horde.org>
  16:  * @author   Michael Slusarz <slusarz@horde.org>
  17:  * @category Horde
  18:  * @license  http://www.horde.org/licenses/gpl GPL
  19:  * @package  IMP
  20:  */
  21: class IMP_Imap_Tree implements ArrayAccess, Countable, Iterator, Serializable
  22: {
  23:     /* Serialized version. */
  24:     const VERSION = 1;
  25: 
  26:     /* Constants for mailboxElt attributes. */
  27:     const ELT_NOSELECT = 1;
  28:     const ELT_NAMESPACE = 2;
  29:     const ELT_IS_OPEN = 4;
  30:     const ELT_IS_SUBSCRIBED = 8;
  31:     // Unused constant: 16
  32:     const ELT_IS_POLLED = 32;
  33:     const ELT_NEED_SORT = 64;
  34:     const ELT_VFOLDER = 128;
  35:     const ELT_NONIMAP = 256;
  36:     const ELT_INVISIBLE = 512;
  37: 
  38:     /* The isOpen() expanded mode constants. */
  39:     const OPEN_NONE = 0;
  40:     const OPEN_ALL = 1;
  41:     const OPEN_USER = 2;
  42: 
  43:     /* The folder list filtering constants. */
  44:     const FLIST_NOCONTAINER = 1;
  45:     const FLIST_UNSUB = 2;
  46:     const FLIST_VFOLDER = 4;
  47:     const FLIST_NOCHILDREN = 8;
  48:     const FLIST_EXPANDED = 16;
  49:     const FLIST_ANCESTORS = 32;
  50:     const FLIST_SAMELEVEL = 64;
  51:     const FLIST_NOBASE = 128;
  52:     const FLIST_ASIS = 256;
  53: 
  54:     /* The string used to indicate the base of the tree. This must include
  55:      * null since this is the only 7-bit character not allowed in IMAP
  56:      * mailboxes. */
  57:     const BASE_ELT = "base\0";
  58: 
  59:     /* Add null to folder key since it allows us to sort by name but
  60:      * never conflict with an IMAP mailbox. */
  61:     const VFOLDER_KEY = "vfolder\0";
  62: 
  63:     /* Defines used with namespace display. */
  64:     const SHARED_KEY = "shared\0";
  65:     const OTHER_KEY = "other\0";
  66: 
  67:     /**
  68:      * Tree changed flag.  Set when something in the tree has been altered.
  69:      *
  70:      * @var boolean
  71:      */
  72:     public $changed = false;
  73: 
  74:     /**
  75:      * Unseen count.
  76:      *
  77:      * @var array
  78:      */
  79:     public $unseen = 0;
  80: 
  81:     /**
  82:      * Array containing the mailbox tree.
  83:      *
  84:      * @var array
  85:      */
  86:     protected $_tree;
  87: 
  88:     /**
  89:      * Location of current element in the tree.
  90:      *
  91:      * @var string
  92:      */
  93:     protected $_currparent;
  94: 
  95:     /**
  96:      * Location of current element in the tree.
  97:      *
  98:      * @var integer
  99:      */
 100:     protected $_currkey;
 101: 
 102:     /**
 103:      * Show unsubscribed mailboxes?
 104:      *
 105:      * @var boolean
 106:      */
 107:     protected $_showunsub;
 108: 
 109:     /**
 110:      * Parent list.
 111:      *
 112:      * @var array
 113:      */
 114:     protected $_parent;
 115: 
 116:     /**
 117:      * The string used for the IMAP delimiter.
 118:      *
 119:      * @var string
 120:      */
 121:     protected $_delimiter;
 122: 
 123:     /**
 124:      * The list of namespaces to add to the tree.
 125:      *
 126:      * @var array
 127:      */
 128:     protected $_namespaces;
 129: 
 130:     /**
 131:      * Used to determine the list of element changes.
 132:      *
 133:      * @var array
 134:      */
 135:     protected $_eltdiff = null;
 136: 
 137:     /**
 138:      * Cached data that is not saved across serialization.
 139:      *
 140:      * @var array
 141:      */
 142:     protected $_cache = array(
 143:         'filter' => array(
 144:             'base' => null,
 145:             'mask' => 0
 146:         )
 147:     );
 148: 
 149:     /**
 150:      * Constructor.
 151:      */
 152:     public function __construct()
 153:     {
 154:         $this->init();
 155:     }
 156: 
 157:     /**
 158:      * Initalize the tree.
 159:      */
 160:     public function init()
 161:     {
 162:         global $conf, $injector, $prefs, $session;
 163: 
 164:         $imp_imap = $injector->getInstance('IMP_Factory_Imap')->create();
 165: 
 166:         $unsubmode = (!$imp_imap->access(IMP_Imap::ACCESS_FOLDERS) ||
 167:                       !$prefs->getValue('subscribe') ||
 168:                       $session->get('imp', 'showunsub'));
 169: 
 170:         /* Reset class variables to the defaults. */
 171:         $this->changed = true;
 172:         $this->_currkey = $this->_currparent = null;
 173:         $this->_delimiter = null;
 174:         $this->_namespaces = $this->_parent = $this->_tree = array();
 175:         $this->_showunsub = $unsubmode;
 176:         unset($this->_cache['fulllist'], $this->_cache['subscribed']);
 177: 
 178:         /* Do IMAP specific initialization. */
 179:         if ($imp_imap->imap) {
 180:             $ns = $imp_imap->getNamespaceList();
 181:             $ptr = reset($ns);
 182:             $this->_delimiter = $ptr['delimiter'];
 183:             if ($imp_imap->access(IMP_Imap::ACCESS_FOLDERS)) {
 184:                 $this->_namespaces = $ns;
 185:             }
 186:         }
 187: 
 188:         /* Create a placeholder element to the base of the tree list so we can
 189:          * keep track of whether the base level needs to be sorted. */
 190:         $this->_tree[self::BASE_ELT] = array(
 191:             'a' => self::ELT_NEED_SORT,
 192:             'v' => self::BASE_ELT
 193:         );
 194: 
 195:         /* Add INBOX and exit if folders aren't allowed or if we are using
 196:          * POP3. */
 197:         if (!$imp_imap->access(IMP_Imap::ACCESS_FOLDERS)) {
 198:             $this->_insertElt($this->_makeElt('INBOX', self::ELT_IS_SUBSCRIBED));
 199:             return;
 200:         }
 201: 
 202:         /* Add namespace elements. */
 203:         if ($prefs->getValue('tree_view')) {
 204:             foreach ($this->_namespaces as $val) {
 205:                 $type = null;
 206: 
 207:                 switch ($val['type']) {
 208:                 case Horde_Imap_Client::NS_OTHER:
 209:                     $type = self::OTHER_KEY;
 210:                     break;
 211: 
 212:                 case Horde_Imap_Client::NS_SHARED:
 213:                     $type = self::SHARED_KEY;
 214:                     break;
 215:                 }
 216: 
 217:                 if (!is_null($type) && !isset($this->_tree[$type])) {
 218:                     $this->_insertElt($this->_makeElt(
 219:                         $type,
 220:                         self::ELT_NOSELECT | self::ELT_NAMESPACE | self::ELT_NONIMAP
 221:                     ));
 222:                 }
 223:             }
 224:         }
 225: 
 226:         /* Create the list (INBOX and all other hierarchies). */
 227:         $this->_insert($this->_getList($this->_showunsub), $this->_showunsub ? null : true);
 228: 
 229:         /* Add virtual folders to the tree. */
 230:         $imp_search = $injector->getInstance('IMP_Search');
 231:         $imp_search->setIteratorFilter(IMP_Search::LIST_VFOLDER);
 232:         $this->updateVFolders(iterator_to_array($imp_search));
 233:     }
 234: 
 235:     /**
 236:      * Returns the list of mailboxes on the server.
 237:      *
 238:      * @param boolean $showunsub  Show unsubscribed mailboxes?
 239:      *
 240:      * @return array  See Horde_Imap_Client_Base::listMailboxes().
 241:      * @throws IMP_Imap_Exception
 242:      */
 243:     protected function _getList($showunsub)
 244:     {
 245:         if ($showunsub && isset($this->_cache['fulllist'])) {
 246:             return $this->_cache['fulllist'];
 247:         } elseif (!$showunsub && isset($this->_cache['subscribed'])) {
 248:             return $this->_cache['subscribed'];
 249:         }
 250: 
 251:         $searches = array();
 252:         foreach (array_keys($this->_namespaces) as $val) {
 253:             $searches[] = $val . '*';
 254:         }
 255: 
 256:         $imp_imap = $GLOBALS['injector']->getInstance('IMP_Factory_Imap')->create();
 257:         $result = $imp_imap->listMailboxes($searches, $showunsub ? Horde_Imap_Client::MBOX_ALL : Horde_Imap_Client::MBOX_SUBSCRIBED_EXISTS, array('attributes' => true, 'delimiter' => true, 'sort' => true));
 258: 
 259:         /* INBOX must always appear. */
 260:         if (empty($result['INBOX'])) {
 261:             $result = $imp_imap->listMailboxes('INBOX', Horde_Imap_Client::MBOX_ALL, array('attributes' => true, 'delimiter' => true)) + $result;
 262:         }
 263: 
 264:         $tmp = array();
 265:         foreach ($result as $val) {
 266:             $tmp[strval($val['mailbox'])] = $val;
 267:         }
 268:         $this->_cache[$showunsub ? 'fulllist' : 'subscribed'] = $tmp;
 269: 
 270:         return $result;
 271:     }
 272: 
 273:     /**
 274:      * Make a single mailbox tree element.
 275:      *
 276:      * @param string $name         The mailbox name.
 277:      * @param integer $attributes  The mailbox's attributes.
 278:      *
 279:      * @return array  An array with the following keys (we use single letters
 280:      *                to save in session storage space):
 281:      *   - a: (integer) Attributes mask.
 282:      *   - c: (integer) Level count.
 283:      *   - p: (string) Parent node.
 284:      *   - v: (string) Value.
 285:      *
 286:      * @throws Horde_Exception
 287:      */
 288:     protected function _makeElt($name, $attributes = 0)
 289:     {
 290:         $elt = array(
 291:             'a' => $attributes,
 292:             'c' => 0,
 293:             'p' => self::BASE_ELT,
 294:             'v' => strval($name)
 295:         );
 296: 
 297:         /* Check for polled status. */
 298:         $this->_initPollList();
 299:         $this->_setPolled($elt, isset($this->_cache['poll'][$name]));
 300: 
 301:         /* Check for open status. */
 302:         switch ($GLOBALS['prefs']->getValue('nav_expanded')) {
 303:         case self::OPEN_NONE:
 304:             $open = false;
 305:             break;
 306: 
 307:         case self::OPEN_ALL:
 308:             $open = true;
 309:             break;
 310: 
 311:         case self::OPEN_USER:
 312:             $this->_initExpandedList();
 313:             $open = !empty($this->_cache['expanded'][$name]);
 314:             break;
 315:         }
 316:         $this->_setOpen($elt, $open);
 317: 
 318:         if (is_null($this->_delimiter)) {
 319:             $elt['c'] = 0;
 320:             return $elt;
 321:         }
 322: 
 323:         $ns_info = $this->_getNamespace($name);
 324:         $delimiter = is_null($ns_info)
 325:             ? $this->_delimiter
 326:             : $ns_info['delimiter'];
 327:         $tmp = explode($delimiter, $name);
 328:         $elt['c'] = count($tmp) - 1;
 329: 
 330:         try {
 331:             $this->_setInvisible($elt, !Horde::callHook('display_folder', array($elt['v']), 'imp'));
 332:         } catch (Horde_Exception_HookNotSet $e) {}
 333: 
 334:         if ($elt['c'] != 0) {
 335:             $elt['p'] = implode(is_null($ns_info) ? $this->_delimiter : $ns_info['delimiter'], array_slice($tmp, 0, $elt['c']));
 336:         }
 337: 
 338:         if (is_null($ns_info)) {
 339:             return $elt;
 340:         }
 341: 
 342:         switch ($ns_info['type']) {
 343:         case Horde_Imap_Client::NS_PERSONAL:
 344:             /* Strip personal namespace. */
 345:             if (!empty($ns_info['name']) && ($elt['c'] != 0)) {
 346:                 --$elt['c'];
 347:                 if (strpos($elt['p'], $ns_info['delimiter']) === false) {
 348:                     $elt['p'] = self::BASE_ELT;
 349:                 } elseif (strpos($elt['v'], $ns_info['name'] . 'INBOX' . $ns_info['delimiter']) === 0) {
 350:                     $elt['p'] = 'INBOX';
 351:                 }
 352:             }
 353:             break;
 354: 
 355:         case Horde_Imap_Client::NS_OTHER:
 356:         case Horde_Imap_Client::NS_SHARED:
 357:             if (substr($ns_info['name'], 0, -1 * strlen($ns_info['delimiter'])) == $elt['v']) {
 358:                 $elt['a'] = self::ELT_NOSELECT | self::ELT_NAMESPACE;
 359:             }
 360: 
 361:             if ($GLOBALS['prefs']->getValue('tree_view')) {
 362:                 /* Don't add namespace element to tree. */
 363:                 if ($this->isNamespace($elt)) {
 364:                     return false;
 365:                 }
 366: 
 367:                 if ($elt['c'] == 1) {
 368:                     $elt['p'] = ($ns_info['type'] == Horde_Imap_Client::NS_OTHER)
 369:                         ? self::OTHER_KEY
 370:                         : self::SHARED_KEY;
 371:                 }
 372:             }
 373:             break;
 374:         }
 375: 
 376:         return $elt;
 377:     }
 378: 
 379:     /**
 380:      * Expand a mail folder.
 381:      *
 382:      * @param string $folder      The folder name to expand.
 383:      * @param boolean $expandall  Expand all folders under this one?
 384:      */
 385:     public function expand($folder, $expandall = false)
 386:     {
 387:         $folder = $this->_convertName($folder);
 388: 
 389:         if (!isset($this->_tree[$folder])) {
 390:             return;
 391:         }
 392:         $elt = &$this->_tree[$folder];
 393: 
 394:         if ($this->hasChildren($elt)) {
 395:             if (!$this->isOpen($elt)) {
 396:                 $this->changed = true;
 397:                 $this->_setOpen($elt, true);
 398:             }
 399: 
 400:             /* Expand all children beneath this one. */
 401:             if ($expandall && !empty($this->_parent[$folder])) {
 402:                 foreach ($this->_parent[$folder] as $val) {
 403:                     $this->expand($this->_tree[$val]['v'], true);
 404:                 }
 405:             }
 406:         }
 407:     }
 408: 
 409:     /**
 410:      * Collapse a mail folder.
 411:      *
 412:      * @param string $folder  The folder name to collapse.
 413:      */
 414:     public function collapse($folder)
 415:     {
 416:         $folder = $this->_convertName($folder);
 417: 
 418:         if (isset($this->_tree[$folder])) {
 419:             $this->changed = true;
 420:             $this->_setOpen($this->_tree[$folder], false);
 421:         }
 422:     }
 423: 
 424:     /**
 425:      * Insert a folder/mailbox into the tree.
 426:      *
 427:      * @param mixed $id  The name of the folder (or a list of folder names)
 428:      *                   to add.
 429:      */
 430:     public function insert($id)
 431:     {
 432:         if (is_array($id)) {
 433:             /* We want to add from the BASE of the tree up for efficiency
 434:              * sake. */
 435:             $this->_sortList($id);
 436:         } else {
 437:             $id = array($id);
 438:         }
 439: 
 440:         /* Process Virtual Folders here. */
 441:         reset($id);
 442:         while (list($key, $val) = each($id)) {
 443:             if (strpos($val, self::VFOLDER_KEY) === 0) {
 444:                 if (!isset($this->_tree[$val])) {
 445:                     if (!isset($this->_tree[self::VFOLDER_KEY])) {
 446:                         $elt = $this->_makeElt(self::VFOLDER_KEY, self::ELT_VFOLDER | self::ELT_NOSELECT | self::ELT_NONIMAP);
 447:                         $this->_insertElt($elt);
 448:                     }
 449: 
 450:                     $elt = $this->_makeElt($val, self::ELT_VFOLDER | self::ELT_IS_SUBSCRIBED | self::ELT_NONIMAP);
 451:                     $elt['v'] = Horde_String::substr($val, Horde_String::length(self::VFOLDER_KEY) + Horde_String::length($this->_delimiter));
 452:                     $this->_insertElt($elt);
 453:                 }
 454: 
 455:                 unset($id[$key]);
 456:             }
 457:         }
 458: 
 459:         if (!empty($id)) {
 460:             try {
 461:                 $this->_insert($GLOBALS['injector']->getInstance('IMP_Factory_Imap')->create()->listMailboxes($id, Horde_Imap_Client::MBOX_ALL, array('attributes' => true, 'delimiter' => true, 'sort' => true)));
 462:             } catch (IMP_Imap_Exception $e) {}
 463:         }
 464:     }
 465: 
 466:     /**
 467:      * Insert mailbox elements into the tree.
 468:      *
 469:      * @param array $elts   See Horde_Imap_Client_Base::listMailboxes().
 470:      * @param boolean $sub  If set, the list of $elts are known to be either
 471:      *                      all subscribed (true) or unsubscribed (false). If
 472:      *                      null, subscribed status must be looked up on the
 473:      *                      server.
 474:      */
 475:     protected function _insert($elts, $sub = null)
 476:     {
 477:         $sub_pref = $GLOBALS['prefs']->getValue('subscribe');
 478: 
 479:         foreach ($elts as $val) {
 480:             $key = strval($val['mailbox']);
 481:             if (isset($this->_tree[$key]) ||
 482:                 in_array('\nonexistent', $val['attributes'])) {
 483:                 continue;
 484:             }
 485: 
 486:             /* Break apart the name via the delimiter and go step by
 487:              * step through the name to make sure all subfolders exist
 488:              * in the tree. */
 489:             $parts = explode($val['delimiter'], $key);
 490:             $parts[0] = $this->_convertName($parts[0]);
 491:             for ($i = 1, $p_count = count($parts); $i <= $p_count; ++$i) {
 492:                 $part = implode($val['delimiter'], array_slice($parts, 0, $i));
 493: 
 494:                 if (!isset($this->_tree[$part])) {
 495:                     $attributes = 0;
 496: 
 497:                     /* Set subscribed values. We know the folder is
 498:                      * subscribed, without query of the IMAP server, in the
 499:                      * following situations:
 500:                      * + Subscriptions are turned off.
 501:                      * + $sub is true.
 502:                      * + Folder is INBOX.
 503:                      * + Folder has the \subscribed attribute set. */
 504:                     if (!$sub_pref ||
 505:                         (($i == $p_count) &&
 506:                          (($sub === true) ||
 507:                           ($key == 'INBOX') ||
 508:                           in_array('\subscribed', $val['attributes'])))) {
 509:                         $attributes |= self::ELT_IS_SUBSCRIBED;
 510:                     } elseif (is_null($sub) && ($i == $p_count)) {
 511:                         $this->_getList(false);
 512:                         if (isset($this->_cache['subscribed'][$part])) {
 513:                             $attributes |= self::ELT_IS_SUBSCRIBED;
 514:                         }
 515:                     }
 516: 
 517:                     if (($i != $p_count) ||
 518:                         in_array('\noselect', $val['attributes'])) {
 519:                         $attributes |= self::ELT_NOSELECT;
 520:                     }
 521: 
 522:                     $this->_insertElt($this->_makeElt($part, $attributes));
 523:                 }
 524:             }
 525:         }
 526:     }
 527: 
 528:     /**
 529:      * Insert an element into the tree.
 530:      *
 531:      * @param array $elt  The element to insert. The key in the tree is the
 532:      *                    'v' (value) element of the element.
 533:      */
 534:     protected function _insertElt($elt)
 535:     {
 536:         if (!$elt || isset($this->_tree[$elt['v']])) {
 537:             return;
 538:         }
 539: 
 540:         // UW fix - it may return both 'foo' and 'foo/' as folder names.
 541:         // Only add one of these (without the namespace character) to
 542:         // the tree.  See Ticket #5764.
 543:         $ns_info = $this->_getNamespace($elt['v']);
 544:         if (isset($this->_tree[rtrim($elt['v'], is_null($ns_info) ? $this->_delimiter : $ns_info['delimiter'])])) {
 545:             return;
 546:         }
 547: 
 548:         $this->changed = true;
 549: 
 550:         $prev = is_null($this->_eltdiff)
 551:             ? null
 552:             : $this->hasChildren($this->_tree[$elt['p']]);
 553: 
 554:         /* Set the parent array to the value in $elt['p']. */
 555:         if (empty($this->_parent[$elt['p']])) {
 556:             $this->_parent[$elt['p']] = array();
 557:         }
 558: 
 559:         $this->_parent[$elt['p']][] = $elt['v'];
 560:         $this->_tree[$elt['v']] = $elt;
 561: 
 562:         $this->_addEltDiff($elt, 'a');
 563:         if (!is_null($prev) &&
 564:             ($this->hasChildren($this->_tree[$elt['p']]) != $prev)) {
 565:             $this->_addEltDiff($this->_tree[$elt['p']], 'c');
 566:         }
 567: 
 568:         /* Make sure we are sorted correctly. */
 569:         if (count($this->_parent[$elt['p']]) > 1) {
 570:             $this->_setNeedSort($this->_tree[$elt['p']], true);
 571:         }
 572:     }
 573: 
 574:     /**
 575:      * Delete an element from the tree.
 576:      *
 577:      * @param mixed $id  The element name or an array of element names.
 578:      *
 579:      * @return boolean  Return true on success, false on error.
 580:      */
 581:     public function delete($id)
 582:     {
 583:         if (is_array($id)) {
 584:             /* We want to delete from the TOP of the tree down to ensure that
 585:              * parents have an accurate view of what children are left. */
 586:             $this->_sortList($id);
 587:             $id = array_reverse($id);
 588: 
 589:             foreach ($id as $val) {
 590:                 $currsuccess = $this->delete($val);
 591:                 if (!$currsuccess) {
 592:                     return false;
 593:                 }
 594:             }
 595: 
 596:             return true;
 597:         }
 598: 
 599:         $id = $this->_convertName($id, true);
 600:         $vfolder_base = ($id == self::VFOLDER_KEY);
 601:         $search_id = $GLOBALS['injector']->getInstance('IMP_Search')->createSearchId($id);
 602: 
 603:         if ($vfolder_base ||
 604:             (isset($this->_tree[$search_id]) &&
 605:              $this->isVFolder($this->_tree[$search_id]))) {
 606:             if (!$vfolder_base) {
 607:                 $id = $search_id;
 608:             }
 609: 
 610:             $parent = $this->_tree[$id]['p'];
 611:             $this->_addEltDiff($this->_tree[$id], 'd');
 612:             unset($this->_tree[$id]);
 613: 
 614:             /* Delete the entry from the parent tree. */
 615:             $key = array_search($id, $this->_parent[$parent]);
 616:             unset($this->_parent[$parent][$key]);
 617: 
 618:             /* Rebuild the parent tree. */
 619:             if (!$vfolder_base && empty($this->_parent[$parent])) {
 620:                 $this->delete($parent);
 621:             } else {
 622:                 $this->_parent[$parent] = array_values($this->_parent[$parent]);
 623:             }
 624:             $this->changed = true;
 625: 
 626:             return true;
 627:         }
 628: 
 629:         $ns_info = $this->_getNamespace($id);
 630: 
 631:         if (($id == 'INBOX') ||
 632:             !isset($this->_tree[$id]) ||
 633:             ($id == $ns_info['name'])) {
 634:             return false;
 635:         }
 636: 
 637:         $this->changed = true;
 638: 
 639:         $elt = &$this->_tree[$id];
 640: 
 641:         /* Delete the entry from the folder list cache(s). */
 642:         unset($this->_cache['fulllist'][$id], $this->_cache['subscribed'][$id]);
 643: 
 644:         /* Do not delete from tree if there are child elements - instead,
 645:          * convert to a container element. */
 646:         if ($this->hasChildren($elt)) {
 647:             $this->_setContainer($elt, true);
 648:             return true;
 649:         }
 650: 
 651:         $parent = $elt['p'];
 652: 
 653:         /* Delete the tree entry. */
 654:         $this->_addEltDiff($elt, 'd');
 655:         unset($this->_tree[$id]);
 656: 
 657:         /* Delete the entry from the parent tree. */
 658:         $key = array_search($id, $this->_parent[$parent]);
 659:         unset($this->_parent[$parent][$key]);
 660: 
 661:         if (empty($this->_parent[$parent])) {
 662:             /* This folder is now completely empty (no children). */
 663:             unset($this->_parent[$parent]);
 664:             if (isset($this->_tree[$parent])) {
 665:                 if ($this->isContainer($this->_tree[$parent]) &&
 666:                     !$this->isNamespace($this->_tree[$parent])) {
 667:                     $this->delete($parent);
 668:                 } else {
 669:                     $this->_modifyExpandedList($parent, 'remove');
 670:                     $this->_setOpen($this->_tree[$parent], false);
 671:                     $this->_addEltDiff($this->_tree[$parent], 'c');
 672:                 }
 673:             }
 674:         } else {
 675:             /* Rebuild the parent tree. */
 676:             $this->_parent[$parent] = array_values($this->_parent[$parent]);
 677: 
 678:             if (!$this->hasChildren($this->_tree[$parent])) {
 679:                 $this->_addEltDiff($this->_tree[$parent], 'c');
 680:             }
 681:         }
 682: 
 683:         /* Remove the mailbox from the expanded folders list. */
 684:         $this->_modifyExpandedList($id, 'remove');
 685: 
 686:         /* Remove the mailbox from the nav_poll list. */
 687:         $this->removePollList($id);
 688: 
 689:         return true;
 690:     }
 691: 
 692:     /**
 693:      * Subscribe an element to the tree.
 694:      *
 695:      * @param mixed $id  The element name or an array of element names.
 696:      */
 697:     public function subscribe($id)
 698:     {
 699:         if (!is_array($id)) {
 700:             $id = array($id);
 701:         }
 702: 
 703:         foreach ($id as $val) {
 704:             $val = $this->_convertName($val);
 705:             if (isset($this->_tree[$val])) {
 706:                 $this->changed = true;
 707:                 $this->_setSubscribed($this->_tree[$val], true);
 708:                 $this->_setContainer($this->_tree[$val], false);
 709:             }
 710:         }
 711:     }
 712: 
 713:     /**
 714:      * Unsubscribe an element from the tree.
 715:      *
 716:      * @param mixed $id  The element name or an array of element names.
 717:      */
 718:     public function unsubscribe($id)
 719:     {
 720:         if (!is_array($id)) {
 721:             $id = array($id);
 722:         } else {
 723:             /* We want to delete from the TOP of the tree down to ensure that
 724:              * parents have an accurate view of what children are left. */
 725:             $this->_sortList($id);
 726:             $id = array_reverse($id);
 727:         }
 728: 
 729:         foreach ($id as $val) {
 730:             $val = $this->_convertName($val);
 731: 
 732:             /* INBOX can never be unsubscribed to. */
 733:             if (isset($this->_tree[$val]) && ($val != 'INBOX')) {
 734:                 $this->changed = true;
 735: 
 736:                 $elt = &$this->_tree[$val];
 737: 
 738:                 /* Do not delete from tree if there are child elements -
 739:                  * instead, convert to a container element. */
 740:                 if (!$this->_showunsub && $this->hasChildren($elt)) {
 741:                     $this->_setContainer($elt, true);
 742:                 }
 743: 
 744:                 /* Set as unsubscribed, add to unsubscribed list, and remove
 745:                  * from subscribed list. */
 746:                 $this->_setSubscribed($elt, false);
 747:             }
 748:         }
 749:     }
 750: 
 751:     /**
 752:      * Set an attribute for an element.
 753:      *
 754:      * @param array &$elt     The tree element.
 755:      * @param integer $const  The constant to set/remove from the bitmask.
 756:      * @param boolean $bool   Should the attribute be set?
 757:      */
 758:     protected function _setAttribute(&$elt, $const, $bool)
 759:     {
 760:         if ($bool) {
 761:             $elt['a'] |= $const;
 762:         } else {
 763:             $elt['a'] &= ~$const;
 764:         }
 765:     }
 766: 
 767:     /**
 768:      * Does the element have any active children?
 769:      *
 770:      * @param mixed $in  A mailbox name or a tree element.
 771:      *
 772:      * @return boolean  True if the element has active children.
 773:      */
 774:     public function hasChildren($in)
 775:     {
 776:         $elt = $this->getElement($in);
 777: 
 778:         if ($elt && isset($this->_parent[$elt['v']])) {
 779:             foreach ($this->_parent[$elt['v']] as $val) {
 780:                 if ($this->_showunsub &&
 781:                     !$this->isContainer($this->_tree[$val]) &&
 782:                     !$this->isNamespace($this->_tree[$val])) {
 783:                     return true;
 784:                 } elseif ($this->isSubscribed($this->_tree[$val]) ||
 785:                           $this->hasChildren($this->_tree[$val])) {
 786:                     return true;
 787:                 }
 788:             }
 789:         }
 790: 
 791:         return false;
 792:     }
 793: 
 794:     /**
 795:      * Is the tree element open?
 796:      *
 797:      * @param mixed $in  A mailbox name or a tree element.
 798:      *
 799:      * @return integer  True if the element is open.
 800:      */
 801:     public function isOpen($in)
 802:     {
 803:         $elt = $this->getElement($in);
 804: 
 805:         return ($elt &&
 806:                 ($elt['a'] & self::ELT_IS_OPEN) &&
 807:                 $this->hasChildren($elt));
 808:     }
 809: 
 810:     /**
 811:      * Set the open attribute for an element.
 812:      *
 813:      * @param array &$elt    A tree element.
 814:      * @param boolean $bool  The setting.
 815:      */
 816:     protected function _setOpen(&$elt, $bool)
 817:     {
 818:         $this->_setAttribute($elt, self::ELT_IS_OPEN, $bool);
 819:         $this->_modifyExpandedList($elt['v'], $bool ? 'add' : 'remove');
 820:     }
 821: 
 822:     /**
 823:      * Is this element a container only, not a mailbox (meaning you can
 824:      * not open it)?
 825:      *
 826:      * @param mixed $in  A mailbox name or a tree element.
 827:      *
 828:      * @return integer  True if the element is a container.
 829:      */
 830:     public function isContainer($in)
 831:     {
 832:         $elt = $this->getElement($in);
 833: 
 834:         return ($elt &&
 835:                 (($elt['a'] & self::ELT_NOSELECT) ||
 836:                  (!$this->_showunsub &&
 837:                   !$this->isSubscribed($elt) &&
 838:                   $this->hasChildren($elt))));
 839:     }
 840: 
 841:     /**
 842:      * Set the element as a container?
 843:      *
 844:      * @param array &$elt    A tree element.
 845:      * @param boolean $bool  Is the element a container?
 846:      */
 847:     protected function _setContainer(&$elt, $bool)
 848:     {
 849:         $this->_setAttribute($elt, self::ELT_NOSELECT, $bool);
 850:         $this->_addEltDiff($elt, 'c');
 851:     }
 852: 
 853:     /**
 854:      * Is the user subscribed to this element?
 855:      *
 856:      * @param mixed $in  A mailbox name or a tree element.
 857:      *
 858:      * @return integer  True if the user is subscribed to the element.
 859:      */
 860:     public function isSubscribed($in)
 861:     {
 862:         $elt = $this->getElement($in);
 863: 
 864:         return ($elt && ($elt['a'] & self::ELT_IS_SUBSCRIBED));
 865:     }
 866: 
 867:     /**
 868:      * Set the subscription status for an element.
 869:      *
 870:      * @param array &$elt    A tree element.
 871:      * @param boolean $bool  Is the element subscribed to?
 872:      */
 873:     protected function _setSubscribed(&$elt, $bool)
 874:     {
 875:         $this->_setAttribute($elt, self::ELT_IS_SUBSCRIBED, $bool);
 876:         if (isset($this->_cache['subscribed'])) {
 877:             if ($bool) {
 878:                 $this->_cache['subscribed'][$elt['v']] = 1;
 879:             } else {
 880:                 unset($this->_cache['subscribed'][$elt['v']]);
 881:             }
 882:         }
 883:     }
 884: 
 885:     /**
 886:      * Is the element a namespace container?
 887:      *
 888:      * @param mixed $in  A mailbox name or a tree element.
 889:      *
 890:      * @return integer  True if the element is a namespace container.
 891:      */
 892:     public function isNamespace($in)
 893:     {
 894:         $elt = $this->getElement($in);
 895: 
 896:         return ($elt && ($elt['a'] & self::ELT_NAMESPACE));
 897:     }
 898: 
 899:     /**
 900:      * Is the element a non-IMAP element?
 901:      *
 902:      * @param mixed $in  A mailbox name or a tree element.
 903:      *
 904:      * @return integer  True if the element is a non-IMAP element.
 905:      */
 906:     public function isNonImapElt($in)
 907:     {
 908:         $elt = $this->getElement($in);
 909: 
 910:         return ($elt && ($elt['a'] & self::ELT_NONIMAP));
 911:     }
 912: 
 913:     /**
 914:      * Initialize the expanded folder list.
 915:      */
 916:     protected function _initExpandedList()
 917:     {
 918:         if (!isset($this->_cache['expanded'])) {
 919:             $serialized = $GLOBALS['prefs']->getValue('expanded_folders');
 920:             $this->_cache['expanded'] = $serialized
 921:                 ? unserialize($serialized)
 922:                 : array();
 923:         }
 924:     }
 925: 
 926:     /**
 927:      * Add/remove an element to the expanded list.
 928:      *
 929:      * @param string $id      The element name to add/remove.
 930:      * @param string $action  Either 'add' or 'remove';
 931:      */
 932:     protected function _modifyExpandedList($id, $action)
 933:     {
 934:         $this->_initExpandedList();
 935: 
 936:         if ($action == 'add') {
 937:             $change = empty($this->_cache['expanded'][$id]);
 938:             $this->_cache['expanded'][$id] = true;
 939:         } else {
 940:             $change = !empty($this->_cache['expanded'][$id]);
 941:             unset($this->_cache['expanded'][$id]);
 942:         }
 943: 
 944:         if ($change) {
 945:             $GLOBALS['prefs']->setValue('expanded_folders', serialize($this->_cache['expanded']));
 946:         }
 947:     }
 948: 
 949:     /**
 950:      * Initializes and returns the list of mailboxes to poll.
 951:      *
 952:      * @param boolean $sort  Sort the directory list?
 953:      *
 954:      * @return array  The list of mailboxes to poll (IMP_Mailbox objects).
 955:      */
 956:     public function getPollList($sort = false)
 957:     {
 958:         $this->setIteratorFilter(self::FLIST_NOCONTAINER);
 959: 
 960:         if ($GLOBALS['prefs']->getValue('nav_poll_all')) {
 961:             return iterator_to_array($this);
 962:         }
 963: 
 964:         $plist = array();
 965:         foreach ($this as $val) {
 966:             if ($this->isPolled($val)) {
 967:                 $plist[] = $val;
 968:             }
 969:         }
 970: 
 971:         if ($sort) {
 972:             $ns_new = $this->_getNamespace(null);
 973:             Horde_Imap_Client_Sort::sortMailboxes($plist, array(
 974:                 'delimiter' => $ns_new['delimiter'],
 975:                 'inbox' => true
 976:             ));
 977:         }
 978: 
 979:         return IMP_Mailbox::get(array_filter($plist));
 980:     }
 981: 
 982:     /**
 983:      * Init the poll list.
 984:      */
 985:     protected function _initPollList()
 986:     {
 987:         if (!isset($this->_cache['poll']) &&
 988:             !$GLOBALS['prefs']->getValue('nav_poll_all')) {
 989:             /* We ALWAYS poll the INBOX. */
 990:             $this->_cache['poll'] = array('INBOX' => 1);
 991: 
 992:             /* Add the list of polled mailboxes from the prefs. */
 993:             if ($navPollList = @unserialize($GLOBALS['prefs']->getValue('nav_poll'))) {
 994:                 $this->_cache['poll'] += $navPollList;
 995:             }
 996:         }
 997:     }
 998: 
 999:     /**
1000:      * Add element to the poll list.
1001:      *
1002:      * @param mixed $id  The element name or a list of element names to add.
1003:      */
1004:     public function addPollList($id)
1005:     {
1006:         if (!is_array($id)) {
1007:             $id = array($id);
1008:         }
1009: 
1010:         if (empty($id) ||
1011:             $GLOBALS['prefs']->getValue('nav_poll_all') ||
1012:             $GLOBALS['prefs']->isLocked('nav_poll')) {
1013:             return;
1014:         }
1015: 
1016:         $changed = false;
1017: 
1018:         $this->_initPollList();
1019: 
1020:         foreach (IMP_Mailbox::get($id) as $val) {
1021:             if ($val->nonimap || $val->container) {
1022:                 continue;
1023:             }
1024: 
1025:             $mbox_str = strval($val);
1026: 
1027:             if (!$this->isSubscribed($this->_tree[$mbox_str])) {
1028:                 $val->subscribe(true);
1029:             }
1030:             $this->_setPolled($this->_tree[$mbox_str], true);
1031:             if (empty($this->_cache['poll'][$mbox_str])) {
1032:                 $this->_cache['poll'][$mbox_str] = true;
1033:                 $changed = true;
1034:             }
1035:         }
1036: 
1037:         if ($changed) {
1038:             $this->_updatePollList();
1039:         }
1040:     }
1041: 
1042:     /**
1043:      * Remove element from the poll list.
1044:      *
1045:      * @param mixed $id  The folder/mailbox or a list of folders/mailboxes to
1046:      *                   remove.
1047:      */
1048:     public function removePollList($id)
1049:     {
1050:         if ($GLOBALS['prefs']->getValue('nav_poll_all') ||
1051:             $GLOBALS['prefs']->isLocked('nav_poll')) {
1052:             return;
1053:         }
1054: 
1055:         if (!is_array($id)) {
1056:             $id = array($id);
1057:         }
1058: 
1059:         $removed = false;
1060: 
1061:         $this->_initPollList();
1062: 
1063:         foreach (IMP_Mailbox::get($id) as $val) {
1064:             if (!$val->inbox &&
1065:                 isset($this->_cache['poll'][strval($val)])) {
1066:                 unset($this->_cache['poll'][strval($val)]);
1067:                 if (isset($this->_tree[strval($val)])) {
1068:                     $this->_setPolled($this->_tree[strval($val)], false);
1069:                 }
1070:                 $removed = true;
1071:             }
1072:         }
1073: 
1074:         if ($removed) {
1075:             $this->_updatePollList();
1076:         }
1077:     }
1078: 
1079:     /**
1080:      * Update the nav_poll preference.
1081:      */
1082:     protected function _updatePollList()
1083:     {
1084:         $GLOBALS['prefs']->setValue('nav_poll', serialize($this->_cache['poll']));
1085:         $this->changed = true;
1086:     }
1087: 
1088:     /**
1089:      * Prune non-existent folders from poll list.
1090:      */
1091:     public function prunePollList()
1092:     {
1093:         $prune = array();
1094: 
1095:         $this->setIteratorFilter(self::FLIST_NOCONTAINER);
1096:         foreach ($this as $val) {
1097:             if (!$this->isPolled($val) || !$val->exists) {
1098:                 $prune[] = $val;
1099:             }
1100:         }
1101: 
1102:         $this->removePollList($prune);
1103:     }
1104: 
1105:     /**
1106:      * Does the user want to poll this mailbox for new/unseen messages?
1107:      *
1108:      * @param mixed $in  A mailbox name or a tree element.
1109:      *
1110:      * @return integer  True if the user wants to poll the element.
1111:      */
1112:     public function isPolled($in)
1113:     {
1114:         $elt = $this->getElement($in);
1115: 
1116:         if ($this->isNonImapElt($in) || $this->isContainer($in)) {
1117:             return false;
1118:         }
1119: 
1120:         return $GLOBALS['prefs']->getValue('nav_poll_all')
1121:             ? true
1122:             : ($elt && ($elt['a'] & self::ELT_IS_POLLED));
1123:     }
1124: 
1125:     /**
1126:      * Set the polled attribute for an element.
1127:      *
1128:      * @param array &$elt    A tree element.
1129:      * @param boolean $bool  The setting.
1130:      */
1131:     protected function _setPolled(&$elt, $bool)
1132:     {
1133:         $this->_setAttribute($elt, self::ELT_IS_POLLED, $bool);
1134:     }
1135: 
1136:     /**
1137:      * Is the element invisible?
1138:      *
1139:      * @param mixed $in  A mailbox name or a tree element.
1140:      *
1141:      * @return integer  True if the element is marked as invisible.
1142:      */
1143:     public function isInvisible($in)
1144:     {
1145:         $elt = $this->getElement($in);
1146: 
1147:         return ($elt && ($elt['a'] & self::ELT_INVISIBLE));
1148:     }
1149: 
1150:     /**
1151:      * Set the invisible attribute for an element.
1152:      *
1153:      * @param array &$elt    A tree element.
1154:      * @param boolean $bool  The setting.
1155:      */
1156:     protected function _setInvisible(&$elt, $bool)
1157:     {
1158:         $this->_setAttribute($elt, self::ELT_INVISIBLE, $bool);
1159:     }
1160: 
1161:     /**
1162:      * Flag the element as needing its children to be sorted.
1163:      *
1164:      * @param array &$elt    A tree element.
1165:      * @param boolean $bool  The setting.
1166:      */
1167:     protected function _setNeedSort(&$elt, $bool)
1168:     {
1169:         $this->_setAttribute($elt, self::ELT_NEED_SORT, $bool);
1170:     }
1171: 
1172:     /**
1173:      * Does this element's children need sorting?
1174:      *
1175:      * @param array $elt  A tree element.
1176:      *
1177:      * @return integer  True if the children need to be sorted.
1178:      */
1179:     protected function _needSort($elt)
1180:     {
1181:         return (($elt['a'] & self::ELT_NEED_SORT) && (count($this->_parent[$elt['v']]) > 1));
1182:     }
1183: 
1184:     /**
1185:      * Should we expand all elements?
1186:      */
1187:     public function expandAll()
1188:     {
1189:         foreach ($this->_parent[self::BASE_ELT] as $val) {
1190:             $this->expand($val, true);
1191:         }
1192:     }
1193: 
1194:     /**
1195:      * Should we collapse all elements?
1196:      */
1197:     public function collapseAll()
1198:     {
1199:         foreach ($this->_tree as $key => $val) {
1200:             if ($key !== self::BASE_ELT) {
1201:                 $this->collapse($val['v']);
1202:             }
1203:         }
1204:     }
1205: 
1206:     /**
1207:      * Switch subscribed/unsubscribed viewing.
1208:      *
1209:      * @param boolean $unsub  Show unsubscribed elements?
1210:      */
1211:     public function showUnsubscribed($unsub)
1212:     {
1213:         if ((bool)$unsub === $this->_showunsub) {
1214:             return;
1215:         }
1216: 
1217:         $this->_showunsub = $unsub;
1218:         $this->changed = true;
1219: 
1220:         /* If we are switching from unsubscribed to subscribed, no need
1221:          * to do anything (we just ignore unsubscribed stuff). */
1222:         if ($unsub === false) {
1223:             return;
1224:         }
1225: 
1226:         /* If we are switching from subscribed to unsubscribed, we need
1227:          * to add all unsubscribed elements that live in currently
1228:          * discovered items. */
1229:         $old_eltdiff = $this->_eltdiff;
1230:         $this->_eltdiff = null;
1231:         $this->_insert($this->_getList(true), false);
1232:         $this->_eltdiff = $old_eltdiff;
1233:     }
1234: 
1235:     /**
1236:      * Sorts a list of mailboxes.
1237:      *
1238:      * @param array &$mbox   The list of mailboxes to sort.
1239:      * @param boolean $base  Are we sorting a list of mailboxes in the base
1240:      *                       of the tree.
1241:      */
1242:     protected function _sortList(&$mbox, $base = false)
1243:     {
1244:         if (!$base) {
1245:             Horde_Imap_Client_Sort::sortMailboxes($mbox, array('delimiter' => $this->_delimiter));
1246:             return;
1247:         }
1248: 
1249:         $basesort = $othersort = array();
1250:         /* INBOX always appears first. */
1251:         $sorted = array('INBOX');
1252: 
1253:         foreach ($mbox as $key => $val) {
1254:             if ($this->isNonImapElt($this->_tree[$val])) {
1255:                 $othersort[$key] = IMP_Mailbox::get($val)->label;
1256:             } elseif ($val !== 'INBOX') {
1257:                 $basesort[$key] = IMP_Mailbox::get($val)->label;
1258:             }
1259:         }
1260: 
1261:         natcasesort($basesort);
1262:         natcasesort($othersort);
1263:         foreach (array_merge(array_keys($basesort), array_keys($othersort)) as $key) {
1264:             $sorted[] = $mbox[$key];
1265:         }
1266: 
1267:         $mbox = $sorted;
1268:     }
1269: 
1270:     /**
1271:      * Convert a mailbox name to the correct, internal name (i.e. make sure
1272:      * INBOX is always capitalized for IMAP servers).
1273:      *
1274:      * @param string $name  The mailbox name.
1275:      *
1276:      * @return string  The converted name.
1277:      */
1278:     protected function _convertName($name)
1279:     {
1280:         return (strcasecmp($name, 'INBOX') == 0)
1281:             ? 'INBOX'
1282:             : strval($name);
1283:     }
1284: 
1285:     /**
1286:      * Get namespace info for a full folder path.
1287:      *
1288:      * @param string $mailbox  The folder path.
1289:      *
1290:      * @return mixed  The namespace info for the folder path or null if the
1291:      *                path doesn't exist.
1292:      */
1293:     protected function _getNamespace($mailbox)
1294:     {
1295:         if (!in_array($mailbox, array(self::OTHER_KEY, self::SHARED_KEY, self::VFOLDER_KEY)) &&
1296:             (strpos($mailbox, self::VFOLDER_KEY . $this->_delimiter) !== 0)) {
1297:             return $GLOBALS['injector']->getInstance('IMP_Factory_Imap')->create()->getNamespace($mailbox);
1298:         }
1299:         return null;
1300:     }
1301: 
1302:     /**
1303:      * Set the start point for determining element differences via eltDiff().
1304:      */
1305:     public function eltDiffStart()
1306:     {
1307:         $this->_eltdiff = array(
1308:             'a' => array(),
1309:             'c' => array(),
1310:             'd' => array(),
1311:             'o' => array()
1312:         );
1313:     }
1314: 
1315:     /**
1316:      * Mark an element as changed.
1317:      *
1318:      * @param array $elt    An element array.
1319:      * @param string $type  Either 'a', 'c', or 'd'.
1320:      */
1321:     protected function _addEltDiff($elt, $type)
1322:     {
1323:         if (is_null($this->_eltdiff)) {
1324:             return;
1325:         }
1326: 
1327:         $ed = &$this->_eltdiff;
1328:         $id = $elt['v'];
1329: 
1330:         if (array_key_exists($id, $ed['o'])) {
1331:             if (($type != 'd') && ($ed['o'][$id] == $elt)) {
1332:                 unset(
1333:                     $ed['a'][$id],
1334:                     $ed['c'][$id],
1335:                     $ed['d'][$id],
1336:                     $ed['o'][$id]
1337:                 );
1338:                 return;
1339:             }
1340:         } else {
1341:             $ed['o'][$id] = ($type == 'a')
1342:                 ? null
1343:                 : $elt;
1344:         }
1345: 
1346:         switch ($type) {
1347:         case 'a':
1348:             unset($ed['c'][$id], $ed['d'][$id]);
1349:             $ed['a'][$id] = 1;
1350:             break;
1351: 
1352:         case 'c':
1353:             if (!isset($ed['a'][$id])) {
1354:                 $ed['c'][$id] = 1;
1355:             }
1356:             break;
1357: 
1358:         case 'd':
1359:             unset($ed['a'][$id], $ed['c'][$id]);
1360:             $ed['d'][$id] = 1;
1361:             break;
1362:         }
1363:     }
1364: 
1365:     /**
1366:      * Return the list of elements that have changed since eltDiffStart()
1367:      * was last called.
1368:      *
1369:      * @return array  Returns false if no changes have occurred, or an array
1370:      *                with the following keys:
1371:      *   - a: (array) Elements that have been added.
1372:      *   - c: (array) Elements that have been changed.
1373:      *   - d: (array) Elements that have been deleted.
1374:      */
1375:     public function eltDiff()
1376:     {
1377:         if (is_null($this->_eltdiff) || !$this->changed) {
1378:             return false;
1379:         }
1380: 
1381:         $ret = array(
1382:             'a' => array_keys($this->_eltdiff['a']),
1383:             'c' => array_keys($this->_eltdiff['c']),
1384:             'd' => array_keys($this->_eltdiff['d'])
1385:         );
1386: 
1387:         $this->_eltdiff = null;
1388: 
1389:         return $ret;
1390:     }
1391: 
1392:     /**
1393:      * Updates the virtual folder list in the tree.
1394:      *
1395:      * @param array $vfolders  A list of IMP_Search_VFolder objects.
1396:      */
1397:     public function updateVFolders($vfolders)
1398:     {
1399:         /* Clear old entries. */
1400:         if (isset($this->_parent[self::VFOLDER_KEY])) {
1401:             foreach ($this->_parent[self::VFOLDER_KEY] as $key) {
1402:                 unset($this->_tree[$key]);
1403:             }
1404:             unset($this->_parent[self::VFOLDER_KEY]);
1405:             $this->changed = true;
1406:         }
1407: 
1408:         if (!$GLOBALS['injector']->getInstance('IMP_Factory_Imap')->create()->access(IMP_Imap::ACCESS_FOLDERS)) {
1409:             return;
1410:         }
1411: 
1412:         foreach ($vfolders as $val) {
1413:             if ($val->enabled) {
1414:                 $this->insert(self::VFOLDER_KEY . $this->_delimiter . $val);
1415:             }
1416:         }
1417:     }
1418: 
1419:     /**
1420:      * Returns whether this element is a virtual folder.
1421:      *
1422:      * @param mixed $in  A mailbox name or a tree element.
1423:      *
1424:      * @return integer  True if the element is a virtual folder.
1425:      */
1426:     public function isVFolder($in)
1427:     {
1428:         $elt = $this->getElement($in);
1429: 
1430:         return ($elt && ($elt['a'] & self::ELT_VFOLDER));
1431:     }
1432: 
1433:     /**
1434:      * Rename a current folder.
1435:      *
1436:      * @param string $old  The old mailbox name.
1437:      * @param string $new  The new mailbox name.
1438:      */
1439:     public function rename($old, $new)
1440:     {
1441:         $new_list = $polled = array();
1442: 
1443:         $this->setIteratorFilter(self::FLIST_NOCONTAINER | self::FLIST_UNSUB | self::FLIST_NOBASE | self::FLIST_ASIS, $old);
1444:         $old_list = array_merge(
1445:             array(IMP_Mailbox::get($old)),
1446:             iterator_to_array($this)
1447:         );
1448: 
1449:         foreach ($old_list as $val) {
1450:             $new_list[] = $new_name = substr_replace($val, $new, 0, strlen($old));
1451:             if ($val->polled) {
1452:                 $polled[] = $new_name;
1453:             }
1454:         }
1455: 
1456:         $this->insert($new_list);
1457:         $this->delete($old_list);
1458: 
1459:         $this->addPollList($polled);
1460:     }
1461: 
1462:     /**
1463:      * Sort a level in the tree.
1464:      *
1465:      * @param string $id  The parent folder whose children need to be sorted.
1466:      */
1467:     protected function _sortLevel($id)
1468:     {
1469:         if ($this->_needSort($this->_tree[$id])) {
1470:             $this->_sortList($this->_parent[$id], ($id === self::BASE_ELT));
1471:             $this->_setNeedSort($this->_tree[$id], false);
1472:             $this->changed = true;
1473:         }
1474:     }
1475: 
1476:     /**
1477:      * Determines the mailbox name to create given a parent and the new name.
1478:      *
1479:      * @param string $parent  The parent name (UTF7-IMAP).
1480:      * @param string $parent  The new mailbox name (UTF7-IMAP).
1481:      *
1482:      * @return IMP_Mailbox  The new mailbox.
1483:      * @throws IMP_Exception
1484:      */
1485:     public function createMailboxName($parent, $new)
1486:     {
1487:         $ns_info = empty($parent)
1488:             ? $GLOBALS['injector']->getInstance('IMP_Factory_Imap')->create()->defaultNamespace()
1489:             : $this->_getNamespace($parent);
1490: 
1491:         if (is_null($ns_info)) {
1492:             if ($this->isNamespace($this->_tree[$parent])) {
1493:                 $ns_info = $this->_getNamespace($new);
1494:                 if (in_array($ns_info['type'], array(Horde_Imap_Client::NS_OTHER, Horde_Imap_Client::NS_SHARED))) {
1495:                     return IMP_Mailbox::get($new);
1496:                 }
1497:             }
1498:             throw new IMP_Exception(_("Cannot directly create mailbox in this folder."));
1499:         }
1500: 
1501:         $mbox = $ns_info['name'];
1502:         if (!empty($parent)) {
1503:             $mbox .= substr_replace($parent, '', 0, strlen($ns_info['name']));
1504:             $mbox = rtrim($mbox, $ns_info['delimiter']) . $ns_info['delimiter'];
1505:         }
1506: 
1507:         return IMP_Mailbox::get($mbox . $new);
1508:     }
1509: 
1510:     /**
1511:      * Creates a Horde_Tree representation of the current tree (respecting
1512:      * the current iterator filter).
1513:      *
1514:      * @param string|Horde_Tree $name  Either the tree name, or a Horde_Tree
1515:      *                                 object to add nodes to.
1516:      * @param array $opts              Additional options:
1517:      *   - basename: (boolean) Use raw basename instead of abbreviated label?
1518:      *               DEFAULT: false
1519:      *   - checkbox: (boolean) Display checkboxes?
1520:      *               DEFAULT: false
1521:      *   - editvfolder: (boolean) Display vfolder edit links?
1522:      *                  DEFAULT: false
1523:      *   - open: (boolean) Force child mailboxes to this status.
1524:      *           DEFAULT: null
1525:      *   - parent: (string) The parent object of the current level.
1526:      *             DEFAULT: null (add to base level)
1527:      *   - poll_info: (boolean) Include poll information?
1528:      *                DEFAULT: false
1529:      *   - render_params: (array) List of params to pass to renderer if
1530:      *                    auto-creating.
1531:      *                    DEFAULT: 'alternate', 'lines', and 'lines_base'
1532:      *                             are passed in with true values.
1533:      *   - render_type: (string) The renderer name.
1534:      *                  DEFAULT: Javascript
1535:      *
1536:      * @return Horde_Tree  The tree object.
1537:      */
1538:     public function createTree($name, array $opts = array())
1539:     {
1540:         $opts = array_merge(array(
1541:             'parent' => null,
1542:             'render_params' => array(),
1543:             'render_type' => 'Javascript'
1544:         ), $opts);
1545: 
1546:         $this->unseen = 0;
1547: 
1548:         if ($name instanceof Horde_Tree_Base) {
1549:             $tree = $name;
1550:             $parent = $opts['parent'];
1551:         } else {
1552:             $tree = $GLOBALS['injector']->getInstance('Horde_Core_Factory_Tree')->create($name, $opts['render_type'], array_merge(array(
1553:                 'alternate' => true,
1554:                 'lines' => true,
1555:                 'lines_base' => true,
1556:                 'nosession' => true
1557:             ), $opts['render_params']));
1558:             $parent = null;
1559:         }
1560: 
1561:         $mailbox_page = (IMP::getViewMode() == 'mimp')
1562:             ? 'mailbox-mimp.php'
1563:             : 'mailbox.php';
1564: 
1565:         foreach ($this as $val) {
1566:             $after = '';
1567:             $params = array();
1568: 
1569:             switch ($opts['render_type']) {
1570:             case 'IMP_Tree_Flist':
1571:                 if ($val->vfolder_container) {
1572:                     continue 2;
1573:                 }
1574: 
1575:                 $is_open = true;
1576:                 $label = $params['orig_label'] = empty($opts['basename'])
1577:                     ? $val->abbrev_label
1578:                     : $val->basename;
1579:                 break;
1580: 
1581:             case 'IMP_Tree_Jquerymobile':
1582:                 $is_open = true;
1583:                 $label = $val->display_html;
1584:                 $icon = $val->icon;
1585:                 $params['icon'] = $icon->icon;
1586:                 $params['special'] = $val->special;
1587:                 $params['class'] = 'imp-folder';
1588:                 $params['urlattributes'] = array('mailbox' => $val->form_to);
1589:                 break;
1590: 
1591:             case 'IMP_Tree_Simplehtml':
1592:                 $is_open = $val->is_open;
1593:                 if ($tree->shouldToggle($val->form_to)) {
1594:                     if ($is_open) {
1595:                         $this->collapse($val);
1596:                     } else {
1597:                         $this->expand($val);
1598:                     }
1599:                     $is_open = !$is_open;
1600:                 }
1601:                 $label = htmlspecialchars(Horde_String::abbreviate($val->display, 30 - ($val->level * 2)));
1602:                 break;
1603: 
1604:             case 'Javascript':
1605:                 $is_open = $val->is_open;
1606:                 $label = empty($opts['basename'])
1607:                     ? htmlspecialchars($val->abbrev_label)
1608:                     : htmlspecialchars($val->basename);
1609:                 $icon = $val->icon;
1610:                 $params['icon'] = $icon->icon;
1611:                 $params['iconopen'] = $icon->iconopen;
1612:                 break;
1613:             }
1614: 
1615:             if (!empty($opts['poll_info']) && $val->polled) {
1616:                 $poll_info = $val->poll_info;
1617: 
1618:                 if ($opts['render_type'] == 'IMP_Tree_Jquerymobile') {
1619:                     if ($poll_info->unseen) {
1620:                         $after = $poll_info->unseen;
1621:                     }
1622:                 } else {
1623:                     if ($poll_info->unseen) {
1624:                         $this->unseen += $poll_info->unseen;
1625:                         $label = '<strong>' . $label . '</strong>';
1626:                     }
1627: 
1628:                     $after = '&nbsp;(' . $poll_info->unseen . '/' . $poll_info->msgs . ')';
1629:                 }
1630:             }
1631: 
1632:             if ($val->container) {
1633:                 $params['container'] = true;
1634:             } else {
1635:                 $params['url'] = $val->url($mailbox_page);
1636:                 if ($this->_showunsub && !$val->sub) {
1637:                     $params['class'] = 'folderunsub';
1638:                 }
1639:             }
1640: 
1641:             $checkbox = empty($opts['checkbox'])
1642:                 ? ''
1643:                 : '<input type="checkbox" class="checkbox" name="folder_list[]" value="' . $val->form_to . '"';
1644: 
1645:             if ($val->vfolder) {
1646:                 $checkbox .= ' disabled="disabled"';
1647: 
1648:                 if (!empty($opts['editvfolder']) && $val->container) {
1649:                     $after = '&nbsp[' .
1650:                         Horde::getServiceLink('prefs', 'imp')->add('group', 'searches')->link(array('title' => _("Edit Virtual Folder"))) . _("Edit") . '</a>'.
1651:                         ']';
1652:                 }
1653:             }
1654: 
1655:             $tree->addNode(
1656:                 $val->form_to,
1657:                 ($val->level) ? IMP_Mailbox::get($val->parent)->form_to : $parent,
1658:                 $label,
1659:                 $val->level,
1660:                 isset($opts['open']) ? $opts['open'] : $is_open,
1661:                 $params,
1662:                 $after,
1663:                 empty($opts['checkbox']) ? null : $checkbox . ' />'
1664:             );
1665:         }
1666: 
1667:         return $tree;
1668:     }
1669: 
1670:     /**
1671:      * Returns the internal IMAP Tree element for a given mailbox.
1672:      *
1673:      * @param mixed $elt  A mailbox name or a tree element.
1674:      *
1675:      * @return mixed  The element array, or null if not found.
1676:      */
1677:     public function getElement($in)
1678:     {
1679:         if (is_array($in)) {
1680:             return $in;
1681:         }
1682: 
1683:         $in = $this->_convertName($in);
1684: 
1685:         return isset($this->_tree[$in])
1686:             ? $this->_tree[$in]
1687:             : null;
1688:     }
1689: 
1690:     /* ArrayAccess methods. */
1691: 
1692:     public function offsetExists($offset)
1693:     {
1694:         return isset($this->_tree[$this->_convertName($offset)]);
1695:     }
1696: 
1697:     public function offsetGet($offset)
1698:     {
1699:         return $this->offsetExists($offset)
1700:             ? IMP_Mailbox::get($this->_convertName($offset))
1701:             : null;
1702:     }
1703: 
1704:     public function offsetSet($offset, $value)
1705:     {
1706:         $this->insert($offset);
1707:     }
1708: 
1709:     public function offsetUnset($offset)
1710:     {
1711:         $this->delete($offset);
1712:     }
1713: 
1714:     /* Countable methods. */
1715: 
1716:     /**
1717:      * Return the number of mailboxes on the server.
1718:      */
1719:     public function count()
1720:     {
1721:         $this->setIteratorFilter(self::FLIST_NOCONTAINER | self::FLIST_UNSUB);
1722:         return count(iterator_to_array($this));
1723:     }
1724: 
1725:     /* Iterator methods. */
1726: 
1727:     public function current()
1728:     {
1729:         return $this->valid()
1730:             ? $this[$this->_parent[$this->_currparent][$this->_currkey]]
1731:             : null;
1732:     }
1733: 
1734:     public function key()
1735:     {
1736:         return $this->valid()
1737:             ? $this->_parent[$this->_currparent][$this->_currkey]
1738:             : null;
1739:     }
1740: 
1741:     public function next()
1742:     {
1743:         $curr = $this->current();
1744:         if (is_null($curr)) {
1745:             return;
1746:         }
1747: 
1748:         $c = &$this->_cache['filter'];
1749: 
1750:         $old_showunsub = $this->_showunsub;
1751:         if ($c['mask'] & self::FLIST_UNSUB) {
1752:             $this->_showunsub = true;
1753:         }
1754: 
1755:         if ($this->_activeElt($curr, true)) {
1756:             /* Move into child element. */
1757:             $this->_currkey = 0;
1758:             $this->_currparent = $curr->value;
1759:             $this->_sortLevel($curr->value);
1760: 
1761:             $curr = $this->current();
1762:         } else {
1763:             /* Else, increment within the current subfolder. */
1764:             ++$this->_currkey;
1765: 
1766:             /* Descend tree until we reach a level that has more leaves we
1767:              * have not yet traversed. */
1768:             while ((($curr = $this->current()) === null) &&
1769:                    (!isset($c['samelevel']) ||
1770:                     ($c['samelevel'] != $this->_currparent)) &&
1771:                    ($parent = $this->_getParent($this->_currparent, true))) {
1772:                 list($this->_currparent, $this->_currkey) = $parent;
1773:             }
1774:         }
1775: 
1776:         if (is_null($curr)) {
1777:             /* If we don't have a current element by this point, we have run
1778:              * off the end of the tree. */
1779:             $this->_currkey = null;
1780:         } elseif (!$this->_activeElt($curr)) {
1781:             $this->next();
1782:         }
1783: 
1784:         $this->_showunsub = $old_showunsub;
1785:     }
1786: 
1787:     public function rewind()
1788:     {
1789:         $this->_currkey = 0;
1790:         $this->_currparent = self::BASE_ELT;
1791:         $this->_sortLevel($this->_currparent);
1792: 
1793:         $c = &$this->_cache['filter'];
1794: 
1795:         /* If showing unsubscribed, toggle subscribed flag to make sure we
1796:          * have subscribed mailbox information. */
1797:         if (!$this->_showunsub &&
1798:             ($c['mask'] & self::FLIST_UNSUB) &&
1799:             !($c['mask'] & self::FLIST_ASIS)) {
1800:             $this->showUnsubscribed(true);
1801:             $this->showUnsubscribed(false);
1802:         }
1803: 
1804:         // Search for base element.
1805:         if ($c['base']) {
1806:             if ($tmp = $this[$c['base']]) {
1807:                 if ($c['mask'] & self::FLIST_ANCESTORS) {
1808:                     $p = $tmp->value;
1809:                     $c['ancestors'] = array($p => null);
1810:                     while ($parent = $this->_getParent($p)) {
1811:                         $c['ancestors'][$parent[0]] = $parent[1];
1812:                         $p = $parent[0];
1813:                     }
1814:                 } elseif ($c['mask'] & self::FLIST_NOBASE) {
1815:                     $this->_currparent = $tmp->value;
1816:                     $this->_currkey = isset($this->_parent[$tmp->value])
1817:                         ? 0
1818:                         : null;
1819:                     $c['samelevel'] = $tmp->value;
1820: 
1821:                     /* Check to make sure current element is valid. */
1822:                     $curr = $this->current();
1823:                     if (is_null($curr)) {
1824:                         $this->_currkey = null;
1825:                     } elseif (!$this->_activeElt($curr)) {
1826:                         $this->next();
1827:                     }
1828:                 } else {
1829:                     $this->_currparent = strval($tmp->parent);
1830:                     $this->_currkey = array_search($tmp->value, $this->_parent[$this->_currparent]);
1831: 
1832:                     if ($c['mask'] & self::FLIST_SAMELEVEL) {
1833:                         $this->_currkey = 0;
1834:                         $c['samelevel'] = $this->_currparent;
1835:                     }
1836:                 }
1837:             } else {
1838:                 $this->_currkey = null;
1839:             }
1840:         }
1841:     }
1842: 
1843:     public function valid()
1844:     {
1845:         return (!is_null($this->_currkey) &&
1846:                 isset($this->_parent[$this->_currparent][$this->_currkey]));
1847:     }
1848: 
1849:     /* Helper functions for Iterator methods. */
1850: 
1851:     /**
1852:      * Set the current iterator filter and reset the internal pointer.
1853:      *
1854:      * @param integer $mask  A mask with the following possible elements:
1855:      * <ul>
1856:      *  <li>
1857:      *   IMP_Imap_Tree::FLIST_NOCONTAINER: Don't include container elements.
1858:      *  </li>
1859:      *  <li>
1860:      *   IMP_Imap_Tree::FLIST_UNSUB: Include unsubscribed elements.
1861:      *  </li>
1862:      *  <li>
1863:      *   IMP_Imap_Tree::FLIST_VFOLDER: Include Virtual Folders.
1864:      *  </li>
1865:      *  <li>
1866:      *   IMP_Imap_Tree::FLIST_NOCHILDREN: Don't include child elements.
1867:      *  </li>
1868:      *  <li>
1869:      *   IMP_Imap_Tree::FLIST_EXPANDED: Only include expanded folders.
1870:      *  </li>
1871:      *  <li>
1872:      *   IMP_Imap_Tree::FLIST_ASIS: Display the list as is currently cached
1873:      *                              in this object.
1874:      *  </li>
1875:      *  <li>Options that require $base to be set:
1876:      *   <ul>
1877:      *    <li>
1878:      *     IMP_Imap_Tree::FLIST_ANCESTORS: Include ancestors of $base.
1879:      *    </li>
1880:      *    <li>
1881:      *     IMP_Imap_Tree::FLIST_SAMELEVEL: Include all mailboxes at the same
1882:      *                                     level as $base.
1883:      *    </li>
1884:      *    <li>
1885:      *     IMP_Imap_Tree::FLIST_NOBASE: Don't include $base in the return.
1886:      *    </li>
1887:      *   </ul>
1888:      *  </li>
1889:      * </ul>
1890:      * @param string $base  Include all mailboxes below this element.
1891:      */
1892:     public function setIteratorFilter($mask = 0, $base = null)
1893:     {
1894:         $this->_cache['filter'] = array(
1895:             'base' => $base,
1896:             'mask' => $mask
1897:         );
1898:         reset($this);
1899:     }
1900: 
1901:     /**
1902:      * Is the given element an "active" element (i.e. an element that should
1903:      * be worked with given the current viewing parameters).
1904:      *
1905:      * @param IMP_Mailbox $elt      A mailbox element.
1906:      * @param boolean $child_check  Check children?
1907:      *
1908:      * @return boolean  True if it is an active element.
1909:      */
1910:     protected function _activeElt($elt, $child_check = false)
1911:     {
1912:         /* Skip invisible elements. */
1913:         if ($elt->invisible) {
1914:             return false;
1915:         }
1916: 
1917:         $c = &$this->_cache['filter'];
1918: 
1919:         /* Skip virtual folders unless told to display them. */
1920:         if ($elt->vfolder && !($c['mask'] & self::FLIST_VFOLDER)) {
1921:             return false;
1922:         }
1923: 
1924:         if ($child_check) {
1925:             /* Checks done when determining whether to proceed into child
1926:              * node. */
1927:             if (!isset($this->_parent[$elt->value])) {
1928:                return false;
1929:             }
1930: 
1931:             /* If element exists in ancestors list, it is valid. */
1932:             if (isset($c['ancestors']) &&
1933:                 isset($c['ancestors'][$this->_currparent]) &&
1934:                 ($c['ancestors'][$this->_currparent] == $this->_currkey)) {
1935:                 return true;
1936:             }
1937: 
1938:             /* If expanded is requested, we assume it overrides nochildren. */
1939:             if ($c['mask'] & self::FLIST_EXPANDED) {
1940:                 return $elt->is_open;
1941:             }
1942: 
1943:             if ($c['mask'] & self::FLIST_NOCHILDREN) {
1944:                 return false;
1945:             }
1946:         } else {
1947:             /* Checks done when determining whether to mark current element as
1948:              * valid. */
1949:             if ($elt->container) {
1950:                 if (($c['mask'] & self::FLIST_NOCONTAINER) ||
1951:                     !$elt->children) {
1952:                     return false;
1953:                 }
1954:             } elseif (!$this->_showunsub && !$elt->sub) {
1955:                 return false;
1956:             }
1957:         }
1958: 
1959:         return true;
1960:     }
1961: 
1962:     /**
1963:      * Return the parent/key for the current element.
1964:      *
1965:      * @param boolean $inc  Increment key?
1966:      *
1967:      * @return mixed  An array with two values: parent name and the key.
1968:      *                Returns null if no parent.
1969:      */
1970:     protected function _getParent($parent, $inc = false)
1971:     {
1972:         if ($parent == self::BASE_ELT) {
1973:             return null;
1974:         }
1975: 
1976:         $p = strval($this[$parent]->parent);
1977: 
1978:         return array(
1979:             $p,
1980:             array_search($parent, $this->_parent[$p], true) + ($inc ? 1 : 0)
1981:         );
1982:     }
1983: 
1984:     /* Serializable methods. */
1985: 
1986:     /**
1987:      */
1988:     public function serialize()
1989:     {
1990:         return serialize(array(
1991:             // Serialized data ID.
1992:             self::VERSION,
1993:             $this->_delimiter,
1994:             $this->_namespaces,
1995:             $this->_parent,
1996:             $this->_showunsub,
1997:             $this->_tree
1998:         ));
1999:     }
2000: 
2001:     /**
2002:      * @throws Exception
2003:      */
2004:     public function unserialize($data)
2005:     {
2006:         $data = @unserialize($data);
2007:         if (!is_array($data) ||
2008:             !isset($data[0]) ||
2009:             ($data[0] != self::VERSION)) {
2010:             throw new Exception('Cache version change');
2011:         }
2012: 
2013:         $this->_delimiter = $data[1];
2014:         $this->_namespaces = $data[2];
2015:         $this->_parent = $data[3];
2016:         $this->_showunsub = $data[4];
2017:         $this->_tree = $data[5];
2018:     }
2019: 
2020: }
2021: 
API documentation generated by ApiGen