<?php
/**
 * Copyright 2000-2001 Chris Hyde <chris@jeks.net>
 * Copyright 2000-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 2000-2001 Chris Hyde
 * @copyright 2000-2017 Horde LLC
 * @license   http://www.horde.org/licenses/gpl GPL
 * @package   IMP
 */

/**
 * This class contains all functions related to handling messages within IMP.
 * Actions such as moving, copying, and deleting messages are handled in here
 * so that code need not be repeated between mailbox, message, and other
 * pages.
 *
 * @author    Chris Hyde <chris@jeks.net>
 * @author    Chuck Hagenbuch <chuck@horde.org>
 * @author    Michael Slusarz <slusarz@horde.org>
 * @category  Horde
 * @copyright 2000-2001 Chris Hyde
 * @copyright 2000-2017 Horde LLC
 * @license   http://www.horde.org/licenses/gpl GPL
 * @package   IMP
 */
class IMP_Message
{
    /**
     * Copies or moves a list of messages to a new mailbox.
     * Handles search and Trash mailboxes.
     * Also handles moves to the tasklist and/or notepad applications.
     *
     * @param string $targetMbox    The mailbox to move/copy messages to
     *                              (UTF-8).
     * @param string $action        Either 'copy' or 'move'.
     * @param IMP_Indices $indices  An indices object.
     * @param array $opts           Additional options:
     *   - create: (boolean) Should the target mailbox be created?
     *             DEFAULT: false
     *   - mailboxob: (IMP_Mailbox_List) Update this mailbox object.
     *                DEFAULT: No update.
     *
     * @return boolean  True if successful, false if not.
     */
    public function copy($targetMbox, $action, IMP_Indices $indices,
                         array $opts = array())
    {
        global $conf, $notification;

        if (!count($indices)) {
            return false;
        }

        $targetMbox = IMP_Mailbox::get($targetMbox);

        /* If the target is a tasklist, handle the move/copy specially. */
        if ($conf['tasklist']['use_tasklist'] &&
            (strpos($targetMbox, IMP::TASKLIST_EDIT) === 0)) {
            $this->_createTasksOrNotes(str_replace(IMP::TASKLIST_EDIT, '', $targetMbox), $action, $indices, 'task');
            return true;
        }

        /* If the target is a notepad, handle the move/copy specially. */
        if ($conf['notepad']['use_notepad'] &&
            (strpos($targetMbox, IMP::NOTEPAD_EDIT) === 0)) {
            $this->_createTasksOrNotes(str_replace(IMP::NOTEPAD_EDIT, '', $targetMbox), $action, $indices, 'note');
            return true;
        }

        if (!empty($opts['create']) && !$targetMbox->create()) {
            return false;
        }

        $imap_move = false;
        $return_value = true;

        switch ($action) {
        case 'move':
            $imap_move = true;
            $message = _("There was an error moving messages from \"%s\" to \"%s\". This is what the server said");
            break;

        case 'copy':
            $message = _("There was an error copying messages from \"%s\" to \"%s\". This is what the server said");
            break;
        }

        foreach ($indices as $ob) {
            try {
                if ($targetMbox->readonly) {
                    throw new IMP_Exception(_("The target directory is read-only."));
                }

                if (($action == 'move') && $ob->mbox->readonly) {
                    throw new IMP_Exception(_("The source directory is read-only."));
                }

                $ob->mbox->uidvalid;

                /* Attempt to copy/move messages to new mailbox. */
                $imp_imap = $ob->mbox->imp_imap;
                $imp_imap->copy($ob->mbox, $targetMbox, array(
                    'ids' => $imp_imap->getIdsOb($ob->uids),
                    'move' => $imap_move
                ));

                if (($action == 'move') &&
                    !empty($opts['mailboxob']) &&
                    $opts['mailboxob']->isBuilt()) {
                    $opts['mailboxob']->removeMsgs($ob->mbox->getIndicesOb($ob->uids));
                }
            } catch (Exception $e) {
                $error_msg = sprintf($message, $ob->mbox->display, $targetMbox->display) . ': ' . $e->getMessage();
                if ($e instanceof IMP_Imap_Exception) {
                    $e->notify($error_msg);
                } else {
                    $notification->push($error_msg, 'horde.error');
                }
                $return_value = false;
            }
        }

        return $return_value;
    }

