<?php

define('IMP_MAILBOX_MOVE', 1);
define('IMP_MAILBOX_COPY', 2);
define('IMP_MAILBOX_DELETE', 3);
define('IMP_MAILBOX_UNDELETE', 4);
define('IMP_MAILBOX_EXPUNGE', 5);
define('IMP_MAILBOX_EMPTY', 6);
define('IMP_MAILBOX_UPDATE', 7);

/**
 * The IMP_Mailbox:: class contains all code related to handling mailbox
 * access.
 *
 * $Horde: imp/lib/Mailbox.php,v 1.76.10.3 2005/02/03 04:15:37 slusarz Exp $
 *
 * Copyright 2002-2005 Michael Slusarz <slusarz@bigworm.colorado.edu>
 *
 * See the enclosed file COPYING for license information (GPL). If you
 * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
 *
 * @author  Michael Slusarz <slusarz@bigworm.colorado.edu>
 * @version $Revision: 1.76.10.3 $
 * @since   IMP 4.0
 * @package IMP
 */
class IMP_Mailbox {

    /**
     * The current message index.
     *
     * @var integer $_index
     */
    var $_index = null;

    /**
     * The location in the sorted array we are at.
     *
     * @var integer $_arrayIndex
     */
    var $_arrayIndex = null;

    /**
     * The location of the last message we were at.
     *
     * @var integer $_lastArrayIndex
     */
    var $_lastArrayIndex = null;

    /**
     * The array of sorted indices.
     *
     * @var array $_sorted
     */
    var $_sorted = array();

    /**
     * The array of sorted mailboxes.
     *
     * @var array $_sortedMbox
     */
    var $_sortedMbox = array();

    /**
     * The threading information for the current mailbox.
     *
     * @var object IMAP_Thread $_thread
     */
    var $_thread = null;

    /**
     * Are we updating the message index?
     *
     * @var boolean $_indexset
     */
    var $_indexset = false;

    /**
     * Has a sort already been done?
     *
     * @var boolean $_sortdone
     */
    var $_sortdone = false;

    /**
     * Should we show Hide/purge deleted message links?
     *
     * @var boolean $_showdelete
     */
    var $_showdelete = false;

    /**
     * The number of new messages in the mailbox.
     *
     * @var integer $_newmsgs
     */
    var $_newmsgs = 0;

    /**
     * Using POP to access mailboxes?
     *
     * @var boolean $_usepop
     */
    var $_usepop = false;

    /**
     * The sort method for this mailbox.
     *
     * @var integer $_sortby
     */
    var $_sortby;

    /**
     * Are we hiding deleted messages?
     *
     * @var boolean $_delhide
     */
    var $_delhide;

    /**
     * Special folder flag.
     *
     * @var boolean $_specialFolder
     */
    var $_specialFolder = null;

    /**
     * Has the sorting limit been reached?
     *
     * @var boolean $_sortLimit
     */
    var $_sortLimit = false;

    /**
     * Variables relating to showing message previews.
     *
     * @var array $_previews
     */
    var $_previews = array();

    /**
     * Constructor.
     *
     * @access public
     *
     * @param optional integer $index  The index of the current message. This
     *                                 will cause IMP_Message to update the
     *                                 various message arrays after each
     *                                 action.
     */
    function IMP_Mailbox($index = null)
    {
        global $imp, $prefs;

        if (!is_null($index)) {
            $this->setNewIndex($index);
        }

        if ($imp['base_protocol'] == 'pop3') {
            $this->_usepop = true;
        }

        /* Default sort field for this mailbox. */
        $this->_sortby = $prefs->getValue('sortby');

        /* Determine whether we're hiding deleted messages; let the
           trash folder take precedence. */
        $this->_delhide = ($prefs->getValue('delhide') && !$prefs->getValue('use_trash'));
    }

    /**
     * Sets a new index.
     *
     * @access public
     *
     * @param integer $index  The index of the new current message.
     */
    function setNewIndex($index)
    {
        $this->_setIndex($index);
        $this->_setSorted();
        $this->_setArrayIndex();
    }

