<?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.43 2003/07/15 07:27:04 slusarz Exp $
 *
 * Copyright 2002-2003 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.43 $
 * @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 indent level for each message (if messages sorted by thread).
     *
     * @var array $_threadIndent
     */
    var $_threadIndent = array();

    /**
     * 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;

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

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

    /**
     * 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');
    }

    /**
     * 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();
    }

    /**
     * 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.
     * '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();

        /* 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['mailbox']['show_attachments']) {
                    $overview[$key]['structure'] = @imap_fetchstructure($imp['stream'], $header->uid, FT_UID);
                } else {
                    $overview[$key]['structure'] = null;
                }
                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;
            }
        }

        /* 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') &&
                 !Horde::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.
     *
     * @access public
     */
    function newMessageCount()
    {
        return $this->_newmsgs;
    }

    /**
     * Should delete message links be shown? Only show if the configuration
     * requires it, or there 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;
        }
    }

    /**
     * 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;
            }
        }
    }

    /**
     * Returns the current message index.
     *
     * @access public
     *
     * @return integer  The message index.
     */
    function getIndex()
    {
        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;
        }
    }

    /**
     * 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($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);
    }

    /**
     * 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;
    }

    /**
     * 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 $direction    1 if newest first, 0 if newest last.
     *
     * @return array  The sorted list of messages.
     */
    function _threadSort($direction)
    {
        global $prefs, $imp;

        $sorted = array();
        $branchsOpen = array();

        $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();
            }
        }
        foreach ($ref_array as $key => $val) {
            if (strpos($key, '.num')) {
                $branchsOpen[substr($key, 0, -4)] = 'Open';

                if ($val > 0) {
                    $this->_threadIndent[$val] = count($branchsOpen);

                    if ($this->_delhide()) {
                        $overview = @imap_fetch_overview($imp['stream'], $val, FT_UID);
                        if (is_array($overview) && ($overview[0]->deleted == 0)) {
                            $sorted[] = $val;
                        }
                    } else {
                        $sorted[] = $val;
                    }
                }
            }
            if (strpos($key, '.branch')) {
                unset($branchsOpen[substr($key, 0, -7)]);
            }
        }
        if ($direction === '1') {
            $sorted = array_reverse($sorted);
        }

        return $sorted;
    }

    /**
     * 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_BASE . '/lib/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();
            }
        }
    }

    /**
     * Gets the indention level for a message ID.
     *
     * @access public
     *
     * @param integer $msgid  The message ID.
     *
     * @return mixed  Returns the thread indent level if $msgid found.
     *                Returns false on failure.
     */
    function getThreadIndent($msgid)
    {
        if (isset($this->_threadIndent[$msgid])) {
            return $this->_threadIndent[$msgid];
        } else {
            return false;
        }
    }

    /**
     * Returns information on a message offset from the current message.
     *
     * @access public
     *
     * @param integer  The offset from the current message.
     *
     * @return array  'index'        --  The message index.
     *                'thismailbox'  --  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['thismailbox'] = $this->_sortedMbox[$index];
        } else {
            $return_array['thismailbox'] = $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();
    }

    /**
     * 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();
    }

    /**
     * Hide deleted can be set even if we are using a trash folder -
     * the trash folder should take preference.
     *
     * @access private
     */
    function _delhide()
    {
        global $prefs;

        if ($prefs->getValue('delhide') && !$prefs->getValue('use_trash')) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * Update the current mailbox.
     *
     * @access public
     *
     * @param integer $action            The action to perform.
     * @param optional mixed $indices    The indices the action is being
     *                                   performed on.
     * @param optional boolean $success  Was the action successful?
     */
    function updateMailbox($action, $indices = null, $success = true)
    {
        global $prefs;

        if (!is_array($indices)) {
            $indices = array($indices);
        }

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

        // TODO: Use indices.
        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:
            /* If we are hiding deleted messages, we need to resort every
               time. */
            if ($success && $this->_delhide()) {
                $this->_getSortedIndex();
            } elseif ($success && $prefs->getValue('use_trash')) {
                /* Nuke message from sorted list if sent to trash. */
                $this->_removeCurrent();
            } else {
                /* An unsuccessful move - just move to the next message. */
                $this->_incrementIndex();

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

        case IMP_MAILBOX_DELETE:
            /* POP3 has already taken care of expunging the mailbox to
               make the delete stick; now we just need to recalculate
               the message list. _getSortedIndex() will ensure that
               IMAP servers hiding deleted messages will skip the next
               message. */
            if ($success && ($this->_delhide() || $this->_usepop)) {
                $this->_incrementIndex();
                $this->_setLastValidIndex();
            } elseif ($success && $prefs->getValue('use_trash')) {
                /* Nuke the message we've just deleted from the sorted
                   index. */
                $this->_removeCurrent();
            } else {
                /* The message has been marked as deleted so we should
                   move to the next one -OR- if delete unsuccessful,
                   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:
            $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');

        $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->_sortby != SORTTHREAD) &&
                            !$this->aboveSortLimit() &&
                            ($imp['mailbox'] != IMP_SEARCH_MBOX) ) {
                            /* Use imap_sort() to find unread messages. */
                            $search = 'UNSEEN';
                            if ($prefs->getValue('delhide')) {
                                $search .= ' UNDELETED';
                            }
                            $query = imap_sort($imp['stream'], $this->_sortby, $prefs->getValue('sortdir'), SE_UID, $search);
                            if (!empty($query)) {
                                $key = ($startpage == IMP_MAILBOXSTART_FIRSTUNSEEN) ? reset($query) : end($query);
                                $first_new = array_search($key, $sorted) + 1;
                                $page = ceil($first_new / $maxmsgs);
                            }
                        }
                        break;
                    }
                }

                if (empty($page)) {
                    $page = $prefs->getValue('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;
    }

    /**
     * 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();

        if (empty($sorted)) {
            global $notification;
            $notification->push(_("No messages matched your search."), 'horde.warning');
            header('Location: ' . Horde::applicationUrl('search.php', true));
            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;
            }
        }
    }

    /**
     * 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_BASE . '/lib/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;
    }

}