    /**
     * Deletes a list of messages.
     * Handles search and Trash mailboxes.
     *
     * @param IMP_Indices $indices  An indices object.
     * @param array $opts           Additional options:
     *   - keeplog: (boolean) Should any history information of the message be
     *              kept?
     *   - mailboxob: (IMP_Mailbox_List) Update this mailbox object.
     *                DEFAULT: No update.
     *   - nuke: (boolean) Override user preferences and nuke (i.e.
     *           permanently delete) the messages instead?
     *
     * @return integer|boolean  The number of messages deleted if successful,
     *                          false if not.
     */
    public function delete(IMP_Indices $indices, array $opts = array())
    {
        global $injector, $notification, $prefs;

        if (!count($indices)) {
            return false;
        }

        $trash = IMP_Mailbox::getPref(IMP_Mailbox::MBOX_TRASH);
        $use_trash = $prefs->getValue('use_trash');
        if ($use_trash && !$trash) {
            $notification->push(_("Cannot move messages to Trash - no Trash mailbox set in preferences."), 'horde.error');
            return false;
        }

        $ajax_queue = $injector->getInstance('IMP_Ajax_Queue');
        $maillog = empty($opts['keeplog'])
            ? $injector->getInstance('IMP_Maillog')
            : null;
        $return_value = 0;

        /* Check for Trash mailbox. */
        $no_expunge = $use_trash_mbox = $use_vtrash = false;
        if ($use_trash &&
            empty($opts['nuke']) &&
            $injector->getInstance('IMP_Factory_Imap')->create()->access(IMP_Imap::ACCESS_TRASH)) {
            $use_vtrash = $trash->vtrash;
            $use_trash_mbox = !$use_vtrash;
        }

        /* Check whether we are marking messages as seen.
         * If using virtual trash, we must mark the message as seen or else it
         * will appear as an 'unseen' message for purposes of new message
         * counts. */
        $mark_seen = empty($opts['nuke']) &&
                     ($use_vtrash || $prefs->getValue('delete_mark_seen'));

        if ($use_trash_mbox && !$trash->create()) {
            /* If trash mailbox could not be created, just mark message as
             * deleted. */
            $no_expunge = true;
            $return_value = $use_trash_mbox = false;
        }

        foreach ($indices as $ob) {
            try {
                if (!$ob->mbox->access_deletemsgs) {
                    throw new IMP_Exception(_("This mailbox is read-only."));
                }

                $ob->mbox->uidvalid;
            } catch (IMP_Exception $e) {
                $notification->push(sprintf(_("There was an error deleting messages from the mailbox \"%s\"."), $ob->mbox->display) . ' ' . $e->getMessage(), 'horde.error');
                $return_value = false;
                continue;
            }

            $imp_indices = $ob->mbox->getIndicesOb($ob->uids);
            if ($return_value !== false) {
                $return_value += count($ob->uids);
            }

            $imp_imap = $ob->mbox->imp_imap;
            $ids_ob = $imp_imap->getIdsOb($ob->uids);

            /* Trash is only valid for IMAP mailboxes. */
            if ($use_trash_mbox &&
                ($ob->mbox != $trash) &&
                /* TODO(?): Don't use Trash mailbox for remote accounts. */
                !$ob->mbox->remote_mbox) {
                if ($ob->mbox->access_expunge) {
                    try {
                        if ($mark_seen) {
                            $imp_imap->store($ob->mbox, array(
                                'add' => array(
                                    Horde_Imap_Client::FLAG_SEEN
                                ),
                                'ids' => $ids_ob
                            ));
                        }

                        $imp_imap->copy($ob->mbox, $trash, array(
                            'ids' => $ids_ob,
                            'move' => true
                        ));

                        if (!empty($opts['mailboxob']) &&
                            $opts['mailboxob']->isBuilt()) {
                            $opts['mailboxob']->removeMsgs($imp_indices);
                        }
                    } catch (IMP_Imap_Exception $e) {
                        if ($e->getCode() == $e::OVERQUOTA) {
                            $notification->push(_("You are over your quota, so your messages will be permanently deleted instead of moved to the Trash mailbox."), 'horde.warning');
                            $opts['nuke'] = true;
                            return $this->delete(new IMP_Indices($ob->mbox, $ob->uids), $opts);
                        }

                        return false;
                    }
                }
            } else {
                /* Delete message logs now. This may result in loss of message
                 * log data for messages that might not be deleted - i.e. if
                 * an error occurs. But 1) the user has already indicated they
                 * don't care about this data and 2) message IDs (used by some
                 * maillog backends) won't be available after deletion. */
                if ($maillog) {
                    $maillog->deleteLog(new IMP_Maillog_Messages($ob->mbox, $ids_ob));
                }

                /* Delete the messages. */
                $expunge_now = false;
                $del_flags = array(Horde_Imap_Client::FLAG_DELETED);

                if (!$use_vtrash &&
                    (!$imp_imap->access(IMP_Imap::ACCESS_TRASH) ||
                     !empty($opts['nuke']) ||
                     ($use_trash &&
                      ($ob->mbox == $trash) || $ob->mbox->remote_mbox))) {
                    /* Purge messages immediately. */
                    $expunge_now = !$no_expunge;
                } elseif ($mark_seen) {
                    $del_flags[] = Horde_Imap_Client::FLAG_SEEN;
                }

                try {
                    $imp_imap->store($ob->mbox, array(
                        'add' => $del_flags,
                        'ids' => $ids_ob
                    ));
                    if ($expunge_now) {
                        $this->expungeMailbox(
                            $imp_indices->indices(),
                            array(
                                'mailboxob' => empty($opts['mailboxob']) ? null : $opts['mailboxob']
                            )
                        );
                    } elseif (!empty($opts['mailboxob']) &&
                              $opts['mailboxob']->isBuilt() &&
                              $ob->mbox->hideDeletedMsgs()) {
                        $opts['mailboxob']->removeMsgs($imp_indices);
                    } else {
                        $ajax_queue->flag($del_flags, true, new IMP_Indices($ob->mbox, $ids_ob));
                    }
                } catch (IMP_Imap_Exception $e) {}
            }
        }

        return $return_value;
    }