    /**
     * Determines if mail previews should be generated.
     *
     * @access public
     *
     * @return boolean  True if mail previews should be generated.
     */
    function showPreviews()
    {
        global $conf, $prefs;

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

        if (!$conf['mailbox']['show_preview'] ||
            !$prefs->getValue('preview_enabled')) {
            return false;
        }

        $this->_previews['unread'] = $prefs->getValue('preview_show_unread');
        $this->_previews['maxlen'] = $prefs->getValue('preview_maxlen');
        if ($this->_previews['maxlen'] != -1) {
            $this->_previews['maxlen'] = max(5, $this->_previews['maxlen'] - 3);
        }
        $this->_previews['strip'] = $prefs->getValue('preview_strip_nl');
        $this->_previews['tooltip'] = $prefs->getValue('preview_show_tooltip');

        return true;
    }

    /**
     * Build the array of message information.
     *
     * @access public
     *
     * @param integer $begin              The beginning message number.
     * @param integer $beginIndex         The beginning index.
     * @param integer $end                The ending message number.
     * @param array $sorted               The list of sorted UIDs.
     * @param optional array $sortedMbox  The list of mailboxes associated with
     *                                    UIDs. If empty, UIDS will be
     *                                    associated with the current mailbox.
     *
     * @return array  An array with information on the requested messages.
     * <pre>
     * Key: array index in original $sorted array
     * 'header'     --  Header information from imap_fetch_overview().
     * 'mbox'       --  Name of the mailbox.
     * 'preview'    --  The message preview (if the 'show_preview' preference
     *                  is active).
     * 'structure'  --  Structure of the message (if the 'show_attachments'
     *                  configuration option is active).
     * </pre>
     */
    function buildMailboxArray($begin, $beginIndex, $end, $sorted,
                               $sortedMbox = array())
    {
        global $conf, $imp;

        $this->_showdelete = false;

        $mboxes = array();
        $overview = array();
        $show_preview = $this->showPreviews();

        /* Build the list of mailboxes and messages. */
        for ($i = $begin - 1, $j = $beginIndex; $i < $end; $i++, $j++) {
            /* Make sure that the index is actually in the slice of messages
               we're looking at. If we're hiding deleted messages, for
               example, there may be gaps here. */
            if (isset($sorted[$i])) {
                if (isset($sortedMbox[$i])) {
                    $mboxname = $sortedMbox[$i];
                } else {
                    $mboxname = $imp['mailbox'];
                }

                if (!isset($mboxes[$mboxname])) {
                    $mboxes[$mboxname] = array();
                }
                $mboxes[$mboxname][$sorted[$i]] = $i;
            }
        }

        require_once IMP_BASE . '/lib/IMAP.php';
        $imp_imap = &IMP_IMAP::singleton();

        /* Retrieve information from each mailbox. */
        foreach ($mboxes as $mbox => $ids) {
            $imp_imap->changeMbox($mbox, OP_READONLY);
            $imapOverview = @imap_fetch_overview($imp['stream'], implode(',', array_keys($ids)), FT_UID);
            foreach ($imapOverview as $header) {
                $key = $ids[$header->uid];
                $overview[$key] = array();
                $overview[$key]['header'] = $header;
                if ($header->deleted) {
                    $this->_showdelete = true;
                }

                if ($conf['fetchmail']['show_account_colors']) {
                    $hdr = @imap_fetchheader($imp['stream'], $header->uid, FT_UID);
                    if (preg_match("/X-color:(.*)\n/", $hdr, $color)) {
                        $overview[$key]['color'] = trim($color[1]);
                    }
                }

                $overview[$key]['mbox'] = $mbox;

                /* Add preview information. */
                if ($show_preview &&
                    (!$this->_previews['unread'] ||
                     !$header->seen ||
                     $header->recent)) {
                    $ptext = @imap_fetchbody($imp['stream'], $header->msgno, 1);
                    $overview[$key]['structure'] = @imap_fetchstructure($imp['stream'], $header->uid, FT_UID);
                    /* Only attempt to decode if we are working with a text
                     * part. */
                    if (MIME::type($overview[$key]['structure']->type, MIME_STRING) == 'text') {
                        switch (MIME::encoding($overview[$key]['structure']->encoding, MIME_STRING)) {
                        case 'base64':
                            $ptext = base64_decode($ptext);
                            break;

                        case 'quoted-printable':
                            $ptext = quoted_printable_decode($ptext);
                            break;
                        }
                    }
                    $ptext = trim(strip_tags($ptext));
                    if (($this->_previews['maxlen'] != -1) &&
                        (String::length($ptext) > $this->_previews['maxlen'])) {
                        $ptext = String::substr($ptext, 0, $this->_previews['maxlen']) . ' ...';
                    }

                    $ptext = str_replace("\r", "\n", $ptext);
                    if ($this->_previews['strip'] &&
                        !$this->_previews['tooltip']) {
                        $ptext = preg_replace('/\n/', ' ', $ptext);
                        $ptext = preg_replace('/(\s)+/', '$1', $ptext);
                    } else {
                        $ptext = preg_replace('/\n(\s*\n)+/', "\n", $ptext);
                    }

                    $overview[$key]['preview'] = $ptext;
                } else {
                    $overview[$key]['structure'] = ($conf['mailbox']['show_attachments']) ? @imap_fetchstructure($imp['stream'], $header->uid, FT_UID) : null;
                    $overview[$key]['preview'] = null;
                }
            }
        }

        /* Sort via the sorted array index. */
        ksort($overview);

        return $overview;
    }

