<?php
/**
 * Copyright 2011-2017 Horde LLC (http://www.horde.org/)
 *
 * See the enclosed file COPYING for license information (GPL). If you
 * did not receive this file, see http://www.horde.org/licenses/gpl.
 *
 * @category  Horde
 * @copyright 2011-2017 Horde LLC
 * @license   http://www.horde.org/licenses/gpl GPL
 * @package   IMP
 */

/**
 * This object is a clearinghouse for actions related to an IMP mailbox.
 *
 * @author    Michael Slusarz <slusarz@horde.org>
 * @category  Horde
 * @copyright 2011-2017 Horde LLC
 * @license   http://www.horde.org/licenses/gpl GPL
 * @package   IMP
 *
 * @property-read string $abbrev_label  Abbreviated version of $label -
 *                                      displays only the bare mailbox name
 *                                      (no parents).
 * @property-read boolean $access_creatembox  Can sub mailboxes be created?
 * @property-read boolean $access_deletembox  Can this mailbox be deleted?
 * @property-read boolean $access_deletembox_acl  Can this mailbox be deleted
 *                                                according to ACL rules?
 * @property-read boolean $access_deletemsgs  Can messages be deleted in this
 *                                            mailbox?
 * @property-read boolean $access_empty  Can this mailbox be emptied?
 * @property-read boolean $access_expunge  Can messages be expunged in this
 *                                    mailbox?
 * @property-read boolean $access_filters  Is filtering available?
 * @property-read boolean $access_flags  Are flags available?
 * @property-read boolean $access_search  Is searching available?
 * @property-read boolean $access_sort  Is sorting available?
 * @property-read boolean $access_sortthread  Is thread sort available?
 * @property-read mixed $acl  Either an ACL object for the mailbox, or null if
 *                            no ACL found for the mailbox.
 * @property-read string $basename  The basename of the mailbox (UTF-8).
 * @property-read string $cacheid  Cache ID for the mailbox.
 * @property-read string $cacheid_date  Cache ID for the mailbox, with added
 *                                      data information.
 * @property-read boolean $children  Does the element have children?
 * @property-read boolean $container  Is this a container element?
 * @property string $display  Display version of mailbox. Special mailboxes
 *                            are replaced with localized strings and
 *                            namespace information is removed.
 * @property-read string $display_html  $display that has been HTML encoded.
 * @property-read boolean $drafts  Is this a Drafts mailbox?
 * @property-read boolean $editquery  Can this search query be edited?
 * @property-read boolean $editvfolder  Can this virtual folder be edited?
 * @property-read boolean $exists  Does this mailbox exist on the IMAP server?
 * @property-read string $form_to  Converts this mailbox to a form
 *                                 representation.
 * @property-read object $icon  Icon information for the mailbox. Properties:
 *   - alt: (string) The alt text for the icon.
 *   - class: (string) The CSS class name.
 *   - icon: (Horde_Themes_Image) The icon graphic to use.
 *   - iconopen: (Horde_Themes_Image) The openicon to use.
 *   - user_icon: (boolean) Use a user defined icon?
 * @property-read IMP_Imap $imp_imap  The IMP_Imap object for this mailbox.
 * @property-read string $imap_mbox  The actual name of the underlying IMAP
 *                                   mailbox.
 * @property-read Horde_Imap_Client_Mailbox $imap_mbox_ob  Convert this object
 *                                                         tp an
 *                                                         Imap_Client mailbox
 *                                                         object.
 * @property-read boolean $inbox  Is this the INBOX?
 * @property-read boolean $innocent_show  Show the innocent action in this
 *                                        mailbox?
 * @property-read boolean $invisible  Is this mailbox invisible?
 * @property-read boolean $is_imap  Is this an IMAP mailbox?
 * @property-read boolean $is_open  Is this level expanded?
 * @property-read string $label  The mailbox label. Essentially is $display
 *                               that can be modified by user hook.
 * @property-read integer $level  The child level of this element.
 * @property-read IMP_Mailbox_List $list_ob  Returns the List object for the
 *                                           mailbox.
 * @property-read string $namespace  Is this a namespace element?
 * @property-read IMP_Mailbox $namespace_append  The mailbox with necessary
 *                                               namespace information appended.
 * @property-read string $namespace_delimiter  The delimiter for this
 *                                             namespace.
 * @property-read Horde_Imap_Client_Data_Namespace $namespace_info  Namespace
 *                                                                  info.
 * @property-read boolean $nonimap  Is this a non-IMAP element?
 * @property-read IMP_Mailbox $parent  The parent element. Returns null if no
 *                                     parent. (Base of tree is returned as
 *                                     a special element).
 * @property-read string $parent_imap  The IMAP parent name.
 * @property-read IMP_Imap_PermanentFlags $permflags  Return the list of
 *                                                    permanent flags
 *                                                    available to set in the
 *                                                    mailbox.
 * @property-read boolean $polled  Show polled information?
 * @property-read object $poll_info  Poll information for the mailbox.
 *                                   Properties:
 *   - msgs: (integer) The number of total messages in the element, if polled.
 *   - recent: (integer) The number of new messages in the element, if polled.
 *   - unseen: (integer) The number of unseen messages in the element, if
 *             polled.
 * @property-read string $pref_from  Convert mailbox name from preference
 *                                   storage.
 * @property-read string $pref_to  Convert mailbox name to preference storage.
 * @property-read boolean $query  Is this a search query?
 * @property-read boolean $readonly  Is this mailbox read-only?
 * @property-read boolean $remote  Is this a remote element?
 * @property-read IMP_Remote_Account $remote_account  Return the account
 *                                                    object for this element
 *                                                    (null if not a remote
 *                                                    element).
 * @property-read boolean $remote_container  Is this mailbox a remote special
 *                                           element?
 * @property-read boolean $remote_mbox  Is this mailbox on a remote server?
 * @property-read boolean $search  Is this a search mailbox?
 * @property-read string $size  Human readable size of the mailbox.
 * @property-read integer $size_raw  Size of mailbox in bytes. @since 6.2.14
 * @property-read IMP_Prefs_Sort $sortob  Sort ob for use with this mailbox.
 * @property-read boolean $spam  Is this a Spam mailbox?
 * @property-read boolean $spam_show  Show the spam action in this mailbox?
 * @property-read boolean $special  Is this is a "special" element?
 * @property-read boolean $special_outgoing  Is this a "special" element
 *                                           dealing with outgoing messages?
 * @property-read boolean $specialvfolder  Is this a "special" virtual folder?
 * @property-read boolean $sub  Is this mailbox subscribed to?
 * @property-read array $subfolders  Returns the list of subfolders as mailbox
 *                                   objects (including the current mailbox).
 * @property-read array $subfolders_only  Returns the list of subfolders as
 *                                        mailbox objects (NOT including the
 *                                        current mailbox).
 * @property-read boolean $systemquery  Is this a system (built-in) search
 *                                      query?
 * @property-read boolean $templates  Is this a Templates mailbox?
 * @property-read boolean $trash  Is this a Trash mailbox?
 * @property-read IMP_Ftree_Element $tree_elt  The tree element (null if it
 *                                             doesn't exist in the tree).
 * @property-read string $uidvalid  Returns the UIDVALIDITY string. Throws an
 *                                  IMP_Exception on error.
 * @property-read string $utf7imap  The UTF7-IMAP representation of this
 *                                  object.
 * @property-read string $value  The value of this element (IMAP mailbox name;
 *                               UTF-8).
 * @property-read boolean $vfolder  Is this a virtual folder?
 * @property-read boolean $vfolder_container  Is this the virtual folder
 *                                            container?
 * @property-read boolean $vinbox  Is this the virtual inbox?
 * @property-read boolean $vtrash  Is this the virtual trash?
 */