    /**
     * Undeletes a list of messages.
     * Handles search mailboxes.
     * This function works with IMAP only, not POP3.
     *
     * @param IMP_Indices $indices  An indices object.
     *
     * @return boolean  True if successful, false if not.
     */
    public function undelete(IMP_Indices $indices)
    {
        return $this->flag(array(
            'remove' => array(Horde_Imap_Client::FLAG_DELETED)
        ), $indices);
    }

    /**
     * Copies or moves a list of messages to a tasklist or notepad.
     * Handles search and Trash mailboxes.
     *
     * @param string $list          The list in which the task or note will be
     *                              created.
     * @param string $action        Either 'copy' or 'move'.
     * @param IMP_Indices $indices  An indices object.
     * @param string $type          The object type to create ('note' or
     *                              'task').
     */
    protected function _createTasksOrNotes($list, $action,
                                           IMP_Indices $indices, $type)
    {
        global $injector, $registry, $notification;

        foreach ($indices as $ob) {
            foreach ($ob->uids as $uid) {
                /* Fetch the message contents. */
                $imp_contents = $injector->getInstance('IMP_Factory_Contents')->create($ob->mbox->getIndicesOb($uid));

                /* Fetch the message headers. */
                $imp_headers = $imp_contents->getHeader();
                $subject = $imp_headers->getValue('subject');

                /* Re-flow the message for prettier formatting. */
                $body_part = $imp_contents->getMIMEPart($imp_contents->findBody());
                $flowed = new Horde_Text_Flowed($body_part->getContents());
                if ($body_part->getContentTypeParameter('delsp') == 'yes') {
                    $flowed->setDelSp(true);
                }
                $body = $flowed->toFlowed(false);

                /* Convert to current charset */
                /* TODO: When Horde_Icalendar supports setting of charsets
                 * we need to set it there instead of relying on the fact
                 * that both Nag and IMP use the same charset. */
                $body = Horde_String::convertCharset($body, $body_part->getCharset(), 'UTF-8');

                /* Create a new iCalendar. */
                $vCal = new Horde_Icalendar();
                $vCal->setAttribute('PRODID', '-//The Horde Project//IMP ' . $registry->getVersion() . '//EN');
                $vCal->setAttribute('METHOD', 'PUBLISH');

                switch ($type) {
                case 'task':
                    /* Create a new vTodo object using this message's
                     * contents. */
                    $vTodo = Horde_Icalendar::newComponent('vtodo', $vCal);
                    $vTodo->setAttribute('SUMMARY', $subject);
                    $vTodo->setAttribute('DESCRIPTION', $body);
                    $vTodo->setAttribute('PRIORITY', '3');

                    /* Get the list of editable tasklists. */
                    try {
                        $lists = $registry->call('tasks/listTasklists', array(false, Horde_Perms::EDIT));
                    } catch (Horde_Exception $e) {
                        $lists = null;
                        $notification->push($e);
                    }

                    /* Attempt to add the new vTodo item to the requested
                     * tasklist. */
                    try {
                        $res = $registry->call('tasks/import', array($vTodo, 'text/calendar', $list));
                    } catch (Horde_Exception $e) {
                        $res = null;
                        $notification->push($e);
                    }
                    break;

                case 'note':
                    /* Create a new vNote object using this message's
                     * contents. */
                    $vNote = Horde_Icalendar::newComponent('vnote', $vCal);
                    $vNote->setAttribute('BODY', $subject . "\n". $body);

                    /* Get the list of editable notepads. */
                    try {
                        $lists = $registry->call('notes/listNotepads', array(false, Horde_Perms::EDIT));
                    } catch (Horde_Exception $e) {
                        $lists = null;
                        $notification->push($e);
                    }

                    /* Attempt to add the new vNote item to the requested
                     * notepad. */
                    try {
                        $res = $registry->call('notes/import', array($vNote, 'text/x-vnote', $list));
                    } catch (Horde_Exception $e) {
                        $res = null;
                        $notification->push($e);
                    }
                    break;
                }

                if (!is_null($res)) {
                    if (!$res) {
                        switch ($type) {
                        case 'task':
                            $notification->push(_("An unknown error occured while creating the new task."), 'horde.error');
                            break;

                        case 'note':
                            $notification->push(_("An unknown error occured while creating the new note."), 'horde.error');
                            break;
                        }
                    } elseif (!is_null($lists)) {
                        $name = '"' . htmlspecialchars($subject) . '"';

                        /* Attempt to convert the object name into a
                         * hyperlink. */
                        try {
                            switch ($type) {
                            case 'task':
                                $link = $registry->link('tasks/show', array('uid' => $res));
                                break;

                            case 'note':
                                $link = $registry->hasMethod('notes/show')
                                    ? $registry->link('notes/show', array('uid' => $res))
                                    : false;
                                break;
                            }

                            if ($link) {
                                $name = sprintf('<a href="%s">%s</a>', Horde::url($link), $name);
                            }

                            $notification->push(sprintf(_("%s was successfully added to \"%s\"."), $name, htmlspecialchars($lists[$list]->get('name'))), 'horde.success', array('content.raw'));
                        } catch (Horde_Exception $e) {}
                    }
                }
            }
        }

        /* Delete the original messages if this is a "move" operation. */
        if ($action == 'move') {
            $this->delete($indices);
        }
    }