    /**
     * Builds the list of messages in the mailbox.
     *
     * @access public
     *
     * @return array  The sorted index.
     */
    function buildMailbox()
    {
        global $imp, $prefs;

        $this->_newmsgs = 0;

        if ($imp['mailbox'] == IMP_SEARCH_MBOX) {
            $sorted = $this->_getSearchArray();
        } else {
            $this->_getSortedIndex();
            if (($sorted = $this->getSorted()) &&
                ($prefs->getValue('nav_popup') &&
                 !Util::getFormData('no_newmail_popup'))) {
                $newQuery = 'RECENT';
                if ($this->_delhide) {
                    $newQuery .= ' UNDELETED';
                }
                if (($new = @imap_search($imp['stream'], $newQuery))) {
                    $this->_newmsgs = count($new);
                }
            }
        }

        /* Store the sorted range in IMP_Message. */
        $this->_setRange($sorted);

        return $sorted;
    }

    /**
     * The number of new messages in the mailbox (IMAP RECENT flag,
     * with UNDELETED if we're hiding deleted messages).
     *
     * @access public
     */
    function newMessageCount()
    {
        return $this->_newmsgs;
    }

    /**
     * The number of new messages in the mailbox (IMAP UNSEEN flag).
     *
     * @access public
     */
    function unseenMessageCount()
    {
        global $imp;

        $newQuery = 'UNSEEN';
        if ($this->_delhide) {
            $newQuery .= ' UNDELETED';
        }
        if (($new = @imap_search($imp['stream'], $newQuery))) {
            return count($new);
        } else {
            return false;
        }
    }

    /**
     * Should delete message links be shown? Only show if the
     * configuration requires it, or deleted messages exist in the
     * current mailbox.
     *
     * @access public
     */
    function showDeleteLinks()
    {
        global $imp, $prefs;

        if (!$prefs->getValue('use_trash') &&
            strstr($imp['protocol'], 'imap')) {
            return true;
        } else {
            return $this->_showdelete;
        }
    }

    /**
     * Returns the current message index.
     *
     * @access public
     *
     * @param optional boolean $mbox  Append the mailbox information to the
     *                                data (in IMP_Message format)?
     *
     * @return integer  The message index.
     */
    function getIndex($mbox = false)
    {
        if ($mbox) {
            $data = $this->messageIndices(0);
            return $data['index'] . IMP_IDX_SEP . $data['mailbox'];
        } else {
            return $this->_index;
        }
    }