class IMP_Mailbox
{
    /* Special mailbox prefs. */
    const MBOX_DRAFTS = 'drafts_folder';
    const MBOX_SENT = 'sent_mail_folder';
    const MBOX_SPAM = 'spam_folder';
    const MBOX_TEMPLATES = 'composetemplates_mbox';
    const MBOX_TRASH = 'trash_folder';
    // This is just a placeholder - this pref doesn't exist.
    const MBOX_USERSPECIAL = 'user_special';

    /* Special mailbox identifiers. */
    const SPECIAL_COMPOSETEMPLATES = 'composetemplates';
    const SPECIAL_DRAFTS = 'drafts';
    const SPECIAL_SENT = 'sent';
    const SPECIAL_SPAM = 'spam';
    const SPECIAL_TRASH = 'trash';
    const SPECIAL_USER = 'userspecial';

    /**
     * The IMAP mailbox name (UTF-8).
     *
     * @var string
     */
    protected $_mbox;

    /**
     * Shortcut to obtaining mailbox object(s).
     *
     * @param mixed $mbox  The full IMAP mailbox name(s).
     *
     * @return mixed  The IMP_Mailbox object(s).
     */
    static public function get($mbox)
    {
        if (is_array($mbox)) {
            return array_filter(array_map(array(__CLASS__, 'get'), $mbox));
        }

        if ($mbox instanceof IMP_Mailbox) {
            return $mbox;
        }

        try {
            return $GLOBALS['injector']
                ->getInstance('IMP_Factory_Mailbox')
                ->create(strval($mbox));
        } catch (IMP_Exception $e) {
            return null;
        }
    }

    /**
     * Shortcut to obtaining Horde_Imap_Client_Mailbox object(s).
     *
     * @param mixed $mbox  The full IMAP mailbox name(s).
     *
     * @return mixed  The Horde_Imap_Client_Mailbox object(s).
     */
    static public function getImapMboxOb($mbox)
    {
        if (is_array($mbox)) {
            return array_filter(array_map(array(__CLASS__, 'getImapMboxOb'), $mbox));
        }

        if ($mbox instanceof Horde_Imap_Client_Mailbox) {
            return $mbox;
        }

        // Mailbox names are always UTF-8 within IMP.
        $mbox_ob = new self($mbox);
        return Horde_Imap_Client_Mailbox::get($mbox_ob->imap_mbox);
    }

    /**
     * Shortcut to obtaining a mailbox object from a preference name.
     *
     * @var string $pref  The preference name.
     *
     * @return IMP_Mailbox  The IMP_Mailbox object.
     */
    static public function getPref($pref)
    {
        return self::get(self::prefFrom($GLOBALS['prefs']->getValue($pref)));
    }

    /**
     * Constructor.
     *
     * @var string $mbox  The full IMAP mailbox name.
     *
     * @throws IMP_Exception
     */
    public function __construct($mbox)
    {
        if (strlen($mbox) === 0) {
            throw new IMP_Exception('Mailbox name must not be empty.');
        }

        $this->_mbox = $mbox;
    }

    /**
     */
    public function __toString()
    {
        return strval(
            ($this->_mbox == IMP_Ftree::BASE_ELT) ? '' : $this->_mbox
        );
    }