    /**
     * Strips one or all MIME parts out of a message.
     * Handles search mailboxes.
     *
     * @param IMP_Indices $indices  An indices object.
     * @param string $partid        The MIME ID of the part to strip. All
     *                              parts are stripped if null.
     * @param array $opts           Additional options:
     *   - mailboxob: (IMP_Mailbox_List) Update this mailbox object.
     *                DEFAULT: No update.
     *
     * @return IMP_Indices  Returns the new indices object.
     * @throws IMP_Exception
     */
    public function stripPart(IMP_Indices $indices, $partid = null,
                              array $opts = array())
    {
        global $injector;

        list($mbox, $uid) = $indices->getSingle();
        if (!$uid) {
            return;
        }

        if ($mbox->readonly) {
            throw new IMP_Exception(_("Cannot strip the MIME part as the mailbox is read-only."));
        }

        $uidvalidity = $mbox->uidvalid;

        $contents = $injector->getInstance('IMP_Factory_Contents')->create($indices);
        $message = $contents->getMIMEMessage();
        $boundary = trim($message->getContentTypeParameter('boundary'), '"');

        $url = new Horde_Imap_Client_Url();
        $url->mailbox = $mbox;
        $url->uid = $uid;
        $url->uidvalidity = $uidvalidity;

        $imp_imap = $mbox->imp_imap;

        /* Always add the header to output. */
        $url->section = 'HEADER';
        $parts = array(
            array(
                't' => 'url',
                'v' => strval($url)
            )
        );

        for ($id = 1; ; ++$id) {
            $part = $message->getPart($id);
            if (!$part) {
                break;
            }

            $parts[] = array(
                't' => 'text',
                'v' => "\r\n--" . $boundary . "\r\n"
            );

            if (($id != 1) && is_null($partid) || ($id == $partid)) {
                $newPart = new Horde_Mime_Part();
                $newPart->setType('text/plain');

                /* Need to make sure all text is in the correct charset. */
                $newPart->setCharset('UTF-8');
                $newPart->setContents(sprintf(_("[Attachment stripped: Original attachment type: %s, name: %s]"), $part->getType(), $contents->getPartName($part)));
                $newPart->setDisposition('attachment');

                $parts[] = array(
                    't' => 'text',
                    'v' => $newPart->toString(array(
                        'canonical' => true,
                        'headers' => true,
                        'stream' => true
                    ))
                );
            } else {
                $url->section = $id . '.MIME';
                $parts[] = array(
                    't' => 'url',
                    'v' => strval($url)
                );

                $url->section = $id;
                $parts[] = array(
                    't' => 'url',
                    'v' => strval($url)
                );
            }
        }

        $parts[] = array(
            't' => 'text',
            'v' => "\r\n--" . $boundary . "--\r\n"
        );

        /* Get the headers for the message. */
        $query = new Horde_Imap_Client_Fetch_Query();
        $query->imapDate();
        $query->flags();

        try {
            $res = $imp_imap->fetch($mbox, $query, array(
                'ids' => $imp_imap->getIdsOb($uid)
            ))->first();
            if (is_null($res)) {
                throw new IMP_Imap_Exception();
            }
            $flags = $res->getFlags();

            /* If in Virtual Inbox, we need to reset flag to unseen so that it
             * appears again in the mailbox list. */
            if ($mbox->vinbox) {
                $flags = array_values(array_diff($flags, array(Horde_Imap_Client::FLAG_SEEN)));
            }

            $new_uid = $imp_imap->append($mbox, array(
                array(
                    'data' => $parts,
                    'flags' => $flags,
                    'internaldate' => $res->getImapDate()
                )
            ))->ids;
            $new_uid = reset($new_uid);
        } catch (IMP_Imap_Exception $e) {
            throw new IMP_Exception(_("An error occured while attempting to strip the attachment."));
        }

        $this->delete($indices, array(
            'keeplog' => true,
            'mailboxob' => empty($opts['mailboxob']) ? null : $opts['mailboxob'],
            'nuke' => true
        ));

        $indices_ob = $mbox->getIndicesOb($new_uid);

        if (!empty($opts['mailboxob'])) {
            $opts['mailboxob']->setIndex($indices_ob);
        }

        /* We need to replace the old UID(s) in the URL params. */
        $vars = $injector->getInstance('Horde_Variables');
        if (isset($vars->buid)) {
            list(,$vars->buid) = $mbox->toBuids($indices_ob)->getSingle();
        }
        if (isset($vars->uid)) {
            $vars->uid = $new_uid;
        }

        return $indices_ob;
    }