    /**
     * Returns the current array index.
     *
     * @access public
     *
     * @return integer  The array index.
     */
    function getArrayIndex()
    {
        return $this->_arrayIndex;
    }

    /**
     * Returns the current message array index. If the array index has
     * run off the end of the message array, will return the last index.
     *
     * @access public
     *
     * @return integer  The message array index.
     */
    function getMessageIndex()
    {
        if (is_null($this->_arrayIndex)) {
            if (is_null($this->_lastArrayIndex)) {
                $index = 0;
            } else {
                $index = $this->_lastArrayIndex;
            }
        } else {
            $index = $this->_arrayIndex;
        }

        return $index + 1;
    }

    /**
     * Returns the current message count of the mailbox.
     *
     * @access public
     *
     * @return integer  The mailbox message count.
     */
    function getMessageCount()
    {
        return count($this->_sorted);
    }

    /**
     * Checks to see if the current index is valid.
     * This function is only useful if an index was passed to the constructor.
     *
     * @access public
     *
     * @return boolean  True if index is valid, false if not.
     */
    function isValidIndex()
    {
        $this->_sortIfNeeded();

        if (is_null($this->_index) || is_null($this->_arrayIndex)) {
            return false;
        } else {
            return true;
        }
    }

    /**
     * Returns the sorted list of messages.
     *
     * @access public
     *
     * @return array  The sorted list of messages.
     */
    function getSorted()
    {
        return $this->_sorted;
    }

    /**
     * Returns the sorted list of mailboxes associated with the sorted list
     * of messages.
     *
     * @access public
     *
     * @return array  The sorted list of mailboxes.
     */
    function getSortedMbox()
    {
        return $this->_sortedMbox;
    }

    /**
     * Returns information on a message offset from the current message.
     *
     * @access public
     *
     * @param integer $offset  The offset from the current message.
     *
     * @return array  'index'   -- The message index.
     *                'mailbox' -- The mailbox.
     */
    function messageIndices($offset)
    {
        global $imp;

        $return_array = array();
        $index = $this->_arrayIndex + $offset;

        /* If the offset would put us out of array index, return now. */
        if (!isset($this->_sorted[$index])) {
            return $return_array;
        }

        $return_array['index'] = $this->_sorted[$index];
        if ($imp['mailbox'] == IMP_SEARCH_MBOX) {
            $return_array['mailbox'] = $this->_sortedMbox[$index];
        } else {
            $return_array['mailbox'] = $imp['mailbox'];
        }

        return $return_array;
    }

    /**
     * Move current pointer to the next index.
     *
     * @access public
     */
    function moveNext()
    {
        if(($nextindex = $this->messageIndices(1))) {
            $this->_setIndex($nextindex['index']);
        } else {
            $this->_index = null;
        }

        $this->_setArrayIndex();
        $this->_setLastValidIndex();
    }

    /**
     * Update the current mailbox if an action has been performed on the
     * current message index.
     *
     * @access public
     *
     * @param integer $action            The action to perform.
     * @param optional boolean $success  Was the action successful?
     */
    function updateMailbox($action, $success = true)
    {
        global $imp, $prefs;

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

        switch ($action) {
        case IMP_MAILBOX_COPY:
            /* Just move to the next message. */
            $this->_incrementIndex();

            /* Determine if we need to resort. */
            $this->_sortIfNeeded();
            break;

        case IMP_MAILBOX_MOVE:
        case IMP_MAILBOX_DELETE:
            /* If we are using POP, we need to resort every time. */
            if ($success && $this->_usepop) {
                $this->_getSortedIndex();
            } elseif ($success && ($this->_delhide || $prefs->getValue('use_trash'))) {
                /* Nuke message from sorted list if sent to trash or
                   hidden. */
                $this->_removeCurrent();
            } else {
                /* Either we failed, or the message is still in the
                   mailbox and has been marked as deleted - just move
                   to the next message. */
                $this->_incrementIndex();

                /* Determine if we need to resort. */
                $this->_sortIfNeeded();
            }
            break;

        case IMP_MAILBOX_UNDELETE:
            $this->_incrementIndex();
            break;

        case IMP_MAILBOX_EXPUNGE:
        case IMP_MAILBOX_EMPTY:
        case IMP_MAILBOX_UPDATE:
            if ($imp['mailbox'] != IMP_SEARCH_MBOX) {
                $this->_getSortedIndex();
            }
            break;
        }
    }