    /**
     */
    public function __get($key)
    {
        global $injector;

        switch ($key) {
        case 'abbrev_label':
            $label = $this->label;
            return ($this->nonimap || ($pos = strrpos($label, $this->namespace_delimiter)) === false)
                ? $label
                : substr($label, $pos + 1);

        case 'access_creatembox':
            return (!($acl = $this->acl) ||
                    ($acl[Horde_Imap_Client::ACL_CREATEMBOX]));

        case 'access_deletembox':
            return ($this->access_deletembox_acl);

        case 'access_deletembox_acl':
            return (!($acl = $this->acl) ||
                    ($acl[Horde_Imap_Client::ACL_DELETEMBOX]));

        case 'access_deletemsgs':
            return (!($acl = $this->acl) ||
                    ($acl[Horde_Imap_Client::ACL_DELETEMSGS]));

        case 'access_empty':
            if ($this->access_deletemsgs && $this->access_expunge) {
                $special = $this->getSpecialMailboxes();
                return empty($special[self::SPECIAL_TRASH]) ||
                    !$special[self::SPECIAL_TRASH]->vtrash ||
                    ($special[self::SPECIAL_TRASH] == $this);
            }
            return false;

        case 'access_expunge':
            return (!($acl = $this->acl) ||
                    ($acl[Horde_Imap_Client::ACL_EXPUNGE]));

        case 'access_filters':
            return !$this->search && $this->is_imap;

        case 'access_flags':
            return $this->is_imap;

        case 'access_search':
            return $this->is_imap;

        case 'access_sort':
            /* Although possible to abstract other sorting methods, all other
             * non-sequence methods require a download of ALL messages, which
             * is too much overhead.*/
            return $this->is_imap;

        case 'access_sortthread':
            /* Thread sort is always available for IMAP servers, since
             * Horde_Imap_Client_Socket has a built-in ORDEREDSUBJECT
             * implementation. We will always prefer REFERENCES, but will
             * fallback to ORDEREDSUBJECT if the server doesn't support THREAD
             * sorting. */
            return $this->is_imap;

        case 'acl':
            $cache = $injector->getInstance('IMP_Mailbox_SessionCache');
            if (($acl = $cache->getAcl($this->_mbox)) !== false) {
                return $acl;
            }

            if ($this->nonimap) {
                $acl = null;
            } else {
                $acl = $injector->getInstance('IMP_Imap_Acl')->getACL($this, true);
                $hooks = $injector->getInstance('Horde_Core_Hooks');

                if ($hooks->hookExists('mbox_acl', 'imp')) {
                    $hooks->callHook('mbox_acl', 'imp', array($this, $acl));
                }
            }

            $cache->setAcl($this->_mbox, $acl);

            return $acl;

        case 'basename':
            if ($this->nonimap) {
                return $this->label;
            }

            $mbox = $this->remote_mbox
                ? $this->label
                : $this->_mbox;

            return (($pos = strrpos($mbox, $this->namespace_delimiter)) === false)
                ? strval($mbox)
                : substr($mbox, $pos + 1);

        case 'cacheid':
        case 'cacheid_date':
            return $this->_getCacheID($key == 'cacheid_date');

        case 'children':
            return (($elt = $this->tree_elt) && $elt->children);

        case 'container':
            return (($elt = $this->tree_elt) && $elt->container);

        case 'display':
            return $this->nonimap
                ? $this->label
                : $this->_getDisplay();

        case 'display_html':
            return htmlspecialchars($this->display);

        case 'display_notranslate':
            return $this->nonimap
                ? $this->label
                : $this->_getDisplay(true);

        case 'drafts':
            $special = $this->getSpecialMailboxes();
            return ($this->_mbox == $special[self::SPECIAL_DRAFTS]);

        case 'editquery':
            return $injector->getInstance('IMP_Search')->isQuery($this->_mbox, true);

        case 'editvfolder':
            return $injector->getInstance('IMP_Search')->isVFolder($this->_mbox, true);

        case 'exists':
            return $injector->getInstance('IMP_Mailbox_SessionCache')->exists($this);

        case 'form_to':
            return $this->formTo($this->_mbox);

        case 'icon':
            return $this->_getIcon();

        case 'imp_imap':
            return $injector->getInstance('IMP_Factory_Imap')->create(strval($this));

        case 'imap_mbox':
            return strval(
                $injector->getInstance('IMP_Remote')->getMailboxById($this->_mbox) ?: $this->_mbox
            );

        case 'imap_mbox_ob':
            return self::getImapMboxOb($this->_mbox);

        case 'inbox':
            return (strcasecmp($this->_mbox, 'INBOX') === 0);

        case 'innocent_show':
            $p = $this->imp_imap->config->innocent_params;
            return (!empty($p) &&
                    ((isset($p['display']) && empty($p['display'])) || $this->spam));

        case 'invisible':
            return (($elt = $this->tree_elt) && $elt->invisible);

        case 'is_imap':
            return $this->imp_imap->isImap();

        case 'is_open':
            return (($elt = $this->tree_elt) && $elt->open);

        case 'label':
            $cache = $injector->getInstance('IMP_Mailbox_SessionCache');
            if (($label = $cache->getLabel($this->_mbox)) !== false) {
                return $label;
            }

            /* Returns the plain text label that is displayed for the
             * current mailbox, replacing virtual search mailboxes with an
             * appropriate description, removing namespace and mailbox
             * prefix information from what is shown to the user, and
             * passing the label through a user-defined hook. */
            $imp_search = $injector->getInstance('IMP_Search');
            $label = ($ob = $imp_search[$this->_mbox])
                ? $ob->label
                : $this->_getDisplay();

            $hooks = $injector->getInstance('Horde_Core_Hooks');
            if ($hooks->hookExists('mbox_label' ,'imp')) {
                $label = $hooks->callHook(
                    'mbox_label',
                    'imp',
                    array($this->_mbox, $label)
                );
            }

            $cache->setLabel($this->_mbox, $label);

            return $label;

        case 'level':
            return ($elt = $this->tree_elt) ? $elt->level : 0;

        case 'list_ob':
            return $injector->getInstance('IMP_Factory_MailboxList')->create($this);

        case 'namespace':
            return (($elt = $this->tree_elt) && $elt->namespace);

        case 'namespace_append':
            $imp_imap = $this->imp_imap;
            $def_ns = $imp_imap->getNamespace($imp_imap::NS_DEFAULT);
            if (is_null($def_ns)) {
                return $this;
            }
            $empty_ns = $imp_imap->getNamespace('');

            /* If default namespace is empty, or there is no empty namespace,
             * then we can auto-detect namespace from input.
             * If a non-default namespace is empty, then we must always use
             * default namespace. */
            if (!is_null($empty_ns) &&
                ($def_ns->name == $empty_ns->name)) {
                return $this;
            }

            $ns_info = $this->namespace_info;

            if (is_null($ns_info) || !is_null($empty_ns)) {
                return self::get($def_ns->name . $this->_mbox);
            }

            return $this;

        case 'namespace_delimiter':
            $ns_info = $this->namespace_info;
            return is_null($ns_info)
                ? ''
                : $ns_info->delimiter;

        case 'namespace_info':
            return $this->imp_imap->getNamespace(strlen($this) ? $this->_mbox : IMP_Imap::NS_DEFAULT);

        case 'nonimap':
            return ($this->search ||
                    (($elt = $this->tree_elt) && $elt->nonimap));

        case 'parent':
            return ($elt = $this->tree_elt) ? $elt->parent->mbox_ob : null;

        case 'parent_imap':
            return (is_null($p = $this->parent) || !strlen($p))
                ? null
                : $p;

        case 'permflags':
            if ($this->access_flags) {
                $imp_imap = $this->imp_imap;
                try {
                    /* Make sure we are in R/W mailbox mode (SELECT). No flags
                     * are allowed in EXAMINE mode. */
                    $imp_imap->openMailbox($this, Horde_Imap_Client::OPEN_READWRITE);
                    $status = $imp_imap->status($this->_mbox, Horde_Imap_Client::STATUS_FLAGS | Horde_Imap_Client::STATUS_PERMFLAGS);
                    return new IMP_Imap_PermanentFlags($status['permflags'], $status['flags']);
                } catch (Exception $e) {}
            }

            return new IMP_Imap_PermanentFlags();

        case 'poll_info':
            $info = new stdClass;
            $info->msgs = 0;
            $info->recent = 0;
            $info->unseen = 0;

            try {
                if ($msgs_info = $this->imp_imap->status($this->_mbox, Horde_Imap_Client::STATUS_RECENT_TOTAL | Horde_Imap_Client::STATUS_UNSEEN | Horde_Imap_Client::STATUS_MESSAGES)) {
                    if (!empty($msgs_info['recent_total'])) {
                        $info->recent = intval($msgs_info['recent_total']);
                    }
                    $info->msgs = intval($msgs_info['messages']);
                    $info->unseen = intval($msgs_info['unseen']);
                }
            } catch (IMP_Imap_Exception $e) {}

            return $info;

        case 'polled':
            return (!$this->search &&
                    (($elt = $this->tree_elt) && $elt->polled));

        case 'pref_from':
            return $this->prefFrom($this->_mbox);

        case 'pref_to':
            return $this->prefTo($this->_mbox);

        case 'query':
            return $injector->getInstance('IMP_Search')->isQuery($this->_mbox);

        case 'readonly':
            return (($acl = $this->acl) &&
                    !$acl[Horde_Imap_Client::ACL_DELETEMBOX] &&
                    !$acl[Horde_Imap_Client::ACL_DELETEMSGS] &&
                    !$acl[Horde_Imap_Client::ACL_EXPUNGE] &&
                    !$acl[Horde_Imap_Client::ACL_INSERT] &&
                    !$acl[Horde_Imap_Client::ACL_SEEN] &&
                    !$acl[Horde_Imap_Client::ACL_WRITE]);

        case 'remote':
            return $injector->getInstance('IMP_Remote')->isRemoteMbox($this->_mbox);

        case 'remote_account':
            $remote = $injector->getInstance('IMP_Remote');
            $account = ($this->remote_container)
                ? $remote[$this->_mbox]
                : $remote->getRemoteById($this->_mbox);
            return $account ?: null;

        case 'remote_container':
            return (($elt = $this->tree_elt) && $elt->remote);

        case 'remote_mbox':
            return (($elt = $this->tree_elt) && $elt->remote_mbox);

        case 'search':
            return $injector->getInstance('IMP_Search')->isSearchMbox($this->_mbox);

        case 'size':
            return $injector->getInstance('IMP_Mbox_Size')->getSize($this);

        case 'size_raw':
            return $injector->getInstance('IMP_Mbox_Size')->getSize($this, false);

        case 'sortob':
            return $this->imp_imap->access(IMP_Imap::ACCESS_SORT)
                ? $injector->getInstance('IMP_Prefs_Sort')
                : $injector->getInstance('IMP_Prefs_Sort_None');

        case 'spam':
            $special = $this->getSpecialMailboxes();
            return ($this->_mbox == $special[self::SPECIAL_SPAM]);

        case 'spam_show':
            $p = $this->imp_imap->config->spam_params;
            return (!empty($p) && (!empty($p['display']) || !$this->spam));

        case 'special':
            $special = $this->getSpecialMailboxes();

            switch ($this->_mbox) {
            case $special[self::SPECIAL_COMPOSETEMPLATES]:
            case $special[self::SPECIAL_DRAFTS]:
            case $special[self::SPECIAL_SPAM]:
            case $special[self::SPECIAL_TRASH]:
                return true;
            }

            return in_array($this->_mbox, array_merge(
                $special[self::SPECIAL_SENT],
                $special[self::SPECIAL_USER]
            ));

        case 'special_outgoing':
            $special = $this->getSpecialMailboxes();

            return in_array($this->_mbox, array_merge(
                array(
                    $special[self::SPECIAL_COMPOSETEMPLATES],
                    $special[self::SPECIAL_DRAFTS]
                ),
                $special[self::SPECIAL_SENT]
            ));

        case 'specialvfolder':
            return !$this->editvfolder;

        case 'sub':
            return (($elt = $this->tree_elt) && $elt->subscribed);

        case 'subfolders':
            return $this->get(array_merge(array($this->_mbox), $this->subfolders_only));

        case 'subfolders_only':
            return $this->get($this->imp_imap->listMailboxes($this->imap_mbox_ob->list_escape . $this->namespace_delimiter . '*', null, array('flat' => true)));

        case 'systemquery':
            return $injector->getInstance('IMP_Search')->isSystemQuery($this->_mbox);

        case 'templates':
            $special = $this->getSpecialMailboxes();
            return ($this->_mbox == $special[self::SPECIAL_COMPOSETEMPLATES]);

        case 'trash':
            $special = $this->getSpecialMailboxes();
            return ($this->_mbox == $special[self::SPECIAL_TRASH]);

        case 'tree_elt':
            $ftree = $injector->getInstance('IMP_Ftree');
            return $ftree[$this->_mbox];

        case 'uidvalid':
            $cache = $injector->getInstance('IMP_Mailbox_SessionCache');
            $uidvalid = $cache->getUidvalidity($this->_mbox);
            if ($uidvalid === 0) {
                return;
            }

            // POP3 and non-IMAP mailboxes do not support UIDVALIDITY.
            if (!$this->is_imap || $this->nonimap) {
                $cache->setUidvalidity($this->_mbox, 0);
                return false;
            }

            $status = $this->imp_imap->status($this->_mbox, Horde_Imap_Client::STATUS_UIDVALIDITY);

            if (($first = ($uidvalid === false)) ||
                ($status['uidvalidity'] != $uidvalid)) {
                $uidvalid = $status['uidvalidity'];
                $cache->setUidvalidity($this->_mbox, $uidvalid);

                if (!$first) {
                    throw new IMP_Exception(_("Mailbox structure on server has changed."));
                }
            }

            return $uidvalid;

        case 'utf7imap':
            return Horde_String::convertCharset($this->_mbox, 'UTF-8', 'UTF7-IMAP');

        case 'value':
            return $this->_mbox;

        case 'vfolder':
            return $injector->getInstance('IMP_Search')->isVFolder($this->_mbox);

        case 'vfolder_container':
            return ($this->_mbox == IMP_Ftree_Account_Vfolder::VFOLDER_KEY);

        case 'vinbox':
            return $injector->getInstance('IMP_Search')->isVinbox($this->_mbox);

        case 'vtrash':
            return $injector->getInstance('IMP_Search')->isVTrash($this->_mbox);
        }

        return false;
    }

