PageRenderTime 114ms CodeModel.GetById 32ms app.highlight 70ms RepoModel.GetById 2ms app.codeStats 0ms

/imp-h3-4.3.10/lib/IMAP/Tree.php

#
PHP | 2114 lines | 1336 code | 200 blank | 578 comment | 215 complexity | a77810e65f8a02e7cbf4352edbdad3c1 MD5 | raw file

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

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

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