    /**
     * Sets or clears a given flag for a list of messages.
     * Handles search mailboxes.
     * This function works with IMAP only, not POP3.
     *
     * @param array $action         A list of IMAP flag(s). Keys are 'add'
     *                              and/or 'remove'.
     * @param IMP_Indices $indices  An indices object.
     * @param array $opts           Additional options:
     *   - silent: (boolean) Don't output notification messages.
     *   - unchangedsince: (array) The unchangedsince value to pass to the
     *                     IMAP store command. Keys are mailbox names, values
     *                     are the unchangedsince values to use for that
     *                     mailbox.
     *
     * @return boolean  True if successful, false if not.
     */
    public function flag(array $action, IMP_Indices $indices,
                         array $opts = array())
    {
        global $injector, $notification;

        if (!count($indices)) {
            return false;
        }

        $opts = array_merge(array(
            'unchangedsince' => array()
        ), $opts);

        $ajax_queue = $injector->getInstance('IMP_Ajax_Queue');
        $ret = true;

        foreach ($indices as $ob) {
            try {
                if ($ob->mbox->readonly) {
                    throw new IMP_Exception(_("This mailbox is read-only."));
                }

                $ob->mbox->uidvalid;

                $unchangedsince = isset($opts['unchangedsince'][strval($ob->mbox)])
                    ? $opts['unchangedsince'][strval($ob->mbox)]
                    : null;

                /* Flag/unflag the messages now. */
                $imp_imap = $ob->mbox->imp_imap;
                $res = $imp_imap->store($ob->mbox, array_merge($action, array_filter(array(
                    'ids' => $imp_imap->getIdsOb($ob->uids),
                    'unchangedsince' => $unchangedsince
                ))));

                $flag_change = $ob->mbox->getIndicesOb($ob->uids);

                if ($unchangedsince && count($res)) {
                    foreach ($res as $val) {
                        unset($flag_change[$val]);
                    }
                    if (empty($opts['silent'])) {
                        $notification->push(sprintf(_("Flags were not changed for at least one message in the mailbox \"%s\" because the flags were altered by another connection to the mailbox prior to this request. You may redo the flag action if desired; this warning is precautionary to ensure you don't overwrite flag changes."), $ob->mbox->display), 'horde.warning');
                        $ret = false;
                    }
                }

                foreach ($action as $key => $val) {
                    $ajax_queue->flag($val, ($key == 'add'), $flag_change);
                    if ($indices instanceof IMP_Indices_Mailbox) {
                        $ajax_queue->flag($val, ($key == 'add'), $indices->mailbox->toBuids($flag_change));
                    }
                }
            } catch (Exception $e) {
                if (empty($opts['silent'])) {
                    $notification->push(sprintf(_("There was an error flagging messages in the mailbox \"%s\": %s."), $ob->mbox->display, $e->getMessage()), 'horde.error');
                }
                $ret = false;
            }
        }

        return $ret;
    }