    /**
     */
    public function __set($key, $value)
    {
        global $injector;

        switch ($key) {
        case 'display':
            $injector->getInstance('IMP_Mailbox_SessionCache')->setDisplay($this->_mbox, $value);
            break;
        }
    }

    /**
     * Create this mailbox on the server.
     *
     * @param array $opts  Additional options:
     *   - special_use: (array) An array of special-use attributes to attempt
     *                  to add to the mailbox.
     *                  DEFAULT: NONE
     *   - subscribe: (boolean) Override preference value of subscribe.
     *
     * @return boolean  True on success.
     * @throws Horde_Exception
     */
    public function create(array $opts = array())
    {
        global $injector, $notification, $prefs;

        if ($this->exists) {
            return true;
        }

        $imp_imap = $this->imp_imap;

        /* Check permissions. */
        if (!$imp_imap->access(IMP_Imap::ACCESS_CREATEMBOX)) {
            Horde::permissionDeniedError(
                'imp',
                'create_mboxes',
                _("You are not allowed to create mailboxes.")
            );
            return false;
        }
        if (!$imp_imap->access(IMP_Imap::ACCESS_CREATEMBOX_MAX)) {
            Horde::permissionDeniedError(
                'imp',
                'max_create_mboxes',
                sprintf(_("You are not allowed to create more than %d mailboxes."), $imp_imap->max_create_mboxes)
            );
            return false;
        }

        /* Special use flags. */
        $special_use = isset($opts['special_use'])
            ? $opts['special_use']
            : array();

        /* Attempt to create the mailbox. */
        try {
            $imp_imap->createMailbox($this->_mbox, array('special_use' => $special_use));
        } catch (IMP_Imap_Exception $e) {
            if ($e->getCode() == $e::USEATTR) {
                unset($opts['special_use']);
                return $this->create($opts);
            }

            $e->notify(sprintf(_("The mailbox \"%s\" was not created. This is what the server said"), $this->display) . ': ' . $e->getMessage());
            return false;
        }

        $notification->push(sprintf(_("The mailbox \"%s\" was successfully created."), $this->display), 'horde.success');

        /* Subscribe, if requested. */
        if ((!isset($opts['subscribe']) && $prefs->getValue('subscribe')) ||
            !empty($opts['subscribe'])) {
            try {
                $imp_imap->subscribeMailbox($this->_mbox, true);
            } catch (IMP_Imap_Exception $e) {}
        }

        /* Update the mailbox tree. */
        $injector->getInstance('IMP_Ftree')->insert($this->_mbox);

        return true;
    }