    /**
     * Using the preferences and the current mailbox, determines the messages
     * to view on the current page.
     *
     * @access public
     *
     * @param optional integer $page   The page number currently being
     *                                 displayed.
     * @param optional integer $start  The starting message number.
     *
     * @return object stdClass  An object with the following fields:
     * <pre>
     * 'anymsg'     -  Are there any messages at all in mailbox? E.g. If
     *                 'msgcount' is 0, there may still be hidden deleted
     *                 messages.
     * 'begin'      -  The beginning message number of the page.
     * 'end'        -  The ending message number of the page.
     * 'index'      -  The index of the starting message.
     * 'msgcount'   -  The number of viewable messages in the current mailbox.
     * 'page'       -  The current page number.
     * 'pagecount'  -  The number of pages in this mailbox.
     * </pre>
     */
    function buildMailboxPage($page = 0, $start = 0)
    {
        global $imp, $prefs;

        $sorted = $this->buildMailbox();
        $msgcount = $this->getMessageCount();

        $maxmsgs = $prefs->getValue('max_msgs');
        $sortdir = $prefs->getValue('sortdir');

        $this->_determineSort();

        if ($msgcount > $maxmsgs) {
            $pageCount = ceil($msgcount / (($maxmsgs > 0) ? $maxmsgs : 20));

            /* Determine which page to display. */
            if (empty($page) || strcspn($page, '0123456789')) {
                if (!empty($start)) {
                    /* Messages set this when returning to a mailbox. */
                    $page = ceil($start / $maxmsgs);
                } else {
                    $startpage = $prefs->getValue('mailbox_start');
                    switch ($startpage) {
                    case IMP_MAILBOXSTART_FIRSTPAGE:
                        $page = 1;
                        break;

                    case IMP_MAILBOXSTART_LASTPAGE:
                        $page = $pageCount;
                        break;

                    case IMP_MAILBOXSTART_FIRSTUNSEEN:
                    case IMP_MAILBOXSTART_LASTUNSEEN:
                        if (!$this->aboveSortLimit() &&
                            ($imp['mailbox'] != IMP_SEARCH_MBOX)) {
                            /* Use imap_search() to find unread messages. */
                            $search = 'UNSEEN';
                            if ($this->_delhide) {
                                $search .= ' UNDELETED';
                            }
                            $query = @imap_search($imp['stream'], $search, SE_UID);
                            if (!empty($query)) {
                                $sortednew = array_keys(array_intersect($sorted, $query));
                                $first_new = ($startpage == IMP_MAILBOXSTART_FIRSTUNSEEN) ?
                                    array_shift($sortednew) :
                                    array_pop($sortednew);
                                $page = ceil(($first_new + 1) / $maxmsgs);
                            }
                        }
                        break;
                    }
                }

                if (empty($page)) {
                    $page = $sortdir ? 1 : $pageCount;
                }
            }

            /* Make sure we're not past the end or before the beginning, and
               that we have an integer value. */
            $page = intval($page);
            if ($page > $pageCount) {
                $page = $pageCount;
            } elseif ($page < 1) {
                $page = 1;
            }

            $begin = intval((($page - 1) * $maxmsgs) + 1);
            $end = $begin + $maxmsgs - 1;
            if ($end > $msgcount) {
                $end = $msgcount;
            }
        } else {
            $begin = 1;
            $end = $msgcount;
            $page = 1;
            $pageCount = 1;
        }

        if ($imp['mailbox'] == IMP_SEARCH_MBOX) {
            $beginIndex = $begin - 1;
        } else {
            $beginIndex = $this->getArrayIndex();
        }

        /* If there are no viewable messages, check for deleted messages in
           the mailbox. */
        $anymsg = true;
        if ($msgcount == 0) {
            $status = @imap_status($imp['stream'], IMP::serverString($imp['mailbox']), SA_MESSAGES);
            if ($status->messages == 0) {
                $anymsg = false;
            }
        }

        $ob = new stdClass;
        $ob->anymsg    = $anymsg;
        $ob->begin     = $begin;
        $ob->end       = $end;
        $ob->index     = $beginIndex;
        $ob->msgcount  = $msgcount;
        $ob->page      = $page;
        $ob->pagecount = $pageCount;

        return $ob;
    }

