PageRenderTime 197ms CodeModel.GetById 152ms app.highlight 34ms RepoModel.GetById 1ms app.codeStats 1ms

/horde-3.3.13/lib/Horde/IMAP/Tree.php

#
PHP | 2010 lines | 958 code | 213 blank | 839 comment | 197 complexity | 15ac26ce8f3621f6191b013e22003a16 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

   1<?php
   2/* Constants for mailboxElt attributes.
   3 * All versions of c-client (c-client/mail.h) define these constants:
   4 *   LATT_NOINFERIORS (long) 0x1 = 1
   5 *   LATT_NOSELECT (long) 0x2 = 2
   6 *   LATT_MARKED (long) 0x4 = 4
   7 *   LATT_UNMARKED (long) 0x8 = 8
   8 *
   9 * Newer versions of c-client (imap-2002 and greater) define these constants:
  10 *   LATT_REFERRAL (long) 0x10 = 16
  11 *   LATT_HASCHILDREN (long) 0x20 = 32
  12 *   LATT_HASNOCHILDREN (long) 0x40 = 64
  13 * ...but these constant names do not appear in PHP until PHP 4.3.5 and 5.0.
  14 *
  15 * Also, no need to define LATT_REFERRAL at the present time since we don't use
  16 * it anywhere.
  17 */
  18if (!defined('LATT_HASCHILDREN')) {
  19//    @define('LATT_REFERRAL', 16);
  20    @define('LATT_HASCHILDREN', 32);
  21    @define('LATT_HASNOCHILDREN', 64);
  22}
  23
  24/* Start at 128 for our local bitmasks to allow for the c-client LATT_*
  25   constants. */
  26define('IMAPTREE_ELT_HAS_CHILDREN', 128); // DEPRECATED
  27define('IMAPTREE_ELT_IS_OPEN', 256);
  28define('IMAPTREE_ELT_IS_SUBSCRIBED', 512);
  29define('IMAPTREE_ELT_IS_DISCOVERED', 1024);
  30define('IMAPTREE_ELT_IS_POLLED', 2048);
  31define('IMAPTREE_ELT_NEED_SORT', 4096);
  32// '8192' is used by IMP 4.x so do not use it here
  33// TODO: Renumber to 128 in Horde 4.0
  34define('IMAPTREE_ELT_NAMESPACE', 16384);
  35
  36/* The isOpen() expanded mode constants. */
  37define('IMAPTREE_OPEN_NONE', 0);
  38define('IMAPTREE_OPEN_ALL', 1);
  39define('IMAPTREE_OPEN_USER', 2);
  40
  41/* Which mode of IMAP access are we using. */
  42define('IMAPTREE_MODE_MAIL', 0);
  43define('IMAPTREE_MODE_NEWS', 1);
  44
  45/* The initalization mode to use when creating the tree. */
  46define('IMAPTREE_INIT_SUB', 1);
  47define('IMAPTREE_INIT_UNSUB', 2);
  48define('IMAPTREE_INIT_FETCHALL', 4);
  49
  50/* The manner to which to traverse the tree when calling next(). */
  51define('IMAPTREE_NEXT_SHOWCLOSED', 1);
  52define('IMAPTREE_NEXT_SHOWSUB', 2);
  53
  54/* The string used to indicate the base of the tree. */
  55define('IMAPTREE_BASE_ELT', null);
  56
  57/**
  58 * The IMAP_Tree class provides a tree view of the folders supported with
  59 * the PHP imap extension (IMAP/POP3/NNTP repositories).  It provides access
  60 * functions to iterate through this tree and query information about
  61 * individual mailboxes/folders.
  62 *
  63 * $Horde: framework/IMAP/IMAP/Tree.php,v 1.48.2.47 2009/01/06 15:23:11 jan Exp $
  64 *
  65 * Copyright 2000-2009 The Horde Project (http://www.horde.org/)
  66 *
  67 * See the enclosed file COPYING for license information (GPL). If you
  68 * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
  69 *
  70 * @author  Chuck Hagenbuch <chuck@horde.org>
  71 * @author  Jon Parise <jon@horde.org>
  72 * @author  Anil Madhavapeddy <avsm@horde.org>
  73 * @author  Michael Slusarz <slusarz@horde.org>
  74 * @since   Horde 3.0
  75 * @package Horde_IMAP
  76 */
  77class IMAP_Tree {
  78
  79    /**
  80     * Associative array containing the mailbox tree.
  81     *
  82     * @var array
  83     */
  84    var $_tree;
  85
  86    /**
  87     * Location of current element in the tree.
  88     *
  89     * @var string
  90     */
  91    var $_currparent = null;
  92
  93    /**
  94     * Location of current element in the tree.
  95     *
  96     * @var integer
  97     */
  98    var $_currkey = null;
  99
 100    /**
 101     * Location of current element in the tree.
 102     *
 103     * @var array
 104     */
 105    var $_currstack = array();
 106
 107    /**
 108     * Show unsubscribed mailboxes?
 109     *
 110     * @var boolean
 111     */
 112    var $_showunsub = false;
 113
 114    /**
 115     * Parent list.
 116     *
 117     * @var array
 118     */
 119    var $_parent = array();
 120
 121    /**
 122     * The cached list of mailboxes to poll.
 123     *
 124     * @var array
 125     */
 126    var $_poll = null;
 127
 128    /**
 129     * The cached list of expanded folders.
 130     *
 131     * @var array
 132     */
 133    var $_expanded = null;
 134
 135    /**
 136     * Cached list of subscribed mailboxes.
 137     *
 138     * @var array
 139     */
 140    var $_subscribed = null;
 141
 142    /**
 143     * Cached list of unsubscribed mailboxes.
 144     *
 145     * @var array
 146     */
 147    var $_unsubscribed = null;
 148
 149    /**
 150     * Init mode flag.
 151     *
 152     * @var integer
 153     */
 154    var $_initmode = 0;
 155
 156    /**
 157     * Tree changed flag.  Set when something in the tree has been altered.
 158     *
 159     * @var boolean
 160     */
 161    var $_changed = false;
 162
 163    /**
 164     * Have we shown unsubscribed folders previously?
 165     *
 166     * @var boolean
 167     */
 168    var $_unsubview = false;
 169
 170    /**
 171     * The IMAP_Sort object.
 172     *
 173     * @var IMAP_Sort
 174     */
 175    var $_imap_sort = null;
 176
 177    /**
 178     * Insert an element in the tree that doesn't appear on the IMAP server.
 179     * If set, IMAP_Tree:: will call _getNonIMAPElt() to obtain the element
 180     * to add to the tree.
 181     *
 182     * @var boolean
 183     */
 184    var $_nonimapelt = false;
 185
 186    /**
 187     * The name to use when storing the object in the session.
 188     *
 189     * @var string
 190     */
 191    var $_cachename;
 192
 193    /**
 194     * The application that generated this tree.
 195     * THIS SHOULD BE SET IN EVERY SUBCLASS CONSTRUCTOR.
 196     *
 197     * @var string
 198     */
 199    var $_app = null;
 200
 201    /**
 202     * The server string for the current server.
 203     * THIS SHOULD BE SET IN EVERY SUBCLASS CONSTRUCTOR.
 204     *
 205     * @var string
 206     */
 207    var $_server = '';
 208
 209    /**
 210     * Should we use 'mail' mode or 'news' mode?
 211     * THIS SHOULD BE SET IN EVERY SUBCLASS CONSTRUCTOR.
 212     *
 213     * @var integer
 214     */
 215    var $_mode = null;
 216
 217    /**
 218     * The list of namespaces to add to the tree.
 219     *
 220     * @var array
 221     */
 222    var $_namespaces;
 223
 224    /**
 225     * Does the IMAP server support the children extension?
 226     *
 227     * @var boolean
 228     */
 229    var $_childrensupport = null;
 230
 231    /**
 232     * Used to determine the list of element changes.
 233     *
 234     * @var array
 235     */
 236    var $_eltdiff = null;
 237
 238    /**
 239     * Attempts to return a reference to a concrete IMAP_Tree instance.
 240     *
 241     * If an IMAP_Tree object is currently stored in the local session,
 242     * recreate that object.  Else, if $create is true, will create a new
 243     * instance.  Ensures that only one IMAP_Tree instance is available
 244     * at any time.
 245     *
 246     * This method must be invoked as:<pre>
 247     *   $imap_tree = &IMAP_Tree::singleton($app, $classname, [$create[, $unsub]]);</pre>
 248     *
 249     * @param string $app        The current application name.
 250     * @param string $classname  The class name to use when instantiating a new
 251     *                           object.
 252     * @param boolean $create    Create a new IMAP_Tree if it doesn't exist in
 253     *                           the session?
 254     * @param integer $init      The initialization mode to use.
 255     * @param string $cachename  The name to use when storing in the session.
 256     *
 257     * @return IMAP_Tree  The IMAP_Tree object or null.
 258     */
 259    function &singleton($app, $classname, $create = false,
 260                        $init = IMAPTREE_INIT_SUB, $cachename = 'imaptree')
 261    {
 262        static $instance = array();
 263
 264        if (isset($instance[$app])) {
 265            return $instance[$app];
 266        }
 267
 268        if (!empty($_SESSION[$app][$cachename])) {
 269            require_once 'Horde/SessionObjects.php';
 270            $cacheSess = &Horde_SessionObjects::singleton();
 271            $instance[$app] = &$cacheSess->query($_SESSION[$app][$cachename]);
 272            register_shutdown_function(array(&$instance[$app], '_store'));
 273        } elseif ($create) {
 274            $instance[$app] = &new $classname($init, $cachename);
 275        }
 276
 277        return $instance[$app];
 278    }
 279
 280    /**
 281     * Constructor.
 282     *
 283     * @param integer $init      The initialization mode to use.
 284     * @param string $cachename  The name to use when storing in the session.
 285     */
 286    function IMAP_Tree($init = IMAPTREE_INIT_SUB, $cachename = 'imaptree')
 287    {
 288        register_shutdown_function(array(&$this, '_store'));
 289        $this->_cachename = $cachename;
 290        $this->init($init);
 291    }
 292
 293    /**
 294     * Store a serialized version of ourself in the current session.
 295     *
 296     * @access private
 297     */
 298    function _store()
 299    {
 300        /* We only need to restore the object if the tree has changed. */
 301        if (!empty($this->_changed)) {
 302            /* Don't store $_expanded and $_poll - these values are handled
 303             * by the subclasses.
 304             * Don't store $_imap_sort or $_eltdiff - this needs to be
 305             * regenerated for each request.
 306             * Don't store $_currkey, $_currparent, and $_currStack since the
 307             * user MUST call reset() before cycling through the tree.
 308             * Reset the $_changed flag. */
 309            $this->_currkey = $this->_currparent = $this->_eltdiff = $this->_expanded = $this->_imap_sort = $this->_poll = null;
 310            $this->_currStack = array();
 311            $this->_changed = false;
 312
 313            require_once 'Horde/SessionObjects.php';
 314            $cacheSess = &Horde_SessionObjects::singleton();
 315
 316            /* Reuse old session ID if possible. */
 317            if (isset($_SESSION[$this->_app][$this->_cachename])) {
 318                $cacheSess->overwrite($_SESSION[$this->_app][$this->_cachename], $this, false);
 319            } else {
 320                if (!isset($_SESSION[$this->_app])) {
 321                    $_SESSION[$this->_app] = array();
 322                }
 323                $_SESSION[$this->_app][$this->_cachename] = $cacheSess->storeOid($this, false);
 324            }
 325        }
 326    }
 327
 328    /**
 329     * Returns a list of folders/mailboxes matching a certain path.
 330     *
 331     * @access private
 332     *
 333     * @param string $path  The mailbox path.
 334     *
 335     * @return array  A list of mailbox_objects whose name matched $path.
 336     *                The server string has been removed from the name.
 337     */
 338    function _getList($path)
 339    {
 340        $unique = array();
 341
 342        if (!$this->_showunsub) {
 343            $this->_initSubscribed();
 344        }
 345        $newboxes = @imap_getmailboxes($this->_getStream(), $this->_server, $path);
 346        if (is_array($newboxes)) {
 347            foreach ($newboxes as $box) {
 348                if ($this->_showunsub ||
 349                    !isset($this->_subscribed[$box->name])) {
 350                    /* Strip off server string. */
 351                    $box = $this->_removeServerString($box);
 352                    if ($box->name && !isset($unique[$box->name])) {
 353                        $unique[$box->name] = $box;
 354                    }
 355                }
 356            }
 357        }
 358
 359        return $unique;
 360    }
 361
 362    /**
 363     * Make a single mailbox tree element.
 364     * An element consists of the following items (we use single letters here
 365     * to save in session storage space):
 366     *   'a'  --  Attributes
 367     *   'c'  --  Level count
 368     *   'l'  --  Label
 369     *   'p'  --  Parent node
 370     *   'v'  --  Value
 371     *
 372     * @access private
 373     *
 374     * @param object stdClass $ob  The object returned by imap_getmailboxes().
 375     *
 376     * @return array  See format above.
 377     */
 378    function _makeMailboxTreeElt($ob)
 379    {
 380        $elt['a'] = $ob->attributes;
 381        $elt['c'] = 0;
 382        $elt['p'] = IMAPTREE_BASE_ELT;
 383        $elt['v'] = $ob->name;
 384
 385        /* Set subscribed values. Make sure INBOX is always subscribed
 386         * to (if working with mail) so there is always at least 1
 387         * viewable element. */
 388        if ($elt['v'] == 'INBOX') {
 389            $this->_setSubscribed($elt, true);
 390        } elseif (!$this->isSubscribed($elt)) {
 391            $this->_initSubscribed();
 392            $this->_setSubscribed($elt, isset($this->_subscribed[$elt['v']]));
 393        }
 394
 395        /* Check for polled status. */
 396        if (!$this->isPolled($elt)) {
 397            $this->getPollList();
 398            $this->_setPolled($elt, isset($this->_poll[$elt['v']]));
 399        }
 400
 401        /* Check for open status. */
 402        $this->_setOpen($elt, $this->isOpen($elt));
 403
 404        /* Computed values. */
 405        $ns_info = $this->_getNamespace($elt['v']);
 406        $tmp = explode((is_null($ns_info)) ? $this->_delimiter : $ns_info['delimiter'], $elt['v']);
 407        $elt['c'] = count($tmp) - 1;
 408        $label = $elt['c'];
 409        if (!empty($elt['c']) &&
 410            !preg_match("/\{.*pop3.*\}/", $ob->fullServerPath)) {
 411            $elt['p'] = implode((is_null($ns_info)) ? $this->_delimiter : $ns_info['delimiter'], array_slice($tmp, 0, $elt['c']));
 412            /* Strip personal namespace. */
 413            if (!is_null($ns_info) &&
 414                !empty($ns_info['name']) &&
 415                ($ns_info['type'] == 'personal')) {
 416                $elt['c']--;
 417                if (strpos($elt['p'], $ns_info['delimiter']) === false) {
 418                    $elt['p'] = IMAPTREE_BASE_ELT;
 419                } elseif (strpos($elt['v'], $ns_info['name'] . 'INBOX' . $ns_info['delimiter']) === 0) {
 420                    $elt['p'] = 'INBOX';
 421                }
 422            }
 423        }
 424        $elt['l'] = String::convertCharset($tmp[$label], 'UTF7-IMAP');
 425
 426        return $elt;
 427    }
 428
 429    /**
 430     * Initalize the list at the top level of the hierarchy.
 431     *
 432     * @param integer $init  The initialization mode.
 433     */
 434    function init($init)
 435    {
 436        /* Reset all class variables to the defaults. */
 437        $this->_changed = true;
 438        $this->_currkey = null;
 439        $this->_currparent = null;
 440        $this->_currstack = array();
 441        $this->_tree = array();
 442        $this->_parent = array();
 443        $this->_showunsub = $this->_unsubview = ($init & IMAPTREE_INIT_UNSUB);
 444        $this->_subscribed = null;
 445        $this->_unsubscribed = null;
 446
 447        /* Set initialization mode. */
 448        $this->_initmode = $init;
 449
 450        /* Get the initial list of mailboxes from the subclass. */
 451        $boxes = $this->_init();
 452
 453        /* Create a placeholder element to the base of the tree list so we can
 454         * keep track of whether the base level needs to be sorted. */
 455        $this->_tree[IMAPTREE_BASE_ELT] = array('a' => IMAPTREE_ELT_IS_DISCOVERED);
 456
 457        /* Check to make sure all namespace elements are in the tree. */
 458        /* TODO: Remove namespaces availability check in Horde 4.0 */
 459        if ($this->_namespaces) {
 460            foreach ($this->_namespaces as $key => $val) {
 461                if ($val['type'] != 'personal') {
 462                    $name = substr($key, 0, -1 * strlen($val['delimiter']));
 463                    if (!isset($this->_tree[$val['name']])) {
 464                        $ob = &new stdClass;
 465                        $ob->delimiter = $val['delimiter'];
 466                        $ob->attributes = LATT_NOSELECT | IMAPTREE_ELT_NAMESPACE;
 467                        /* $ob->fullServerPath is not completely set here. */
 468                        $ob->fullServerPath = $ob->name = $name;
 469                        $elt = $this->_makeMailboxTreeElt($ob);
 470                        $this->_insertElt($elt);
 471                    }
 472                }
 473            }
 474        }
 475
 476        /* Create the list (INBOX and any other hierarchies). */
 477        $this->_addLevel($boxes);
 478
 479        /* End initialization mode. */
 480        $this->_initmode = 0;
 481    }
 482
 483    /**
 484     * Subclass specific initialization tasks.
 485     * THIS METHOD MUST BE DEFINED IN ALL SUBCLASSES.
 486     *
 487     * @abstract
 488     *
 489     * @access private
 490     *
 491     * @return array  The initial list of elements to add to the tree.
 492     */
 493    function _init()
 494    {
 495        return array();
 496    }
 497
 498    /**
 499     * Expand a mail folder.
 500     *
 501     * @param string $folder      The folder name to expand.
 502     * @param boolean $expandall  Expand all folders under this one?
 503     */
 504    function expand($folder, $expandall = false)
 505    {
 506        $folder = $this->_convertName($folder);
 507
 508        if (!isset($this->_tree[$folder])) {
 509            return;
 510        }
 511
 512        $this->_changed = true;
 513
 514        /* Merge in next level of information. */
 515        if ($this->hasChildren($this->_tree[$folder])) {
 516            if (!$this->isDiscovered($this->_tree[$folder])) {
 517                $info = $this->_childrenInfo($folder);
 518                if (!empty($info)) {
 519                    if ($this->_initmode) {
 520                        if (($this->_initmode & IMAPTREE_INIT_FETCHALL) ||
 521                            $this->isOpen($this->_tree[$folder])) {
 522                            $this->_addLevel($info);
 523                        }
 524                    } else {
 525                        $this->_addLevel($info);
 526                        $this->_setOpen($this->_tree[$folder], true);
 527                    }
 528                }
 529            } else {
 530                if (!($this->_initmode & IMAPTREE_INIT_FETCHALL)) {
 531                    $this->_setOpen($this->_tree[$folder], true);
 532                }
 533
 534                /* Expand all children beneath this one. */
 535                if ($expandall && !empty($this->_parent[$folder])) {
 536                    foreach ($this->_parent[$folder] as $val) {
 537                        $this->expand($this->_tree[$val]['v'], true);
 538                    }
 539                }
 540            }
 541        }
 542    }
 543
 544    /**
 545     * Collapse a mail folder.
 546     *
 547     * @param string $folder  The folder name to collapse.
 548     */
 549    function collapse($folder)
 550    {
 551        $folder = $this->_convertName($folder);
 552
 553        if (!isset($this->_tree[$folder])) {
 554            return;
 555        }
 556
 557        $this->_changed = true;
 558
 559        /* Set the folder attributes to not expanded. */
 560        $this->_setOpen($this->_tree[$folder], false);
 561    }
 562
 563    /**
 564     * Sets the internal array pointer to the next element, and returns the
 565     * next object.
 566     *
 567     * @param integer $mask  A mask with the following elements:
 568     * <pre>
 569     * IMAPTREE_NEXT_SHOWCLOSED - Don't ignore closed elements.
 570     * IMAPTREE_NEXT_SHOWSUB - Only show subscribed elements.
 571     * </pre>
 572     *
 573     * @return mixed  Returns the next element or false if the element doesn't
 574     *                exist.
 575     */
 576    function next($mask = 0)
 577    {
 578        if (is_null($this->_currkey) && is_null($this->_currparent)) {
 579            return false;
 580        }
 581
 582        $curr = $this->current();
 583
 584        $old_showunsub = $this->_showunsub;
 585        if ($mask & IMAPTREE_NEXT_SHOWSUB) {
 586            $this->_showunsub = false;
 587        }
 588
 589        if ($this->_activeElt($curr) &&
 590            (($mask & IMAPTREE_NEXT_SHOWCLOSED) || $this->isOpen($curr)) &&
 591            ($this->_currparent != $curr['v'])) {
 592            /* If the current element is open, and children exist, move into
 593             * it. */
 594            $this->_currstack[] = array('k' => $this->_currkey, 'p' => $this->_currparent);
 595            $this->_currkey = 0;
 596            $this->_currparent = $curr['v'];
 597            if ($this->_needSort($this->_tree[$curr['v']])) {
 598                $this->_sortList($this->_parent[$curr['v']], is_null($curr['v']));
 599                $this->_setNeedSort($this->_tree[$curr['v']], false);
 600                $this->_changed = true;
 601            }
 602        } else {
 603            /* Else, increment within the current subfolder. */
 604            $this->_currkey++;
 605        }
 606
 607        /* If the pointer doesn't point to an element, try to move back to
 608           the previous subfolder.  If there is no previous subfolder,
 609           return false. */
 610        $curr = $this->current();
 611        if (!$curr &&
 612            ($mask & IMAPTREE_NEXT_SHOWCLOSED) &&
 613            !$this->isDiscovered($this->_tree[$this->_currparent]) &&
 614            $this->hasChildren($this->_tree[$this->_currparent])) {
 615            $this->_addLevel($this->_childrenInfo($this->_tree[$this->_currparent]['v']));
 616            $curr = $this->current();
 617        }
 618
 619        $this->_showunsub = $old_showunsub;
 620
 621        if (!$curr) {
 622            if (empty($this->_currstack)) {
 623                $this->_currkey = null;
 624                $this->_currparent = null;
 625                return false;
 626            } else {
 627                do {
 628                    $old = array_pop($this->_currstack);
 629                    $this->_currkey = $old['k'] + 1;
 630                    $this->_currparent = $old['p'];
 631                } while ((($curr = $this->current()) == false) &&
 632                         count($this->_currstack));
 633            }
 634        }
 635
 636        if (!$this->_activeElt($curr)) {
 637            /* Skip this entry if:
 638             * 1. We are not showing all elements
 639             * 2. We are not subscribed to this element
 640             * 3. It is not a container -OR-, if it is a container, if there
 641             *    are no viewable elements underneath it.
 642             * 4. This is an empty namespace element. */
 643            return $this->next($mask);
 644        }
 645
 646        return $curr;
 647    }
 648
 649    /**
 650     * Set internal pointer to the head of the tree.
 651     * This MUST be called before you can traverse the tree with next().
 652     *
 653     * @return mixed  Returns the element at the head of the tree or false
 654     *                if the element doesn't exist.
 655     */
 656    function reset()
 657    {
 658        $this->_currkey = 0;
 659        $this->_currparent = IMAPTREE_BASE_ELT;
 660        $this->_currstack = array();
 661
 662        if ($this->_needSort($this->_tree[$this->_currparent])) {
 663            $this->_sortList($this->_parent[$this->_currparent], is_null($this->_currparent));
 664            $this->_setNeedSort($this->_tree[$this->_currparent], false);
 665            $this->_changed = true;
 666        }
 667
 668        /* When resetting in news mode, the first element may not be
 669         * viewable (unlike mail mode, where the first element is INBOX and
 670         * is always viewable).  Therefore, we need to search for the first
 671         * viewable item and that will be the base of the viewable tree. */
 672        if ($this->_mode == IMAPTREE_MODE_NEWS) {
 673            $curr = $this->current();
 674            if (!$this->_activeElt($curr)) {
 675                $this->next();
 676            }
 677        }
 678
 679        return $this->current();
 680    }
 681
 682    /**
 683     * Return the current tree element.
 684     *
 685     * @return array  The current tree element or false if there is no
 686     *                element.
 687     */
 688    function current()
 689    {
 690        if (!isset($this->_parent[$this->_currparent][$this->_currkey])) {
 691            return false;
 692        }
 693
 694        return $this->_addAliases($this->_tree[$this->_parent[$this->_currparent][$this->_currkey]]);
 695    }
 696
 697    /**
 698     * Determines if there are more elements in the current tree level.
 699     *
 700     * @return boolean  True if there are more elements, false if this is the
 701     *                  last element.
 702     */
 703    function peek()
 704    {
 705        for ($i = ($this->_currkey + 1); ; $i++) {
 706            if (!isset($this->_parent[$this->_currparent][$i])) {
 707                return false;
 708            }
 709            if ($this->_activeElt($this->_addAliases($this->_tree[$this->_parent[$this->_currparent][$i]]))) {
 710                return true;
 711            }
 712        }
 713    }
 714
 715    /**
 716     * Adds aliases to a tree element and returns the resulting array.
 717     *
 718     * @access protected
 719     *
 720     * @param array $elt  A tree element.
 721     *
 722     * @return array  A tree element with the aliases added.
 723     */
 724    function _addAliases($elt)
 725    {
 726        $elt['label'] = $elt['l'];
 727        $elt['level'] = $elt['c'];
 728        $elt['parent'] = $elt['p'];
 729        $elt['value'] = $elt['v'];
 730
 731        return $elt;
 732    }
 733
 734    /**
 735     * Returns the requested element.
 736     *
 737     * @param string $name  The name of the tree element.
 738     *
 739     * @return array  Returns the requested element or false if not found.
 740     */
 741    function get($name)
 742    {
 743        $name = $this->_convertName($name);
 744
 745        if (isset($this->_tree[$name])) {
 746            return $this->_addAliases($this->_tree[$name]);
 747        } else {
 748            return false;
 749        }
 750    }
 751
 752    /**
 753     * Insert a folder/mailbox into the tree.
 754     *
 755     * @param mixed $id  The name of the folder (or a list of folder names)
 756     *                   to add (must be present on the mail server).
 757     *
 758     * @return boolean  True on success, false on error.
 759     */
 760    function insert($id)
 761    {
 762        if ($this->_mode == IMAPTREE_MODE_NEWS) {
 763            return false;
 764        }
 765
 766        if (is_array($id)) {
 767            if (!$this->_nonimapelt) {
 768                /* We want to add from the BASE of the tree up for efficiency
 769                 * sake. */
 770                $this->_sortList($id);
 771            }
 772        } else {
 773            $id = array($id);
 774        }
 775
 776        $adds = array();
 777
 778        foreach ($id as $val) {
 779            $ns_info = $this->_getNamespace($val);
 780            if (is_null($ns_info) || $this->_nonimapelt) {
 781                $adds[] = $val;
 782            } else {
 783                /* Break apart the name via the delimiter and go step by step
 784                 * through the name to make sure all subfolders exist in the
 785                 * tree. */
 786                $parts = explode($ns_info['delimiter'], $val);
 787                $parts[0] = $this->_convertName($parts[0]);
 788                for ($i = 0; $i < count($parts); $i++) {
 789                    $adds[] = implode($ns_info['delimiter'], array_slice($parts, 0, $i + 1));
 790                }
 791            }
 792        }
 793
 794        foreach (array_unique($adds) as $id) {
 795            if (isset($this->_tree[$id])) {
 796                continue;
 797            }
 798
 799            $this->_changed = true;
 800
 801            if ($this->_nonimapelt) {
 802                $elt = $this->_getNonIMAPElt($id);
 803            } else {
 804                $ob = $this->_getList($id);
 805                $elt = $this->_makeMailboxTreeElt(reset($ob));
 806                if (!$this->isSubscribed($elt)) {
 807                    $tmp = @imap_lsub($this->_getStream(), $this->_server, $elt['v']);
 808                    if (!empty($tmp)) {
 809                        $this->_setSubscribed($elt, true);
 810                    }
 811                }
 812            }
 813
 814            if ($this->_insertElt($elt)) {
 815                /* We know that the parent folder has children. */
 816                if (isset($this->_tree[$elt['p']])) {
 817                    $this->_setChildren($this->_tree[$elt['p']], true);
 818                }
 819
 820                /* Make sure we are sorted correctly. */
 821                if (count($this->_parent[$elt['p']]) > 1) {
 822                    $this->_setNeedSort($this->_tree[$elt['p']], true);
 823                }
 824
 825                /* Add to subscribed/unsubscribed list. */
 826                if ($this->isSubscribed($elt) &&
 827                    !is_null($this->_subscribed)) {
 828                    $this->_subscribed[$elt['v']] = 1;
 829                } elseif (!$this->isSubscribed($elt) &&
 830                          !is_null($this->_unsubscribed)) {
 831                    $this->_unsubscribed[$elt['v']] = 1;
 832                }
 833            }
 834        }
 835
 836        return true;
 837    }
 838
 839    /**
 840     * Insert an element into the tree.
 841     *
 842     * @access private
 843     *
 844     * @param array $elt  The element to insert. The key in the tree is the
 845     *                    'v' (value) element of the element.
 846     *
 847     * @return boolean  True if added to the tree.
 848     */
 849    function _insertElt($elt)
 850    {
 851        /* Don't include the parent directories that UW passes back in
 852           %/% lists. */
 853        if (strlen($elt['l']) &&
 854            (!isset($this->_tree[$elt['v']]) ||
 855             $this->isContainer($this->_tree[$elt['v']])) &&
 856            /* TODO: Remove dotfiles filter in next major release. */
 857            ($this->_dotfiles || ($elt['l'][0] != '.'))) {
 858            /* Set the parent array to the value in $elt['p']. */
 859            if (empty($this->_parent[$elt['p']])) {
 860                $this->_parent[$elt['p']] = array();
 861            }
 862            $this->_parent[$elt['p']][] = $elt['v'];
 863            $this->_tree[$elt['v']] = $elt;
 864            return true;
 865        } else {
 866            return false;
 867        }
 868    }
 869
 870    /**
 871     * Delete an element from the tree.
 872     *
 873     * @param mixed $id  The element name or an array of element names.
 874     *
 875     * @return boolean  Return true on success, false on error.
 876     */
 877    function delete($id)
 878    {
 879        if (is_array($id)) {
 880            /* We want to delete from the TOP of the tree down to ensure that
 881             * parents have an accurate view of what children are left. */
 882            $this->_sortList($id);
 883            $id = array_reverse($id);
 884
 885            $success = true;
 886            foreach ($id as $val) {
 887                $currsuccess = $this->delete($val);
 888                if (!$currsuccess) {
 889                    $success = false;
 890                }
 891            }
 892            return $success;
 893        } else {
 894            $id = $this->_convertName($id, true);
 895        }
 896
 897        $ns_info = $this->_getNamespace($id);
 898
 899        if (($id == 'INBOX') ||
 900            !isset($this->_tree[$id]) ||
 901            ($id == $ns_info['name'])) {
 902            return false;
 903        }
 904
 905        $this->_changed = true;
 906
 907        $elt = &$this->_tree[$id];
 908
 909        /* Do not delete from tree if there are child elements - instead,
 910         * convert to a container element. */
 911        if ($this->hasChildren($elt)) {
 912            $this->_setContainer($elt, true);
 913            return true;
 914        }
 915
 916        $parent = $elt['p'];
 917
 918        /* Delete the tree entries. */
 919        unset($this->_tree[$id]);
 920        unset($this->_subscribed[$id]);
 921        unset($this->_unsubscribed[$id]);
 922
 923        /* Delete the entry from the parent tree. */
 924        $key = array_search($id, $this->_parent[$parent]);
 925        unset($this->_parent[$parent][$key]);
 926
 927        if (empty($this->_parent[$parent])) {
 928            /* This folder is now completely empty (no children).  If the
 929             * folder is a container only, we should delete the folder from
 930             * the tree. */
 931            unset($this->_parent[$parent]);
 932            if (isset($this->_tree[$parent])) {
 933                $this->_setChildren($this->_tree[$parent], null);
 934                if ($this->isContainer($this->_tree[$parent]) &&
 935                    !$this->isNamespace($this->_tree[$parent])) {
 936                    $this->delete($parent);
 937                } elseif (!$this->hasChildren($this->_tree[$parent])) {
 938                    $this->_removeExpandedList($parent);
 939                    $this->_setChildren($this->_tree[$parent], false);
 940                    $this->_setOpen($this->_tree[$parent], false);
 941                }
 942            }
 943        } else {
 944            /* Rebuild the parent tree. */
 945            $this->_parent[$parent] = array_values($this->_parent[$parent]);
 946        }
 947
 948        /* Remove the mailbox from the expanded folders list. */
 949        $this->_removeExpandedList($id);
 950
 951        /* Remove the mailbox from the nav_poll list. */
 952        $this->removePollList($id);
 953
 954        return true;
 955    }
 956
 957    /**
 958     * Subscribe an element to the tree.
 959     *
 960     * @param mixed $id  The element name or an array of element names.
 961     */
 962    function subscribe($id)
 963    {
 964        if (!is_array($id)) {
 965            $id = array($id);
 966        }
 967
 968        foreach ($id as $val) {
 969            $val = $this->_convertName($val);
 970            if (isset($this->_tree[$val])) {
 971                $this->_changed = true;
 972                $this->_setSubscribed($this->_tree[$val], true);
 973                $this->_setContainer($this->_tree[$val], false);
 974                if (!is_null($this->_subscribed)) {
 975                    $this->_subscribed[$val] = 1;
 976                }
 977                unset($this->_unsubscribed[$val]);
 978            } else {
 979                $this->insert($val);
 980            }
 981        }
 982    }
 983
 984    /**
 985     * Unsubscribe an element from the tree.
 986     *
 987     * @param mixed $id  The element name or an array of element names.
 988     */
 989    function unsubscribe($id)
 990    {
 991        if (!is_array($id)) {
 992            $id = array($id);
 993        } else {
 994            /* We want to delete from the TOP of the tree down to ensure that
 995             * parents have an accurate view of what children are left. */
 996            $this->_sortList($id);
 997            $id = array_reverse($id);
 998        }
 999
1000        foreach ($id as $val) {
1001            $val = $this->_convertName($val);
1002
1003            /* INBOX can never be unsubscribed to (if in mail mode). */
1004            if (isset($this->_tree[$val]) && ($val != 'INBOX')) {
1005                $this->_changed = true;
1006                $this->_unsubview = true;
1007
1008                $elt = &$this->_tree[$val];
1009
1010                /* Do not delete from tree if there are child elements -
1011                 * instead, convert to a container element. */
1012                if (!$this->_showunsub && $this->hasChildren($elt)) {
1013                    $this->_setContainer($elt, true);
1014                }
1015
1016                /* Set as unsubscribed, add to unsubscribed list, and remove
1017                 * from subscribed list. */
1018                $this->_setSubscribed($elt, false);
1019                if (!is_null($this->_unsubscribed)) {
1020                    $this->_unsubscribed[$val] = 1;
1021                }
1022                unset($this->_subscribed[$val]);
1023            }
1024        }
1025    }
1026
1027    /**
1028     * Add another level of hierarchy to the tree.
1029     *
1030     * @access private
1031     *
1032     * @param array $list  A list of stdClass objects in the format returned
1033     *                     from imap_getmailboxes().
1034     */
1035    function _addLevel($list)
1036    {
1037        $expandall = ($this->_initmode & IMAPTREE_INIT_FETCHALL);
1038
1039        foreach ($list as $val) {
1040            $elt = $this->_makeMailboxTreeElt($val);
1041
1042            $parent = $elt['p'];
1043            $this->_insertElt($elt);
1044            $this->_changed = true;
1045
1046            if ($expandall || $this->isOpen($elt)) {
1047                $this->expand($elt['v']);
1048            }
1049        }
1050
1051        /* Sort the list. */
1052        if (!empty($list) &&
1053            !empty($this->_parent[$parent])) {
1054            $this->_setDiscovered($this->_tree[$parent], true);
1055            if (count($this->_parent[$parent]) > 1) {
1056                $this->_setNeedSort($this->_tree[$parent], true);
1057            }
1058        }
1059    }
1060
1061    /**
1062     * Set an attribute for an element.
1063     *
1064     * @access private
1065     *
1066     * @param array &$elt     The tree element.
1067     * @param integer $const  The constant to set/remove from the bitmask.
1068     * @param boolean $bool   Should the attribute be set?
1069     */
1070    function _setAttribute(&$elt, $const, $bool)
1071    {
1072        if ($bool) {
1073            $elt['a'] |= $const;
1074        } else {
1075            $elt['a'] &= ~$const;
1076        }
1077    }
1078
1079    /**
1080     * Does the element have any active children?
1081     *
1082     * @param array $elt  A tree element.
1083     *
1084     * @return boolean  True if the element has active children.
1085     */
1086    function hasChildren($elt)
1087    {
1088        static $hasChildrenCache = array();
1089
1090        $is_ns = $this->isNamespace($elt);
1091
1092        /* Don't do the following if we are dealing with a namespace
1093         * container. */
1094        if (!$is_ns) {
1095            /* Not all IMAP servers support the HASCHILDREN flag (like UW!) so
1096             * we need to skip this check if the IMAP server doesn't set
1097             * either HASCHILDREN or HASNOCHILDREN. */
1098            if (!empty($this->_childrensupport) ||
1099                (is_null($this->_childrensupport) &&
1100                 ($elt['a'] & LATT_HASCHILDREN) ||
1101                 ($elt['a'] & LATT_HASNOCHILDREN))) {
1102                $ret = ($elt['a'] & LATT_HASCHILDREN);
1103
1104                /* CHECK: If we are viewing all folders, and there is a folder
1105                 * listed as expanded but it does not contain any children,
1106                 * then we should remove it from the expanded list since it
1107                 * doesn't exist anymore. */
1108                if ($this->_showunsub && !$ret) {
1109                    $this->_initExpandedList();
1110                    if (!empty($this->_expanded[$elt['v']])) {
1111                        $this->_removeExpandedList($elt['v']);
1112                        $this->_setOpen($this->_tree[$elt['v']], false);
1113                    }
1114                }
1115
1116                if (!$ret) {
1117                    return false;
1118                }
1119
1120                /* If we are viewing all elements (subscribed and unsubscribed)
1121                 * and we reach this point we know that there must be viewable
1122                 * children so return true. */
1123                if ($this->_showunsub) {
1124                    return true;
1125                }
1126            }
1127        }
1128
1129        /* Cache results from below since, most likely if we get this far,
1130         * this code will be accessed several times in the current request. */
1131        if (isset($hasChildrenCache[$elt['v']])) {
1132            return $hasChildrenCache[$elt['v']];
1133        }
1134
1135        /* If we reach this point, then we are either in subscribe-only mode
1136         * or we are dealing with a namespace container. Check for the
1137         * existence of any subscribed mailboxes below the current node. */
1138        $this->_initSubscribed();
1139        $folder_list = $this->_subscribed;
1140        if ($this->_showunsub) {
1141            $this->_initUnsubscribed();
1142            $folder_list += $this->_unsubscribed;
1143        }
1144        $ns_info = $this->_getNamespace($elt['v']);
1145        if (!is_null($ns_info)) {
1146            $search_str = $elt['v'] . $ns_info['delimiter'];
1147            if (($elt['v'] == 'INBOX') &&
1148                ($ns_info['name'] == ('INBOX' . $ns_info['delimiter']))) {
1149                $search_str .= $ns_info['name'];
1150            }
1151            foreach (array_keys($folder_list) as $val) {
1152                if (strpos($val, $search_str) === 0) {
1153                    $this->_hasChildrenCache[$elt['v']] = true;
1154                    return true;
1155                }
1156            }
1157        }
1158
1159        /* Do one final check if this is a namespace container - if we get
1160         * this far, and are viewing all folders, then we know we have no
1161         * children so make sure the element is not set to expanded/open. */
1162        if ($is_ns && $this->_showunsub) {
1163            $this->_initExpandedList();
1164            if (!empty($this->_expanded[$elt['v']])) {
1165                $this->_removeExpandedList($elt['v']);
1166                $this->_setOpen($this->_tree[$elt['v']], false);
1167            }
1168        }
1169
1170        $hasChildrenCache[$elt['v']] = false;
1171        return false;
1172    }
1173
1174    /**
1175     * Set the children attribute for an element.
1176     *
1177     * @access private
1178     *
1179     * @param array &$elt  A tree element.
1180     * @param mixed $bool  The setting. If null, clears the flag.
1181     */
1182    function _setChildren(&$elt, $bool)
1183    {
1184        if (is_null($bool)) {
1185            $this->_setAttribute($elt, LATT_HASCHILDREN, false);
1186            $this->_setAttribute($elt, LATT_HASNOCHILDREN, false);
1187        } else {
1188            $this->_setAttribute($elt, LATT_HASCHILDREN, $bool);
1189            $this->_setAttribute($elt, LATT_HASNOCHILDREN, !$bool);
1190        }
1191    }
1192
1193    /**
1194     * Has the tree element been discovered?
1195     *
1196     * @param array $elt  A tree element.
1197     *
1198     * @return integer  Non-zero if the element has been discovered.
1199     */
1200    function isDiscovered($elt)
1201    {
1202        return $elt['a'] & IMAPTREE_ELT_IS_DISCOVERED;
1203    }
1204
1205    /**
1206     * Set the discovered attribute for an element.
1207     *
1208     * @access private
1209     *
1210     * @param array &$elt    A tree element.
1211     * @param boolean $bool  The setting.
1212     */
1213    function _setDiscovered(&$elt, $bool)
1214    {
1215        $this->_setAttribute($elt, IMAPTREE_ELT_IS_DISCOVERED, $bool);
1216    }
1217
1218    /**
1219     * Is the tree element open?
1220     *
1221     * @param array $elt  A tree element.
1222     *
1223     * @return integer  True if the element is open.
1224     */
1225    function isOpen($elt)
1226    {
1227        if (!$this->_initmode) {
1228            return (($elt['a'] & IMAPTREE_ELT_IS_OPEN) && $this->hasChildren($elt));
1229        } else {
1230            switch ($this->_getInitExpandedMode()) {
1231            case IMAPTREE_OPEN_NONE:
1232                return false;
1233                break;
1234
1235            case IMAPTREE_OPEN_ALL:
1236                return true;
1237                break;
1238
1239            case IMAPTREE_OPEN_USER:
1240                $this->_initExpandedList();
1241                return !empty($this->_expanded[$elt['v']]);
1242                break;
1243            }
1244        }
1245    }
1246
1247    /**
1248     * Set the open attribute for an element.
1249     *
1250     * @access private
1251     *
1252     * @param array &$elt    A tree element.
1253     * @param boolean $bool  The setting.
1254     */
1255    function _setOpen(&$elt, $bool)
1256    {
1257        $this->_setAttribute($elt, IMAPTREE_ELT_IS_OPEN, $bool);
1258        if (!$this->_initmode) {
1259            $this->_initExpandedList();
1260            if ($bool) {
1261                $this->_addExpandedList($elt['v']);
1262            } else {
1263                $this->_removeExpandedList($elt['v']);
1264            }
1265        }
1266    }
1267
1268    /**
1269     * Is this element a container only, not a mailbox (meaning you can
1270     * not open it)?
1271     *
1272     * @param array $elt  A tree element.
1273     *
1274     * @return integer  True if the element is a container.
1275     */
1276    function isContainer($elt)
1277    {
1278        return (($elt['a'] & LATT_NOSELECT) ||
1279                (!$this->_showunsub &&
1280                 !$this->isSubscribed($elt) &&
1281                 $this->hasChildren($elt)));
1282    }
1283
1284    /**
1285     * Set the element as a container?
1286     *
1287     * @access private
1288     *
1289     * @param array &$elt    A tree element.
1290     * @param boolean $bool  Is the element a container?
1291     */
1292    function _setContainer(&$elt, $bool)
1293    {
1294        $this->_setAttribute($elt, LATT_NOSELECT, $bool);
1295    }
1296
1297    /**
1298     * Is the user subscribed to this element?
1299     *
1300     * @param array $elt  A tree element.
1301     *
1302     * @return integer  True if the user is subscribed to the element.
1303     */
1304    function isSubscribed($elt)
1305    {
1306        return $elt['a'] & IMAPTREE_ELT_IS_SUBSCRIBED;
1307    }
1308
1309    /**
1310     * Set the subscription status for an element.
1311     *
1312     * @access private
1313     *
1314     * @param array &$elt    A tree element.
1315     * @param boolean $bool  Is the element subscribed to?
1316     */
1317    function _setSubscribed(&$elt, $bool)
1318    {
1319        $this->_setAttribute($elt, IMAPTREE_ELT_IS_SUBSCRIBED, $bool);
1320    }
1321
1322    /**
1323     * Is the element a namespace container?
1324     *
1325     * @param array $elt  A tree element.
1326     *
1327     * @return integer  True if the element is a namespace container?
1328     */
1329    function isNamespace($elt)
1330    {
1331        return $elt['a'] & IMAPTREE_ELT_NAMESPACE;
1332    }
1333
1334    /**
1335     * Remove the server string from the 'name' parameter.
1336     *
1337     * @access private
1338     *
1339     * @param object stdClass $ob  An object returned from
1340     *                             imap_getmailboxes().
1341     *
1342     * @return stdClass  The object returned with the server string stripped
1343     *                   from the 'name' parameter.
1344     */
1345    function _removeServerString($ob)
1346    {
1347        $ob->fullServerPath = $ob->name;
1348        $ob->name = $this->_convertName(substr($ob->name, strpos($ob->name, '}') + 1));
1349        return $ob;
1350    }
1351
1352    /**
1353     * Initialize the expanded folder list.
1354     * THIS METHOD SHOULD BE DEFINED IN ALL SUBCLASSES.
1355     *
1356     * @abstract
1357     *
1358     * @access private
1359     */
1360    function _initExpandedList()
1361    {
1362        $this->_expanded = array();
1363    }
1364
1365    /**
1366     * Add an element to the expanded list.
1367     * THIS METHOD SHOULD BE DEFINED IN ALL SUBCLASSES.
1368     *
1369     * @abstract
1370     *
1371     * @access private
1372     *
1373     * @param string $id  The element name to remove.
1374     */
1375    function _addExpandedList($id)
1376    {
1377    }
1378
1379    /**
1380     * Remove an element from the expanded list.
1381     * THIS METHOD SHOULD BE DEFINED IN ALL SUBCLASSES.
1382     *
1383     * @abstract
1384     *
1385     * @access private
1386     *
1387     * @param string $id  The element name to remove.
1388     */
1389    function _removeExpandedList($id)
1390    {
1391    }
1392
1393    /**
1394     * Initialize/get the list of elements to poll.
1395     * THIS METHOD SHOULD BE DEFINED IN ALL SUBCLASSES.
1396     *
1397     * @abstract
1398     *
1399     * @return array  The list of elements to poll (name in key field).
1400     */
1401    function getPollList()
1402    {
1403        $this->_poll = array();
1404        return $this->_poll;
1405    }
1406
1407    /**
1408     * Add element to the poll list.
1409     * THIS METHOD SHOULD BE DEFINED IN ALL SUBCLASSES.
1410     *
1411     * @abstract
1412     *
1413     * @param mixed $id  The element name or a list of element names to add.
1414     */
1415    function addPollList($id)
1416    {
1417    }
1418
1419    /**
1420     * Remove element from the poll list.
1421     * THIS METHOD SHOULD BE DEFINED IN ALL SUBCLASSES.
1422     *
1423     * @abstract
1424     *
1425     * @param string $id  The folder/mailbox or a list of folders/mailboxes
1426     *                    to remove.
1427     */
1428    function removePollList($id)
1429    {
1430    }
1431
1432    /**
1433     * Does the user want to poll this mailbox for new/unseen messages?
1434     *
1435     * @param array $elt  A tree element.
1436     *
1437     * @return integer  True if the user wants to poll the element.
1438     */
1439    function isPolled($elt)
1440    {
1441        return $elt['a'] & IMAPTREE_ELT_IS_POLLED;
1442    }
1443
1444    /**
1445     * Set the polled attribute for an element.
1446     *
1447     * @access private
1448     *
1449     * @param array &$elt    A tree element.
1450     * @param boolean $bool  The setting.
1451     */
1452    function _setPolled(&$elt, $bool)
1453    {
1454        $this->_setAttribute($elt, IMAPTREE_ELT_IS_POLLED, $bool);
1455    }
1456
1457    /**
1458     * Flag the element as needing its children to be sorted.
1459     *
1460     * @access private
1461     *
1462     * @param array &$elt    A tree element.
1463     * @param boolean $bool  The setting.
1464     */
1465    function _setNeedSort(&$elt, $bool)
1466    {
1467        $this->_setAttribute($elt, IMAPTREE_ELT_NEED_SORT, $bool);
1468    }
1469
1470    /**
1471     * Does this element's children need sorting?
1472     *
1473     * @param array $elt  A tree element.
1474     *
1475     * @return integer  True if the children need to be sorted.
1476     */
1477    function _needSort($elt)
1478    {
1479        return $elt['a'] & IMAPTREE_ELT_NEED_SORT;
1480    }
1481
1482    /**
1483     * Initialize the list of subscribed mailboxes.
1484     *
1485     * @access private
1486     */
1487    function _initSubscribed()
1488    {
1489        if (is_null($this->_subscribed)) {
1490            $this->_changed = true;
1491            $this->_subscribed = array();
1492            $sublist = array();
1493
1494            /* INBOX is always subscribed to if we are in mail mode. */
1495            if ($this->_mode == IMAPTREE_MODE_MAIL) {
1496                $this->_subscribed['INBOX'] = 1;
1497            }
1498
1499            /* TODO: Remove namespaces availability check in Horde 4.0 */
1500            if ($this->_namespaces) {
1501                foreach ($this->_namespaces as $val) {
1502                    $tmp = @imap_lsub($this->_getStream(), $this->_server, $val['name'] . '*');
1503                    if (!empty($tmp)) {
1504                        $sublist = array_merge($sublist, $tmp);
1505                    }
1506                }
1507            } else {
1508                $sublist = @imap_lsub($this->_getStream(), $this->_server, $this->_prefix . '*');
1509            }
1510
1511            if (!empty($sublist)) {
1512                foreach ($sublist as $val) {
1513                    $this->_subscribed[substr($val, strpos($val, '}') + 1)] = 1;
1514                }
1515            }
1516        }
1517    }
1518
1519    /**
1520     * Initialize the list of unsubscribed mailboxes.
1521     *
1522     * @access private
1523     */
1524    function _initUnsubscribed()
1525    {
1526        if (is_null($this->_unsubscribed)) {
1527            $this->_changed = true;
1528            $this->_initSubscribed();
1529            $this->_unsubscribed = array();
1530            $all_list = array();
1531
1532            /* Get list of all mailboxes. */
1533            /* TODO: Remove namespaces availability check in Horde 4.0 */
1534            if ($this->_namespaces) {
1535                foreach ($this->_namespaces as $val) {
1536                    $tmp = @imap_list($this->_getStream(), $this->_server, $val['name'] . '*');
1537                    if (!empty($tmp)) {
1538                        $all_list = array_merge($all_list, $tmp);
1539                    }
1540                }
1541            } else {
1542                $all_list = @imap_list($this->_getStream(), $this->_server, $this->_prefix . '*');
1543            }
1544
1545            if (!empty($all_list)) {
1546                /* Find all mailboxes that aren't in the subscribed list. */
1547                foreach ($all_list as $val) {
1548                    $val = substr($val, strpos($val, '}') + 1);
1549                    if (!isset($this->_subscribed[$val])) {
1550                        $this->_unsubscribed[$val] = 1;
1551                    }
1552                }
1553            }
1554        }
1555    }
1556
1557    /**
1558     * Should we expand all elements?
1559     */
1560    function expandAll()
1561    {
1562        foreach ($this->_parent[null] as $val) {
1563            $this->expand($val, true);
1564        }
1565    }
1566
1567    /**
1568     * Should we collapse all elements?
1569     */
1570    function collapseAll()
1571    {
1572        foreach ($this->_tree as $key => $val) {
1573            if ($key != IMAPTREE_BASE_ELT) {
1574                $this->collapse($val['v']);
1575            }
1576        }
1577
1578        /* Clear all entries from the expanded list. */
1579        $this->_initExpandedList();
1580        foreach ($this->_expanded as $key => $val) {
1581            $this->_removeExpandedList($key);
1582        }
1583
1584    }
1585
1586    /**
1587     * Return the list of mailboxes in the next level.
1588     *
1589     * @access private
1590     *
1591     * @param string $id  The current mailbox.
1592     *
1593     * @return array  A list of mailbox objects or the empty list.
1594     *                See _getList() for format.
1595     */
1596    function _childrenInfo($id)
1597    {
1598        $info = array();
1599        $ns_info = $this->_getNamespace($id);
1600
1601        if (is_null($ns_info)) {
1602            return $info;
1603        }
1604
1605        if (($id == 'INBOX') &&
1606            ($ns_info['name'] == ('INBOX' . $ns_info['delimiter']))) {
1607            $search = $id . $ns_info['delimiter'] . $ns_info['name'];
1608        } else {
1609            $search = $id . $ns_info['delimiter'];
1610        }
1611
1612        $info = $this->_getList($search . '%');
1613
1614        if (isset($this->_tree[$id])) {
1615            $this->_setChildren($this->_tree[$id], !empty($info));
1616        }
1617
1618        return $info;
1619    }
1620
1621    /**
1622     * Switch subscribed/unsubscribed viewing.
1623     *
1624     * @param boolean $unsub  Show unsubscribed elements?
1625     */
1626    function showUnsubscribed($unsub)
1627    {
1628        if ($unsub === $this->_showunsub) {
1629            return;
1630        }
1631
1632        $this->_showunsub = $unsub;
1633        $this->_changed = true;
1634
1635        /* If

Large files files are truncated, but you can click here to view the full file