    /**
     * Deletes mailbox.
     *
     * @param array $opts  Addtional options:
     *   - subfolders: (boolean) Delete all subfolders?
     *     DEFAULT: false
     *   - subfolders_only: (boolean) If deleting subfolders, delete only
     *                      subfolders (not current mailbox)?
     *     DEFAULT: false
     *
     * @return boolean  True on success.
     */
    public function delete(array $opts = array())
    {
        global $injector, $notification;

        if ($this->vfolder) {
            if ($this->editvfolder) {
                $imp_search = $injector->getInstance('IMP_Search');
                $label = $imp_search[$this->_mbox]->label;
                unset($imp_search[$this->_mbox]);
                $notification->push(sprintf(_("Deleted Virtual Folder \"%s\"."), $label), 'horde.success');
                return true;
            }

            $notification->push(sprintf(_("Could not delete Virtual Folder \"%s\"."), $this->label), 'horde.error');
            return false;
        }

        $deleted = array();
        $imp_imap = $this->imp_imap;
        if (empty($opts['subfolders'])) {
            $to_delete = array($this);
        } else {
            $to_delete = empty($opts['subfolders_only'])
                ? $this->subfolders
                : $this->subfolders_only;
        }

        foreach ($to_delete as $val) {
            if (!$val->access_deletembox_acl) {
                $notification->push(sprintf(_("The mailbox \"%s\" may not be deleted."), $val->display), 'horde.error');
                continue;
            }

            try {
                $imp_imap->deleteMailbox($val->value);
                $notification->push(sprintf(_("The mailbox \"%s\" was successfully deleted."), $val->display), 'horde.success');
                $deleted[] = $val;
            } catch (IMP_Imap_Exception $e) {
                $e->notify(sprintf(_("The mailbox \"%s\" was not deleted. This is what the server said"), $val->display) . ': ' . $e->getMessage());
            }
        }

        if (!empty($deleted)) {
            $injector->getInstance('IMP_Ftree')->delete($deleted);
            $this->_onDelete($deleted);
        }

        return (count($deleted) == count($to_delete));
    }

    /**
     * Rename this mailbox on the server. The subscription status remains the
     * same.  All subfolders will also be renamed.
     *
     * @param string $new_name  The new mailbox name (UTF-8).
     *
     * @return boolean  True on success
     */
    public function rename($new_name)
    {
        global $injector, $notification;

        /* Don't try to rename to an empty string. */
        if (!strlen($new_name)) {
            return false;
        }

        if (!$this->access_deletembox_acl) {
            $notification->push(sprintf(_("The mailbox \"%s\" may not be renamed."), $this->display), 'horde.error');
            return false;
        }

        $new_mbox = $this->get($new_name);
        $old_list = $this->subfolders;

        try {
            $this->imp_imap->renameMailbox($this->_mbox, $new_mbox);
        } catch (IMP_Imap_Exception $e) {
            $e->notify(sprintf(_("Renaming \"%s\" to \"%s\" failed. This is what the server said"), $this->display, $new_mbox->display) . ': ' . $e->getMessage());
            return false;
        }

        $notification->push(sprintf(_("The mailbox \"%s\" was successfully renamed to \"%s\"."), $this->display, $new_mbox->display), 'horde.success');

        $injector->getInstance('IMP_Ftree')->rename($this->_mbox, $new_mbox);
        $this->_onDelete($old_list);

        return true;
    }

    /**
     * Subscribe/unsubscribe to an IMAP mailbox.
     *
     * @param boolean $sub  True to subscribe, false to unsubscribe.
     * @param array $opts   Additional options:
     * <pre>
     *   - subfolders: (boolean) If true, applies actions to all subfolders.
     * </pre>
     *
     * @return boolean  True on success.
     */
    public function subscribe($sub, array $opts = array())
    {
        global $injector, $notification, $prefs;

        /* Skip non-IMAP/container mailboxes. */
        if (!$prefs->getValue('subscribe') ||
            $this->nonimap ||
            $this->container) {
            return false;
        }

        if (!$sub && $this->inbox) {
            $notification->push(sprintf(_("You cannot unsubscribe from \"%s\"."), $this->display), 'horde.error');
            return false;
        }

        $imp_imap = $this->imp_imap;

        try {
            $imp_imap->subscribeMailbox($this->_mbox, $sub);
        } catch (IMP_Imap_Exception $e) {
            if ($sub) {
                $e->notify(sprintf(_("You were not subscribed to \"%s\". Here is what the server said"), $this->display) . ': ' . $e->getMessage());
            } else {
                $e->notify(sprintf(_("You were not unsubscribed from \"%s\". Here is what the server said"), $this->display) . ': ' . $e->getMessage());
            }
            return false;
        }

        $imap_tree = $injector->getInstance('IMP_Ftree');
        if ($sub) {
            $imap_tree->subscribe($this->_mbox);
        } else {
            $imap_tree->unsubscribe($this->_mbox);
        }

        if (empty($opts['subfolders'])) {
            $notify = $sub
                ? sprintf(_("You were successfully subscribed to \"%s\"."), $this->display)
                : sprintf(_("You were successfully unsubscribed from \"%s\"."), $this->display);
        } else {
            $action = false;

            foreach ($this->subfolders_only as $val) {
                try {
                    $imp_imap->subscribeMailbox($val, $sub);
                    if ($sub) {
                        $imap_tree->subscribe($val);
                    } else {
                        $imap_tree->unsubscribe($val);
                    }

                    $action = true;
                } catch (IMP_Imap_Exception $e) {
                    // Ignore errors for sub-mailboxes.
                }
            }

            if ($action) {
                $notify = $sub
                    ? sprintf(_("You were successfully subscribed to \"%s\" and all subfolders."), $this->display)
                    : sprintf(_("You were successfully unsubscribed from \"%s\" and all subfolders."), $this->display);
            }
        }

        $notification->push($notify, 'horde.success');

        return true;
    }

    /**
     * Runs filters on this mailbox.
     */
    public function filter()
    {
        if (!$this->search) {
            $GLOBALS['injector']->getInstance('IMP_Filter')->filter($this);
        }
    }

    /**
     * Filters this mailbox if it is the INBOX and the filter on display pref
     * is active.
     *
     * @return boolean  True if filter() was called.
     */
    public function filterOnDisplay()
    {
        if ($this->inbox &&
            $GLOBALS['prefs']->getValue('filter_on_display')) {
            $this->filter();
            return true;
        }

        return false;
    }

    /**
     * Return the search query object for this mailbox.
     *
     * @return IMP_Search_Query  The search query object.
     */
    public function getSearchOb()
    {
        $imp_search = $GLOBALS['injector']->getInstance('IMP_Search');
        return $imp_search[$this->_mbox];
    }