    /**
     * Return the current sort method used.
     *
     * @access public
     *
     * @return integer  The sort method.
     */
    function sortby()
    {
        return $this->_sortby;
    }

    /**
     * Determine whether the sort limit has been reached.
     *
     * @access public
     *
     * @return boolean  Has the sort limit been reached?
     */
    function aboveSortLimit()
    {
        return $this->_sortLimit;
    }

    /**
     * Is this a 'special' folder (e.g. 'drafts' or 'sent-mail' folder)?
     *
     * @access public
     *
     * @return boolean  Is this a 'special' folder?
     */
    function isSpecialFolder()
    {
        if (is_null($this->_specialFolder)) {
            global $imp, $prefs;

            /* Get the identities. */
            require_once 'Horde/Identity.php';
            $identity = &Identity::singleton(array('imp', 'imp'));

            $preamble = IMP::preambleString();
            $this->_specialFolder = (($imp['mailbox'] == $preamble . $prefs->getValue('drafts_folder')) || (in_array($imp['mailbox'], $identity->getAllSentmailFolders())));
        }

        return $this->_specialFolder;
    }

    /**
     * Sets the private index variable.
     *
     * @access private
     *
     * @param optional integer $index  The index of the current message.
     *                                 If empty, will update the current index
     *                                 to the current array index value.
     */
    function _setIndex($index = null)
    {
        if (empty($index)) {
            if (!is_null($this->_arrayIndex)) {
                if (!empty($this->_sorted[$this->_arrayIndex])) {
                    $this->_index = $this->_sorted[$this->_arrayIndex];
                } else {
                    $this->_index = null;
                }
            }
        } else {
            if (strstr($index, IMP_IDX_SEP)) {
                list($this->_index,) = explode(IMP_IDX_SEP, $index);
            } else {
                $this->_index = $index;
            }
        }
        $this->_indexset = true;
    }

    /**
     * Updates the sorted messages array.
     *
     * @access private
     */
    function _setSorted()
    {
        global $imp;

        $this->_sorted = array();
        $this->_sortedMbox = array();

        if (empty($imp['msgl'])) {
            $this->_sorted = array();
        } else {
            $msglist = explode(IMP_MSG_SEP, $imp['msgl']);
            if ($imp['mailbox'] == IMP_SEARCH_MBOX) {
                foreach ($msglist as $val) {
                    list($idx, $mbox) = explode(IMP_IDX_SEP, $val);
                    $this->_sorted[] = $idx;
                    $this->_sortedMbox[] = $mbox;
                }
            } else {
                $this->_sorted = $msglist;
            }
        }
    }

    /**
     * Updates the message array index.
     *
     * @access private
     *
     * @param optional $offset  The number of messages to increase array index
     *                          by. If null, sets array index to the value
     *                          of the current message index, if any.
     */
    function _setArrayIndex($offset = null)
    {
        global $imp;

        if (is_null($this->_index)) {
            $this->_arrayIndex = null;
        } else {
            if (!is_null($offset)) {
                $this->_lastArrayIndex = $this->_arrayIndex;
                $this->_arrayIndex += $offset;
                if (empty($this->_sorted[$this->_arrayIndex])) {
                    $this->_arrayIndex = null;
                }
                $this->_setIndex();
            } else {
                if ($imp['mailbox'] == IMP_SEARCH_MBOX) {
                    /* Need to compare both mbox name and message UID to
                       obtain the correct array index since there may be
                       duplicate UIDs. */
                    $this->_arrayIndex = null;
                    foreach (array_keys($this->_sorted, $this->_index) as $key) {
                        if ($this->_sortedMbox[$key] == $imp['thismailbox']) {
                            $this->_arrayIndex = $key;
                            break;
                        }
                    }
                } else {
                    /* array_search() returns false on no result. We will
                       set an unsuccessful result to NULL. */
                    if (($this->_arrayIndex = array_search($this->_index, $this->_sorted)) === false) {
                        $this->_arrayIndex = null;
                    }
                }
                $this->_lastArrayIndex = $this->_arrayIndex;
            }
        }
    }