    /**
     * Adds or removes flag(s) for all messages in a list of mailboxes.
     * This function works with IMAP only, not POP3.
     *
     * @param array $flags     The IMAP flag(s) to add or remove.
     * @param array $mboxes    The list of mailboxes to flag.
     * @param boolean $action  If true, add the flag(s), otherwise, remove the
     *                         flag(s).
     *
     * @return boolean  True if successful, false if not.
     */
    public function flagAllInMailbox($flags, $mboxes, $action = true)
    {
        if (empty($mboxes) || !is_array($mboxes)) {
            return false;
        }

        $action_array = $action
            ? array('add' => $flags)
            : array('remove' => $flags);
        $ajax_queue = $GLOBALS['injector']->getInstance('IMP_Ajax_Queue');

        $ajax_queue->poll($mboxes);

        foreach (IMP_Mailbox::get($mboxes) as $val) {
            try {
                /* Grab list of UIDs before flagging, to make sure we
                 * determine the exact subset that has been flagged. */
                $mailbox_list = $val->list_ob->getIndicesOb();
                $val->imp_imap->store($val, $action_array);
                $ajax_queue->flag(reset($action_array), $action, $mailbox_list);
            } catch (IMP_Imap_Exception $e) {
                return false;
            }
        }

        return true;
    }