    /**
     * Return an indices object for this mailbox.
     *
     * @param mixed $in  Either a single UID, array of UIDs, or a
     *                   Horde_Imap_Client_Ids object.
     *
     * @return IMP_Indices  An indices object.
     */
    public function getIndicesOb($in)
    {
        return new IMP_Indices($this, $in);
    }

    /**
     * Return the sorting preference for this mailbox.
     *
     * @param boolean $convert  Convert 'by' to a Horde_Imap_Client constant?
     *
     * @return IMP_Prefs_Sort_Sortpref  Sortpref object.
     */
    public function getSort($convert = false)
    {
        global $prefs;

        $mbox = $this->search
            ? $this
            : self::get($this->pref_from);

        $ob = $this->sortob[strval($mbox)];
        $ob->convertSortby();

        if ($convert && ($ob->sortby == IMP::IMAP_SORT_DATE)) {
            $ob->sortby = $prefs->getValue('sortdate');
        }

        return $ob;
    }

    /**
     * Set the sorting preference for this mailbox.
     *
     * @param integer $by      The sort type.
     * @param integer $dir     The sort direction.
     * @param boolean $delete  Delete the entry?
     */
    public function setSort($by = null, $dir = null, $delete = false)
    {
        $mbox = $this->search
            ? $this
            : self::get($this->pref_from);

        if ($delete) {
            unset($this->sortob[strval($mbox)]);
        } else {
            $change = array();
            if (!is_null($by)) {
                $change['by'] = $by;
            }
            if (!is_null($dir)) {
                $change['dir'] = $dir;
            }
            $this->sortob[strval($mbox)] = $change;
        }
    }

    /**
     * Are deleted messages hidden in this mailbox?
     *
     * @param boolean $deleted  Return value is what should be done with
     *                          deleted messages in general, as opposed to any
     *                          deleted message in the mailbox.
     *
     * @return boolean  True if deleted messages should be hidden.
     */
    public function hideDeletedMsgs($deleted = false)
    {
        global $prefs;

        if (!$this->access_flags) {
            return true;
        }

        if ($prefs->getValue('use_trash')) {
            /* If using Virtual Trash, only show deleted messages in
             * the Virtual Trash mailbox. */
            return $this->get($prefs->getValue(self::MBOX_TRASH))->vtrash
                ? !$this->vtrash
                : ($prefs->getValue('delhide_trash') ? true : $deleted);
        }

        return $prefs->getValue('delhide');
    }

    /**
     * Sets the 'delhide' preference and clears necessary cached data.
     *
     * @param boolean $value  The value to set 'delhide' to.
     */
    public function setHideDeletedMsgs($value)
    {
        $GLOBALS['prefs']->setValue('delhide', $value);
        $GLOBALS['injector']->getInstance('IMP_Factory_MailboxList')->expireAll();
    }

    /**
     * Run a search query on this mailbox that is not stored in the current
     * session. Allows custom queries with custom sorts to be used without
     * affecting cached mailboxes.
     *
     * @param Horde_Imap_Client_Search_Query $query  The search query object.
     * @param integer $sortby                        The sort criteria.
     * @param integer $sortdir                       The sort directory.
     *
     * @return IMP_Indices  An indices object.
     */
    public function runSearchQuery(Horde_Imap_Client_Search_Query $query,
                                   $sortby = null, $sortdir = null)
    {
        try {
            $results = $this->imp_imap->search($this, $query, array(
                'sort' => is_null($sortby) ? null : array($sortby)
            ));
            if ($sortdir) {
                $results['match']->reverse();
            }
            return $this->getIndicesOb($results['match']);
        } catch (IMP_Imap_Exception $e) {
            return new IMP_Indices();
        }
    }

    /**
     * Generate a URL using the current mailbox.
     *
     * @param string|Horde_Url $page  Page name to link to.
     * @param string $buid            The BUID to use on the linked page.
     * @param boolean $encode         Encode the argument separator?
     *
     * @return Horde_Url  URL to $page with any necessary mailbox information
     *                    added to the parameter list of the URL.
     */
    public function url($page, $buid = null, $encode = true)
    {
        if ($page instanceof Horde_Url) {
            return $page->add($this->urlParams($buid))->setRaw(!$encode);
        }

        switch ($GLOBALS['registry']->getView()) {
        case Horde_Registry::VIEW_BASIC:
            switch ($page) {
            case 'message':
                return IMP_Basic_Message::url(array(
                    'buid' => $buid,
                    'mailbox' => $this->_mbox
                ))->setRaw(!$encode);

            case 'mailbox':
                return IMP_Basic_Mailbox::url(array(
                    'mailbox' => $this->_mbox
                ))->setRaw(!$encode);
            }
            break;

        case Horde_Registry::VIEW_DYNAMIC:
            $anchor = is_null($buid)
                ? ('mbox:' . $this->form_to)
                : ('msg:' . $this->form_to . ';' . $buid);
            return Horde::url('index.php')->setAnchor($anchor);

        case Horde_Registry::VIEW_MINIMAL:
            switch ($page) {
            case 'message':
                return IMP_Minimal_Message::url(array(
                    'buid' => $buid,
                    'mailbox' => $this->_mbox
                ))->setRaw(!$encode);

            case 'mailbox':
                return IMP_Minimal_Mailbox::url(array(
                    'mailbox' => $this->_mbox
                ))->setRaw(!$encode);
            }
            break;

        case Horde_Registry::VIEW_SMARTMOBILE:
            $url = Horde::url('smartmobile.php');
            $anchor = is_null($buid)
                ? ('mbox=' . $this->form_to)
                : ('msg=' . $this->form_to . ';' . $buid);
            $url->setAnchor('mailbox?' . $anchor);
            return $url;
        }

        return Horde::url($page . '.php')->add($this->urlParams($buid))->setRaw(!$encode);
    }

    /**
     * Returns list of URL parameters necessary to indicate current mailbox
     * status.
     *
     * @param string $buid  The BUID to use on the linked page.
     *
     * @return array  The list of parameters needed to indicate the current
     *                mailbox status.
     */
    public function urlParams($buid = null)
    {
        $params = array('mailbox' => $this->form_to);
        if (!is_null($buid)) {
            $params['buid'] = $buid;
        }
        return $params;
    }

    /**
     * Determines if this mailbox is equal to the given mailbox.
     * Needed because directly comparing two mailbox objects may fail (the
     * member variables may be different).
     *
     * @param mixed $mbox  The mailbox to compare to.
     *
     * @return boolean  True if the mailboxes are the same.
     */
    public function equals($mbox)
    {
        return ($mbox == $this->_mbox);
    }

    /**
     * Create an indices object from a list of browser-UIDs.
     *
     * @param IMP_Indices|array $buids  Browser-UIDs.
     *
     * @return IMP_Indices  An indices object.
     */
    public function fromBuids($buids)
    {
        if (is_array($buids)) {
            $buids = new IMP_Indices($this->_mbox, $buids);
        }
        $buid_list = $buids->getSingle(true);

        $out = new IMP_Indices();

        if ($buid_list[1]) {
            $list_ob = $this->list_ob;
            foreach ($buid_list[1] as $buid) {
                if ($resolve = $list_ob->resolveBuid($buid)) {
                    $out->add($resolve['m'], $resolve['u']);
                }
            }
        }

        return $out;
    }