    /**
     * Get the sorted list of messages for a mailbox. This function
     * correctly handles sort preferences, deletion preferences
     * (e.g. hide deleted messages), and protocol (e.g. 'pop3'
     * vs. 'imap').
     *
     * The following variables are updated by this method:
     *   $imp['msgl']
     *
     * @access private
     */
    function _getSortedIndex()
    {
        global $imp, $prefs;

        $sortdir = $prefs->getValue('sortdir');
        $sorted = array();

        $this->_determineSort(true);

        if ($this->_sortby == SORTTHREAD) {
            $sorted = $this->_threadSort((bool)$sortdir);
        } else {
            $cmd = array($imp['stream'], $this->_sortby, $sortdir, SE_UID);
            if (!$this->_usepop && $this->_delhide) {
                $cmd[] = 'UNDELETED';
            }
            $sorted = @call_user_func_array('imap_sort', $cmd);
        }

        $this->_setRange($sorted);
    }

    /**
     * Sets the current page of messages based on the current index.
     * The following variables are updated by this method:
     *   $imp['msgl']
     *
     * @access private
     *
     * @param array $arr  The array of message indices.
     */
    function _setRange($arr)
    {
        global $imp;

        $imp['msgl'] = implode(IMP_MSG_SEP, $arr);

        /* Set the new sorted message list. */
        $this->_setSorted();

        /* Update the internal indices, if necessary. */
        if ($this->_indexset) {
            /* Update the current array index to its new position in
               the message array. */
            $this->_setArrayIndex();
        }

        /* Set the _sortdone flag. */
        $this->_sortdone = true;
    }

    /**
     * Perform re-threading sort.
     *
     * @access private
     *
     * @param boolean $new   True for newest messages first, false for oldest
     *                       messages first.
     *
     * @return array  The sorted list of messages.
     */
    function _threadSort($new)
    {
        global $imp;

        $ref_array = @imap_thread($imp['stream'], SE_UID);
        if (!is_array($ref_array)) {
            require_once IMP_BASE . '/lib/IMAP.php';
            $imp_imap = &IMP_IMAP::singleton();
            $imp_imap->reopenIMAPStream();
            $ref_array = @imap_thread($imp['stream'], SE_UID);
            if (!is_array($ref_array)) {
                return array();
            }
        }

        require_once 'Horde/IMAP/Thread.php';
        $delete_list = ($this->_delhide) ? @imap_search($imp['stream'], 'DELETED', SE_UID) : array();
        $this->_thread = &new IMAP_Thread($ref_array, $delete_list);
        return $this->_thread->messageList($new);
    }

    /**
     * Get the IMAP_Thread object for the current mailbox.
     *
     * @access public
     *
     * @return object IMAP_Thread  The IMAP_Thread object for the current
     *                             mailbox.
     */
    function getThreadOb()
    {
        if (is_null($this->_thread)) {
            $this->_threadSort(false);
        }
        return $this->_thread;
    }