    /**
     * Expunges all deleted messages from the list of mailboxes.
     *
     * @param array $mbox_list  The list of mailboxes to empty as keys; an
     *                          optional array of indices to delete as values.
     *                          If the value is not an array, all messages
     *                          flagged as deleted in the mailbox will be
     *                          deleted.
     * @param array $opts       Additional options:
     *   - list: (boolean) Return a list of messages expunged.
     *           DEFAULT: false
     *   - mailboxob: (IMP_Mailbox_List) Update this mailbox object.
     *                DEFAULT: No update.
     *
     * @return IMP_Indices  If 'list' option is true, an indices object
     *                      containing the messages that have been expunged.
     */
    public function expungeMailbox($mbox_list, array $opts = array())
    {
        $msg_list = !empty($opts['list']);

        if (empty($mbox_list)) {
            return $msg_list ? new IMP_Indices() : null;
        }

        $process_list = $update_list = array();

        foreach ($mbox_list as $key => $val) {
            $key = IMP_Mailbox::get($key);

            if ($key->access_expunge) {
                $ids = $key->imp_imap->getIdsOb(is_array($val) ? $val : Horde_Imap_Client_Ids::ALL);

                if ($key->search) {
                    foreach ($key->getSearchOb()->mboxes as $skey) {
                        $process_list[] = array($skey, $ids);
                    }
                } else {
                    $process_list[] = array($key, $ids);
                }
            }
        }

        // [0] = IMP_Mailbox object, [1] = Horde_Imap_Client_Ids object
        foreach ($process_list as $val) {
            /* If expunging a particular UID list, need to check
             * UIDVALIDITY. */
            if (!$val[1]->all) {
                try {
                    $val[0]->uidvalid;
                } catch (IMP_Exception $e) {
                    continue;
                }
            }

            try {
                $update_list[strval($val[0])] = $val[0]->imp_imap->expunge($val[0], array(
                    'ids' => $val[1],
                    'list' => $msg_list
                ));

                if (!empty($opts['mailboxob']) &&
                    $opts['mailboxob']->isBuilt()) {
                    $opts['mailboxob']->removeMsgs($val[1]->all ? true : $val[0]->getIndicesOb($val[1]));
                }
            } catch (IMP_Imap_Exception $e) {}
        }

        if ($msg_list) {
            return new IMP_Indices($update_list);
        }
    }

    /**
     * Empties an entire mailbox.
     *
     * @param array $mbox_list  The list of mailboxes to empty.
     */
    public function emptyMailbox($mbox_list)
    {
        global $notification, $prefs;

        $trash = ($prefs->getValue('use_trash'))
            ? IMP_Mailbox::getPref(IMP_Mailbox::MBOX_TRASH)
            : null;

        foreach (IMP_Mailbox::get($mbox_list) as $mbox) {
            if (!$mbox->access_empty) {
                $notification->push(sprintf(_("Could not delete messages from %s. This mailbox is read-only."), $mbox->display), 'horde.error');
                continue;
            }

            if ($mbox->vtrash) {
                $this->expungeMailbox(array_flip($mbox->getSearchOb()->mboxes));
                $notification->push(_("Emptied all messages from Virtual Trash Folder."), 'horde.success');
                continue;
            }

            /* Make sure there is at least 1 message before attempting to
             * delete. */
            try {
                $imp_imap = $mbox->imp_imap;
                $status = $imp_imap->status($mbox, Horde_Imap_Client::STATUS_MESSAGES);
                if (empty($status['messages'])) {
                    $notification->push(sprintf(_("The mailbox %s is already empty."), $mbox->display), 'horde.message');
                    continue;
                }

                if (!$trash || ($trash == $mbox)) {
                    $imp_imap->store($mbox, array(
                        'add' => array(Horde_Imap_Client::FLAG_DELETED)
                    ));
                    $this->expungeMailbox(array(strval($mbox) => 1));
                } else {
                    $ret = $imp_imap->search($mbox);
                    $this->delete($mbox->getIndicesOb($ret['match']));
                }

                $notification->push(sprintf(_("Emptied all messages from %s."), $mbox->display), 'horde.success');
            } catch (IMP_Imap_Exception $e) {}
        }
    }

}