    /**
     * Create a BUID indices object from a list of UIDs.
     *
     * @param IMP_Indices $uids  UIDs.
     *
     * @return IMP_Indices  An indices object.
     */
    public function toBuids(IMP_Indices $uids)
    {
        $list_ob = $this->list_ob;
        $out = new IMP_Indices();

        foreach ($uids as $val) {
            foreach ($val->uids as $val2) {
                $out->add($this->_mbox, $list_ob->getBuid($val->mbox, $val2));
            }
        }

        return $out;
    }

    /**
     * Return the mailbox name to create given a submailbox name.
     *
     * @param string $new  The submailbox name (UTF-8).
     *
     * @return IMP_Mailbox  The mailbox to create.
     */
    public function createMailboxName($new)
    {
        if ($this->remote_container) {
            $new = $this->remote_account->mailbox($new);
        } else {
            $ns_info = $this->namespace_info;
            $new = strlen($this)
                ? ($this->_mbox . $ns_info->delimiter . $new)
                : $ns_info->name . $new;
        }

        return self::get($new);
    }

    /* Static methods. */

    /**
     * Converts a mailbox string from a form representation.
     * Needed because null characters (used for various internal non-IMAP
     * mailbox representations) will not work in form elements.
     *
     * @param mixed $mbox  The mailbox name(s).
     *
     * @return mixed  The mailbox object(s).
     */
    static public function formFrom($mbox)
    {
        return is_array($mbox)
            ? array_filter(array_map(array(__CLASS__, 'formFrom'), $mbox))
              // Base64url (RFC 4648 [5]) encoding
            : self::get(base64_decode(strtr($mbox, '-_', '+/')));
    }

    /**
     * Converts a mailbox string to a form representation.
     * Needed because null characters (used for various internal non-IMAP
     * mailbox representations) will not work in form elements.
     *
     * @param mixed $mbox  The mailbox name(s).
     *
     * @return mixed  The converted mailbox string(s).
     */
    static public function formTo($mbox)
    {
        return is_array($mbox)
            ? array_filter(array_map(array(__CLASS__, 'formTo'), $mbox))
              // Base64url (RFC 4648 [5]) encoding
            : strtr(rtrim(base64_encode($mbox), '='), '+/', '-_');
    }

    /**
     * Return the list of special mailboxes.
     *
     * @return array  A list of mailboxes, with the self::SPECIAL_* constants
     *                as keys and values containing the IMP_Mailbox objects or
     *                null if the mailbox doesn't exist (self::SPECIAL_SENT
     *                contains an array of objects).
     */
    static public function getSpecialMailboxes()
    {
        global $injector;

        return $injector->getInstance('IMP_Mailbox_SessionCache')->getSpecialMailboxes();
    }

    /**
     * Return the list of sorted special mailboxes.
     *
     * @return array  The list of sorted special mailboxes (IMP_Mailbox
     *                objects).
     */
    static public function getSpecialMailboxesSort()
    {
        $out = array();

        foreach (array_filter(self::getSpecialMailboxes()) as $val) {
            if (is_array($val)) {
                $out = array_merge($out, $val);
            } else {
                $out[] = $val;
            }
        }

        $tmp = array();
        foreach ($out as $val) {
            $tmp[strval($val)] = $val->abbrev_label;
        }
        asort($tmp, SORT_LOCALE_STRING);

        return self::get(array_keys($tmp));
    }

    /**
     * Converts a mailbox name from a value stored in the preferences.
     *
     * @param string $mbox  The mailbox name as stored in a preference.
     *
     * @return string  The full IMAP mailbox name (UTF-8).
     */
    static public function prefFrom($mbox)
    {
        $imp_imap = $GLOBALS['injector']->getInstance('IMP_Factory_Imap')->create();
        if ($imp_imap->isImap()) {
            $empty_ns = $imp_imap->getNamespace('');

            if (!is_null($empty_ns) &&
                (strpos($mbox, $empty_ns->delimiter) === 0)) {
                /* Prefixed with delimiter => from empty namespace. */
                return substr($mbox, strlen($empty_ns->delimiter));
            } elseif ($imp_imap->getNamespace($mbox, true) === null) {
                /* No namespace prefix => from personal namespace. */
                $def_ns = $imp_imap->getNamespace($imp_imap::NS_DEFAULT);
                return $def_ns->name . $mbox;
            }
        }

        return $mbox;
    }

    /**
     * Converts a mailbox name to a value to be stored in a preference.
     *
     * @param string $mbox  The full IMAP mailbox name (UTF-8).
     *
     * @return string  The value to store in a preference.
     */
    static public function prefTo($mbox)
    {
        global $injector;

        $cache = $injector->getInstance('IMP_Mailbox_SessionCache');
        $mbox_str = $ret = strval($mbox);

        if (($pref_to = $cache->getPrefTo($mbox_str)) !== false) {
            return $pref_to;
        }

        if (($ns = self::get($mbox)->namespace_info) !== null) {
            $imp_imap = $injector->getInstance('IMP_Factory_Imap')->create();
            $def_ns = $imp_imap->getNamespace($imp_imap::NS_DEFAULT);

            if ($ns->name == $def_ns->name) {
                /* From personal namespace => strip namespace. */
                $ret = substr($mbox_str, strlen($def_ns->name));
            } else {
                $empty_ns = $imp_imap->getNamespace('');
                if ($ns->name == $empty_ns->name) {
                    /* From empty namespace => prefix with delimiter. */
                    $ret = $empty_ns->delimiter . $mbox_str;
                }
            }
        }

        $cache->setPrefTo($mbox_str, $ret);

        return $ret;
    }

    /* Internal methods. */

    /**
     * Returns a unique identifier for this mailbox's status.
     *
     * This cache ID is guaranteed to change if messages are added/deleted
     * from the mailbox. Additionally, if CONDSTORE is available on the remote
     * IMAP server, this ID will change if flag information changes.
     *
     * For search mailboxes, this value never changes (search mailboxes must
     * be forcibly refreshed).
     *
     * @param boolean $date  If true, adds date information to ID.
     *
     * @return string  The cache ID string, which will change when the
     *                 composition of this mailbox changes.
     */
    protected function _getCacheID($date = false)
    {
        global $prefs;

        $date = $date
            ? 'D' . date('z')
            : '';

        if ($this->search) {
            return '1' . ($date ? '|' . $date : '');
        }

        $sortpref = $this->getSort(true);
        $addl = array(
            $sortpref->sortby,
            $sortpref->sortdir,
            intval($prefs->getValue('delhide'))
        );
        if ($date) {
            $addl[] = $date;
        }

        try {
            return $this->imp_imap->getCacheId($this->_mbox, $addl);
        } catch (IMP_Imap_Exception $e) {
            /* Assume an error means that a mailbox can not be trusted. */
            return strval(new Horde_Support_Randomid());
        }
    }