    /**
     * Determines if a resort is needed, and, if necessary, performs
     * the resort.
     *
     * The sorted range needs to be updated in the following cases:
     *   + Indexes are being tracked
     *   + There is a valid array index
     *   + This is not a search mailbox
     *   + The UIDs may no longer be valid (Optional)
     *   + This is the last message in the mailbox
     *   + A sort has not already occurred by some method in this class
     *
     * @access private
     *
     * @param optional boolean $uidcheck  Whether to check imap_status()
     *                                    results to determine if a resort
     *                                    is needed.
     */
    function _sortIfNeeded($uidcheck = false)
    {
        global $imp;

        /* If array index is null, we have reached the beginning/end
           of the mailbox so we shouldn't sort anything. There is also
           no need to sort the search results. */
        if ($this->_indexset &&
            !is_null($this->_arrayIndex) &&
            ($imp['mailbox'] != IMP_SEARCH_MBOX)) {
            if ($uidcheck) {
                /* Intialize IMAP_Cache object. */
                require_once 'Horde/IMAP/Cache.php';
                $imap_cache = &IMAP_Cache::singleton();

                /* Check to see if mailbox has changed since we last
                   accessed it. */
                if (!$imap_cache->checkCache($imp['stream'], IMP::serverString($imp['mailbox']), true)) {;
                    $this->_getSortedIndex();
                }
            }

            if (!$this->messageIndices(1)) {
                $this->_getSortedIndex();
            }
        }
    }

    /**
     * Returns the current sorted array without the current index message.
     *
     * @access private
     *
     * @return array  The sorted array without the current index in it.
     */
    function _removeCurrent()
    {
        global $imp;

        /* We need to set index to the next index (if any) in the currently
           sorted list. */
        if (($nextindex = $this->messageIndices(1))) {
            $this->_setIndex($nextindex['index']);
        } else {
            $this->_index = null;
        }

        /* Remove the current entry and recalculate the range. */
        unset($this->_sorted[$this->_arrayIndex]);
        if ($imp['mailbox'] == IMP_SEARCH_MBOX) {
            unset($this->_sortedMbox[$this->_arrayIndex]);
            $arr = array_map(create_function('$a, $b', 'return $a . IMP_IDX_SEP . $b;'), array_values($this->_sorted), array_values($this->_sortedMbox));
        } else {
            $arr = array_values($this->_sorted);
        }

        $this->_setRange($arr);
        $this->_setArrayIndex();
        $this->_setLastValidIndex();
    }

    /**
     * Increment the internal indices by one message.
     *
     * @access private
     */
    function _incrementIndex()
    {
        $this->_setArrayIndex(1);
        $this->_setIndex();
    }

    /**
     * If array index is null, we have deleted the last message. If this
     * is the case, set index to the last valid index.
     *
     * @access private
     */
    function _setLastValidIndex()
    {
        if (is_null($this->_arrayIndex) && !empty($this->_lastArrayIndex)) {
            $this->_lastArrayIndex--;
        }
    }

    /**
     * Gets the list of indexes/folders for search results.
     *
     * @access private
     *
     * @return array  The sorted index array.
     */
    function _getSearchArray()
    {
        include_once IMP_BASE . '/lib/Search.php';

        $sorted = IMP_Search::runSearch();

        /* Return to the search page if this is not a virtual folder. */
        if (empty($sorted) && empty($_SESSION['imp']['search']['vfolder_id'])) {
            $GLOBALS['notification']->push(_("No messages matched your search."), 'horde.warning');
            header('Location: ' . Util::addParameter(Horde::applicationUrl('search.php', true), 'no_match', 1));
            exit;
        }

        return $sorted;
    }

    /**
     * Determine the correct sort method for this mailbox.
     *
     * @access private
     *
     * @param optional boolean  Run the sort_limit check?
     */
    function _determineSort($check = false)
    {
        global $conf, $imp;

        if ($check &&
            ($this->_sortby != SORTARRIVAL) &&
            !empty($conf['server']['sort_limit']) &&
            (@imap_num_msg($imp['stream']) > $conf['server']['sort_limit'])) {
            $this->_sortLimit = true;
            $this->_sortby = SORTARRIVAL;
        } else {
            if ($check) {
                $this->_sortLimit = false;
            }
            if ($this->isSpecialFolder()) {
                /* If the preference is to sort by From Address, when we are
                   in the Drafts or Sent folders, sort by To Address. */
                if ($this->_sortby == SORTFROM) {
                    $this->_sortby = SORTTO;
                }
            } elseif ($this->_sortby == SORTTO) {
                $this->_sortby = SORTFROM;
            }
        }
    }

}