    /**
     * If there is information available to tell us about a prefix in front of
     * mailbox names that shouldn't be displayed to the user, then use it to
     * strip that prefix out. Additionally, translate prefix text if this
     * is a special mailbox.
     *
     * @param boolean $notranslate  Don't translate the mailbox prefix?
     *
     * @return string  The mailbox, with any prefix gone/translated.
     */
    protected function _getDisplay($notranslate = false)
    {
        global $injector;

        $cache = $injector->getInstance('IMP_Mailbox_SessionCache');
        if (!$notranslate &&
            (($display = $cache->getDisplay($this->_mbox)) !== false)) {
            return $display;
        }

        /* Handle special container mailboxes. */
        if (($elt = $this->tree_elt) && $elt->nonimap && $elt->container) {
            if ($elt->remote) {
                return _("Remote Accounts");
            } elseif ($elt->vfolder) {
                return _("Virtual Folders");
            } elseif ($elt->namespace_other) {
                return _("Other Users");
            } elseif ($elt->namespace_shared) {
                return _("Shared");
            }
        }

        /* Handle remote mailboxes. */
        if ($this->remote) {
            return $injector->getInstance('IMP_Remote')->label($this->_mbox);
        }

        $ns_info = $this->namespace_info;
        $out = $this->_mbox;

        if (!is_null($ns_info)) {
            /* Return translated namespace information. */
            if (strlen($ns_info->translation) && $this->namespace) {
                $cache->setDisplay($this->_mbox, $ns_info->translation);
                return $ns_info->translation;
            }

            /* Strip personal namespace information. */
            if ($ns_info->type === $ns_info::NS_PERSONAL) {
                $out = $ns_info->stripNamespace($this->_mbox);
            }
        }

        if ($notranslate) {
            return $out;
        }

        /* Bug #9971: Special mailboxes can be empty IMP_Mailbox objects -
         * catch this with the strlen check below. */
        foreach ($this->getSpecialMailboxes() as $key => $val) {
            switch ($key) {
            case self::SPECIAL_COMPOSETEMPLATES:
                if (strval($val) == $this->_mbox) {
                    $out = _("Templates");
                }
                break;

            case self::SPECIAL_DRAFTS:
                if (strval($val) == $this->_mbox) {
                    $out = _("Drafts");
                }
                break;

            case self::SPECIAL_SENT:
                if (in_array($this->_mbox, $val)) {
                    $out = _("Sent");
                }
                break;

            case self::SPECIAL_SPAM:
                if (strval($val) == $this->_mbox) {
                    $out = _("Spam");
                }
                break;

            case self::SPECIAL_TRASH:
                if (strval($val) == $this->_mbox) {
                    $out = _("Trash");
                }
                break;
            }
        }

        if ($this->inbox) {
            $out = _("Inbox");
        } elseif (($this->_mbox == $out) &&
            !is_null($ns_info) &&
            (strpos($out, 'INBOX' . $ns_info->delimiter) === 0)) {
            $out = substr_replace($out, _("Inbox"), 0, 5);
        }

        $cache->setDisplay($this->_mbox, $out);

        return $out;
    }

    /**
     * Return icon information.
     *
     * @return object  Object with the following properties:
     *   - alt
     *   - class
     *   - icon
     *   - iconopen
     *   - user_icon
     */
    protected function _getIcon()
    {
        global $injector;

        $info = new stdClass;
        $info->iconopen = null;
        $info->user_icon = false;

        if ($this->container) {
            /* We are dealing with folders here. */
            if ($this->is_open) {
                $info->alt = _("Opened Folder");
                $info->class = 'folderopenImg';
                $info->icon = 'folders/open.png';
            } else {
                $info->alt = _("Folder");
                $info->class = 'folderImg';
                $info->icon = 'folders/folder.png';
                $info->iconopen = Horde_Themes::img('folders/open.png');
            }
        } elseif ($this->remote_container) {
            $info->alt = _("Remote Account");
            $info->class = 'remoteImg';
            $info->icon = 'shared.png';
        } else {
            $special = $this->getSpecialMailboxes();

            switch ($this->_mbox) {
            case 'INBOX':
                $info->alt = _("Inbox");
                $info->class = 'inboxImg';
                $info->icon = 'folders/inbox.png';
                break;

            case $special[self::SPECIAL_COMPOSETEMPLATES]:
                $info->alt = ("Templates");
                $info->class = 'composetemplatesImg';
                $info->icon = 'folders/drafts.png';
                break;

            case $special[self::SPECIAL_DRAFTS]:
                $info->alt = _("Drafts");
                $info->class = 'draftsImg';
                $info->icon = 'folders/drafts.png';
                break;

            case $special[self::SPECIAL_SPAM]:
                $info->alt = _("Spam");
                $info->class = 'spamImg';
                $info->icon = 'folders/spam.png';
                break;

            case $special[self::SPECIAL_TRASH]:
                $info->alt = _("Trash");
                $info->class = 'trashImg';
                $info->icon = 'folders/trash.png';
                break;

            default:
                if (in_array($this->_mbox, $special[self::SPECIAL_SENT])) {
                    $info->alt = _("Sent");
                    $info->class = 'sentImg';
                    $info->icon = 'folders/sent.png';
                } else {
                    $info->alt = in_array($this->_mbox, $special[self::SPECIAL_USER])
                        ? $this->display
                        : _("Mailbox");
                    if ($this->is_open) {
                        $info->class = 'folderopenImg';
                        $info->icon = 'folders/open.png';
                    } else {
                        $info->class = 'folderImg';
                        $info->icon = 'folders/folder.png';
                    }
                }
                break;
            }

            /* Virtual folders. */
            if ($this->vfolder) {
                $imp_search = $injector->getInstance('IMP_Search');
                if ($imp_search->isVTrash($this->_mbox)) {
                    $info->alt = $imp_search[$this->_mbox]->label;
                    $info->class = 'trashImg';
                    $info->icon = 'folders/trash.png';
                } elseif ($imp_search->isVinbox($this->_mbox)) {
                    $info->alt = $imp_search[$this->_mbox]->label;
                    $info->class = 'inboxImg';
                    $info->icon = 'folders/inbox.png';
                }
            }
        }

        /* Overwrite the icon information now. */
        $mi = $injector->getInstance('IMP_Mailbox_SessionCache')->getIcons($this->_mbox);
        if (!empty($mi)) {
            if (isset($mi['alt'])) {
                $info->alt = $mi['alt'];
            }
            $info->icon = strval($mi['icon']);
            $info->user_icon = true;
        } elseif ($info->icon) {
            $info->icon = Horde_Themes::img($info->icon);
        }

        return $info;
    }

    /**
     * Do the necessary cleanup/cache updates when deleting mailboxes.
     *
     * @param array $deleted  The list of deleted mailboxes.
     */
    protected function _onDelete($deleted)
    {
        /* Clear the mailboxes from the sort prefs. */
        foreach ($this->get($deleted) as $val) {
            $val->setSort(null, null, true);
        }
    }

}
