<?php
/**
 * Copyright 2008-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 2008-2017 Horde LLC
 * @license   http://www.horde.org/licenses/gpl GPL
 * @package   IMP
 */

/**
 * Provides common functions for interaction with IMAP/POP3 servers via the
 * Horde_Imap_Client package.
 *
 * @author    Michael Slusarz <slusarz@horde.org>
 * @category  Horde
 * @copyright 2008-2017 Horde LLC
 * @license   http://www.horde.org/licenses/gpl GPL
 * @package   IMP
 *
 * @property-read boolean $changed  If true, this object has changed.
 * @property-read Horde_Imap_Client_Base $client_ob  The IMAP client object.
 * @property-read IMP_Imap_Config $config  Base backend config settings.
 * @property-read boolean $init  Has the IMAP object been initialized?
 * @property-read integer $max_compose_bodysize  The maximum size (in bytes)
 *                                               of the compose message body.
 * @property-read integer $max_compose_recipients  The maximum number of
 *                                                 recipients to send to per
 *                                                 compose message.
 * @property-read integer $max_compose_timelimit  The maximum number of
 *                                                recipients to send to in the
 *                                                configured timelimit.
 * @property-read integer $max_create_mboxes  The maximum number of mailboxes
 *                                            a user can create.
 * @property-read string $server_key  Server key used to login.
 * @property-read string $thread_algo  The threading algorithm to use.
 * @property-read Horde_Imap_Client_Url $url  A URL object.
 */
class IMP_Imap implements Serializable
{
    /* Access constants. */
    const ACCESS_FOLDERS = 1;
    const ACCESS_SEARCH = 2;
    const ACCESS_FLAGS = 3;
    const ACCESS_UNSEEN = 4;
    const ACCESS_TRASH = 5;
    const ACCESS_CREATEMBOX = 6;
    const ACCESS_CREATEMBOX_MAX = 7;
    const ACCESS_COMPOSE_BODYSIZE = 13;
    const ACCESS_COMPOSE_RECIPIENTS = 8;
    const ACCESS_COMPOSE_TIMELIMIT = 9;
    const ACCESS_ACL = 10;
    const ACCESS_DRAFTS = 11;
    const ACCESS_REMOTE = 12;
    const ACCESS_IMPORT = 14;
    const ACCESS_SORT = 15;

    /* Default namespace. */
    const NS_DEFAULT = "\0default";

    /**
     * Cached backend configuration.
     *
     * @var array
     */
    static protected $_backends = array();

    /**
     * Has this object changed?
     *
     * @var boolean
     */
    protected $_changed = false;

    /**
     * Backend config.
     *
     * @var IMP_Imap_Config
     */
    protected $_config;

    /**
     * Object identifier.
     *
     * @var string
     */
    protected $_id;

    /**
     * The IMAP client object.
     *
     * @var Horde_Imap_Client_Base
     */
    protected $_ob;

    /**
     * Temporary data cache (destroyed at end of request).
     *
     * @var array
     */
    protected $_temp = array();

    /**
     * Constructor.
     *
     * @param string $id  Object identifier.
     */
    public function __construct($id)
    {
        $this->_id = strval($id);
    }

    /**
     */
    public function __get($key)
    {
        switch ($key) {
        case 'changed':
            return $this->_changed;

        case 'client_ob':
            return $this->init
                ? $this->_ob
                : null;

        case 'config':
            return isset($this->_config)
                ? $this->_config
                : new Horde_Support_Stub();

        case 'init':
            return isset($this->_ob);

        case 'max_compose_bodysize':
        case 'max_compose_recipients':
        case 'max_compose_timelimit':
            $perm = $GLOBALS['injector']->getInstance('Horde_Perms')->getPermissions('imp:' . str_replace('max_compose', 'max', $key), $GLOBALS['registry']->getAuth());
            return intval($perm[0]);

        case 'max_create_mboxes':
            $perm = $GLOBALS['injector']->getInstance('Horde_Perms')->getPermissions('imp:' . $this->_getPerm($key), $GLOBALS['registry']->getAuth());
            return intval($perm[0]);

        case 'server_key':
            return $this->init
                ? $this->_ob->getParam('imp:backend')
                : null;

        case 'thread_algo':
            if (!$this->init) {
                return 'ORDEREDSUBJECT';
            }

            if ($thread = $this->_ob->getParam('imp:thread_algo')) {
                return $thread;
            }

            $thread = $this->config->thread;
            $thread_cap = $this->queryCapability('THREAD');
            if (!in_array($thread, is_array($thread_cap) ? $thread_cap : array())) {
                $thread = 'ORDEREDSUBJECT';
            }

            $this->_ob->setParam('imp:thread_algo', $thread);
            $this->_changed = true;

            return $thread;

        case 'url':
            $url = new Horde_Imap_Client_Url();
            if ($this->init) {
                $url->hostspec = $this->getParam('hostspec');
                $url->port = $this->getParam('port');
                $url->protocol = $this->isImap() ? 'imap' : 'pop';
            }
            return $url;
        }
    }

    /**
     */
    public function __toString()
    {
        return $this->_id;
    }

    /**
     * Get the full permission name for a permission.
     *
     * @param string $perm  The permission.
     *
     * @return string  The full (backend-specific) permission name.
     */
    protected function _getPerm($perm)
    {
        return 'backends:' . ($this->init ? $this->server_key . ':' : '') . $perm;
    }

    /**
     * Determine if this is a connection to an IMAP server.
     *
     * @return boolean  True if connected to IMAP server.
     */
    public function isImap()
    {
        return ($this->init &&
                ($this->_ob instanceof Horde_Imap_Client_Socket));
    }

    /**
     * Determine if this is a connection to an IMAP server.
     *
     * @return boolean  True if connected to IMAP server.
     */
    public function isPop3()
    {
        return ($this->init &&
                ($this->_ob instanceof Horde_Imap_Client_Socket_Pop3));
    }

    /**
     * Create the base Horde_Imap_Client object (from an entry in
     * backends.php).
     *
     * @param string $username  The username to authenticate with.
     * @param string $password  The password to authenticate with.
     * @param string $skey      Create a new object using this server key.
     *
     * @return Horde_Imap_Client_Base  Client object.
     * @throws IMP_Imap_Exception
     */
    public function createBaseImapObject($username, $password, $skey)
    {
        if ($this->init) {
            return $this->client_ob;
        }

        if (($config = $this->loadServerConfig($skey)) === false) {
            $error = new IMP_Imap_Exception('Could not load server configuration.');
            Horde::log($error);
            throw $error;
        }

        $imap_config = array(
            'hostspec' => $config->hostspec,
            'id' => $config->id,
            'password' => new IMP_Imap_Password($password),
            'port' => $config->port,
            'secure' => (($secure = $config->secure) ? $secure : false),
            'username' => $username,
            // IMP specific config
            'imp:backend' => $skey
        );

        /* Needed here to set config information in createImapObject(). */
        $this->_config = $config;

        try {
            return $this->createImapObject($imap_config, ($config->protocol == 'imap'));
        } catch (IMP_Imap_Exception $e) {
            unset($this->_config);
            throw $e;
        }
    }

    /**
     * Create a Horde_Imap_Client object.
     *
     * @param array $config  The IMAP configuration.
     * @param boolean $imap  True if IMAP connection, false if POP3.
     *
     * @return Horde_Imap_Client_Base  Client object.
     * @throws IMP_Imap_Exception
     */
    public function createImapObject($config, $imap = true)
    {
        if ($this->init) {
            return $this->_ob;
        }

        $sconfig = $this->config;
        $config = array_merge(array(
            'cache' => $sconfig->cache_params,
            'capability_ignore' => $sconfig->capability_ignore,
            'comparator' => $sconfig->comparator,
            'debug' => $sconfig->debug,
            'debug_literal' => $sconfig->debug_raw,
            'lang' => $sconfig->lang,
            'timeout' => $sconfig->timeout,
            // 'imp:login' - Set in __call()
        ), $config);

        try {
            $this->_ob = $imap
                ? new Horde_Imap_Client_Socket($config)
                : new Horde_Imap_Client_Socket_Pop3($config);
            return $this->_ob;
        } catch (Horde_Imap_Client_Exception $e) {
            Horde::log($e->raw_msg);
            throw new IMP_Imap_Exception($e);
        }
    }

    /**
     * Perform post-login tasks.
     */
    public function doPostLoginTasks()
    {
        global $prefs;

        switch ($this->_config->protocol) {
        case 'imap':
            /* Overwrite default special mailbox names. */
            foreach ($this->_config->special_mboxes as $key => $val) {
                if ($key != IMP_Mailbox::MBOX_USERSPECIAL) {
                    $prefs->setValue($key, $val, array(
                        'force' => true,
                        'nosave' => true
                    ));
                }
            }
            break;

        case 'pop':
            /* Turn some options off if we are working with POP3. */
            foreach (array('newmail_notify', 'save_sent_mail') as $val) {
                $prefs->setValue($val, false, array(
                    'force' => true,
                    'nosave' => true
                ));
                $prefs->setLocked($val, true);
            }
            $prefs->setLocked(IMP_Mailbox::MBOX_DRAFTS, true);
            $prefs->setLocked(IMP_Mailbox::MBOX_SENT, true);
            $prefs->setLocked(IMP_Mailbox::MBOX_SPAM, true);
            $prefs->setLocked(IMP_Mailbox::MBOX_TEMPLATES, true);
            $prefs->setLocked(IMP_Mailbox::MBOX_TRASH, true);
            break;
        }

        $this->updateFetchIgnore();
    }

    /**
     * Update the list of mailboxes to ignore when caching FETCH data in the
     * IMAP client object.
     */
    public function updateFetchIgnore()
    {
        if ($this->isImap()) {
            $special = IMP_Mailbox::getSpecialMailboxes();
            $cache = $this->_ob->getParam('cache');
            $cache['fetch_ignore'] = array_filter(array(
                strval($special[IMP_Mailbox::SPECIAL_SPAM]),
                strval($special[IMP_Mailbox::SPECIAL_TRASH])
            ));
            $this->_ob->setParam('cache', $cache);
        }
    }

    /**
     * Checks access rights for a server.
     *
     * @param integer $right  Access right.
     *
     * @return boolean  Does the access right exist?
     */
    public function access($right)
    {
        global $injector;

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

        switch ($right) {
        case self::ACCESS_ACL:
            return ($this->config->acl && $this->queryCapability('ACL'));

        case self::ACCESS_CREATEMBOX:
            return ($this->isImap() &&
                    $injector->getInstance('Horde_Core_Perms')->hasAppPermission($this->_getPerm('create_mboxes')));

        case self::ACCESS_CREATEMBOX_MAX:
            return ($this->isImap() &&
                    $injector->getInstance('Horde_Core_Perms')->hasAppPermission($this->_getPerm('max_create_mboxes')));

        case self::ACCESS_DRAFTS:
        case self::ACCESS_FLAGS:
        case self::ACCESS_IMPORT:
        case self::ACCESS_SEARCH:
        case self::ACCESS_UNSEEN:
            return $this->isImap();

        case self::ACCESS_FOLDERS:
        case self::ACCESS_TRASH:
            return ($this->isImap() &&
                    $injector->getInstance('Horde_Core_Perms')->hasAppPermission($this->_getPerm('allow_folders')));

        case self::ACCESS_REMOTE:
            return $injector->getInstance('Horde_Core_Perms')->hasAppPermission($this->_getPerm('allow_remote'));

        case self::ACCESS_SORT:
            return ($this->isImap() &&
                    ($this->config->sort_force || $this->_ob->queryCapability('SORT')));
        }

        return false;
    }

    /**
     * Checks compose access rights for a server.
     *
     * @param integer $right  Access right.
     * @param integer $data   Data required to check the rights:
     * <pre>
     *   - ACCESS_COMPOSE_BODYSIZE
     *     The size of the body data.
     *
     *   - ACCESS_COMPOSE_RECIPIENTS
     *   - ACCESS_COMPOSE_TIMELIMIT
     *     The number of e-mail recipients.
     * </pre>
     *
     * @return boolean  Is the access allowed?
     */
    public function accessCompose($right, $data)
    {
        switch ($right) {
        case self::ACCESS_COMPOSE_BODYSIZE:
            $perm_name = 'max_bodysize';
            break;

        case self::ACCESS_COMPOSE_RECIPIENTS:
            $perm_name = 'max_recipients';
            break;

        case self::ACCESS_COMPOSE_TIMELIMIT:
            $perm_name = 'max_timelimit';
            break;

        default:
            return false;
        }

        return $GLOBALS['injector']->getInstance('Horde_Core_Perms')->hasAppPermission(
            $perm_name,
            array(
                'opts' => array(
                    'value' => $data
                )
            )
        );
    }

    /**
     * Get namespace info for a full mailbox path.
     *
     * @param string $mailbox    The mailbox path. (self:NS_DEFAULT will
     *                           return the default personal namespace.)
     * @param boolean $personal  If true, will return empty namespace only
     *                           if it is a personal namespace.
     *
     * @return mixed  The namespace info for the mailbox path or null if the
     *                path doesn't exist.
     */
    public function getNamespace($mailbox, $personal = false)
    {
        if ($this->isImap()) {
            $ns = $this->getNamespaces();
            if ($mailbox !== self::NS_DEFAULT) {
                return $ns->getNamespace($mailbox, $personal);
            }

            foreach ($ns as $val) {
                if ($val->type === $val::NS_PERSONAL) {
                    return $val;
                }
            }
        }

        return null;
    }

    /**
     * Return the cache ID for this mailbox.
     *
     * @param string $mailbox  The mailbox name (UTF-8).
     * @param array $addl      Local IMP metadata to add to the cache ID.
     *
     * @return string  The cache ID.
     */
    public function getCacheId($mailbox, array $addl = array())
    {
        return $this->getSyncToken($mailbox) .
            (empty($addl) ? '' : ('|' . implode('|', $addl)));
    }

    /**
     * Parses the cache ID for this mailbox.
     *
     * @param string $id  Cache ID generated by getCacheId().
     *
     * @return array  Two element array:
     *   - date: (integer) Date information (day of year), if embedded in
     *           cache ID.
     *   - token: (string) Mailbox sync token.
     */
    public function parseCacheId($id)
    {
        $out = array('date' => null);

        if ((($pos = strrpos($id, '|')) !== false) &&
            (substr($id, $pos + 1, 1) == 'D')) {
            $out['date'] = substr($id, $pos + 2);
        }

        $out['token'] = (($pos = strpos($id, '|')) === false)
            ? $id
            : substr($id, 0, $pos);

        return $out;
    }

    /**
     * Returns a list of messages, split into slices based on the total
     * message size.
     *
     * @param string $mbox                IMAP mailbox.
     * @param Horde_Imap_Client_Ids $ids  ID list.
     * @param integer $size               Maximum size of a slice.
     *
     * @return array  An array of Horde_Imap_Client_Ids objects.
     */
    public function getSlices(
        $mbox, Horde_Imap_Client_Ids $ids, $size = 5242880
    )
    {
        $imp_imap = IMP_Mailbox::get($mbox)->imp_imap;

        $query = new Horde_Imap_Client_Fetch_Query();
        $query->size();

        try {
            $res = $imp_imap->fetch($mbox, $query, array(
                'ids' => $ids,
                'nocache' => true
            ));
        } catch (IMP_Imap_Exception $e) {
            return array();
        }

        $curr = $slices = array();
        $curr_size = 0;

        foreach ($res as $key => $val) {
            $curr_size += $val->getSize();
            if ($curr_size > $size) {
                $slices[] = $imp_imap->getIdsOb($curr, $ids->sequence);
                $curr = array();
            }
            $curr[] = $key;
        }

        $slices[] = $imp_imap->getIdsOb($curr, $ids->sequence);

        return $slices;
    }

    /**
     * Handle status() calls. This call may hit multiple servers.
     *
     * @see Horde_Imap_Client_Base#status()
     */
    protected function _status($args)
    {
        global $injector;

        $accounts = $mboxes = $out = array();
        $imap_factory = $injector->getInstance('IMP_Factory_Imap');

        foreach (IMP_Mailbox::get($args[0]) as $val) {
            if ($raccount = $val->remote_account) {
                $accounts[strval($raccount)] = $raccount;
            }
            $mboxes[strval($raccount)][] = $val;
        }

        foreach ($mboxes as $key => $val) {
            $imap = $imap_factory->create($key);
            if ($imap->init) {
                foreach (call_user_func_array(array($imap, 'impStatus'), array($val) + $args) as $key2 => $val2) {
                    $out[isset($accounts[$key]) ? $accounts[$key]->mailbox($key2) : $key2] = $val2;
                }
            }
        }

        return $out;
    }

    /**
     * All other calls to this class are routed to the underlying
     * Horde_Imap_Client_Base object.
     *
     * @param string $method  Method name.
     * @param array $params   Method parameters.
     *
     * @return mixed  The return from the requested method.
     * @throws BadMethodCallException
     * @throws IMP_Imap_Exception
     */
    public function __call($method, $params)
    {
        global $injector;

        if (!$this->init) {
            /* Fallback for these methods. */
            switch ($method) {
            case 'getIdsOb':
                $ob = new Horde_Imap_Client_Ids();
                call_user_func_array(array($ob, 'add'), $params);
                return $ob;
            }

            throw new Horde_Exception_AuthenticationFailure(
                'IMP is marked as authenticated, but no credentials can be found in the session.',
                Horde_Auth::REASON_SESSION
            );
        }

        switch ($method) {
        case 'append':
        case 'createMailbox':
        case 'deleteMailbox':
        case 'expunge':
        case 'fetch':
        case 'getACL':
        case 'getMetadata':
        case 'getMyACLRights':
        case 'getQuota':
        case 'getQuotaRoot':
        case 'getSyncToken':
        case 'setMetadata':
        case 'setQuota':
        case 'store':
        case 'subscribeMailbox':
        case 'sync':
        case 'thread':
            // Horde_Imap_Client_Mailbox: these calls all have the mailbox as
            // their first parameter.
            $params[0] = IMP_Mailbox::getImapMboxOb($params[0]);
            break;

        case 'copy':
        case 'renameMailbox':
            // These calls may hit multiple servers.
            $source = IMP_Mailbox::get($params[0]);
            $dest = IMP_Mailbox::get($params[1]);
            if ($source->remote_account != $dest->remote_account) {
                return call_user_func_array(array($this, '_' . $method), $params);
            }

            // Horde_Imap_Client_Mailbox: these calls all have the mailbox as
            // their first two parameters.
            $params[0] = $source->imap_mbox_ob;
            $params[1] = $dest->imap_mbox_ob;
            break;

        case 'getNamespaces':
            if (isset($this->_temp['ns'])) {
                return $this->_temp['ns'];
            }
            $nsconfig = $this->config->namespace;
            $params[0] = is_null($nsconfig) ? array() : $nsconfig;
            $params[1] = array('ob_return' => true);
            break;

        case 'impStatus':
            /* Internal method: allows status call with array of mailboxes,
             * guaranteeing they are all on this server. */
            $params[0] = IMP_Mailbox::getImapMboxOb($params[0]);
            $method = 'status';
            break;

        case 'openMailbox':
            $mbox = IMP_Mailbox::get($params[0]);
            if ($mbox->search) {
                /* Can't open a search mailbox. */
                return;
            }
            $params[0] = $mbox->imap_mbox_ob;
            break;

        case 'search':
            $params = call_user_func_array(array($this, '_search'), $params);
            break;

        case 'status':
            if (is_array($params[0])) {
                return $this->_status($params);
            }
            $params[0] = IMP_Mailbox::getImapMboxOb($params[0]);
            break;

        default:
            if (!method_exists($this->_ob, $method)) {
                throw new BadMethodCallException(
                    sprintf('%s: Invalid method call "%s".', __CLASS__, $method)
                );
            }
            break;
        }

        try {
            $result = call_user_func_array(array($this->_ob, $method), $params);
        } catch (Horde_Imap_Client_Exception $e) {
            $error = new IMP_Imap_Exception($e);

            if (!$error->authError()) {
                switch ($method) {
                case 'getNamespaces':
                    return new Horde_Imap_Client_Namespace_List();
                }
            }

            Horde::log(
                new Exception(
                    sprintf('[%s] %s', $method, $e->raw_msg),
                    $e->getCode(),
                    $e
                ),
                'WARN'
            );

            throw $error;
        }

        /* Special handling for various methods. */
        switch ($method) {
        case 'createMailbox':
        case 'deleteMailbox':
        case 'renameMailbox':
            $injector->getInstance('IMP_Mailbox_SessionCache')->expire(
                null,
                // Mailbox is first parameter.
                IMP_Mailbox::get($params[0])
            );
            break;

        case 'getNamespaces':
            $this->_temp['ns'] = $result;
            break;

        case 'login':
            if (!$this->_ob->getParam('imp:login')) {
                /* Check for POP3 UIDL support. */
                if ($this->isPop3() && !$this->queryCapability('UIDL')) {
                    Horde::log(
                        sprintf(
                            'The POP3 server does not support the REQUIRED UIDL capability. [server key: %s]',
                            $this->server_key
                        ),
                        'CRIT'
                    );
                    throw new Horde_Exception_AuthenticationFailure(
                        _("The mail server is not currently avaliable."),
                        Horde_Auth::REASON_MESSAGE
                    );
                }

                $this->_ob->setParam('imp:login', true);
                $this->_changed = true;
            }
            break;

        case 'setACL':
            $injector->getInstance('IMP_Mailbox_SessionCache')->expire(
                IMP_Mailbox_SessionCache::CACHE_ACL,
                IMP_Mailbox::get($params[0])
            );
            break;
        }

        return $result;
    }

    /**
     * Prepares an IMAP search query.  Needed because certain configuration
     * parameters may need to be dynamically altered before passed to the
     * Imap_Client object.
     *
     * @param string $mailbox                        The mailbox to search.
     * @param Horde_Imap_Client_Search_Query $query  The search query object.
     * @param array $opts                            Additional options.
     *
     * @return array  Parameters to use in the search() call.
     */
    protected function _search($mailbox, $query = null, array $opts = array())
    {
        $mailbox = IMP_Mailbox::get($mailbox);

        if (!empty($opts['sort']) && $mailbox->access_sort) {
            /* If doing a from/to search, use display sorting if possible.
             * Although there is a fallback to a PHP-based display sort, for
             * performance reasons only do a display sort if it is supported
             * on the server. */
            foreach ($opts['sort'] as $key => $val) {
                switch ($val) {
                case Horde_Imap_Client::SORT_FROM:
                    $opts['sort'][$key] = Horde_Imap_Client::SORT_DISPLAYFROM_FALLBACK;
                    break;

                case Horde_Imap_Client::SORT_TO:
                    $opts['sort'][$key] = Horde_Imap_Client::SORT_DISPLAYTO_FALLBACK;
                    break;
                }
            }
        }

        if (!is_null($query)) {
            $query->charset('UTF-8', false);
        }

        return array($mailbox->imap_mbox_ob, $query, $opts);
    }

    /**
     * Handle copy() calls that hit multiple servers.
     *
     * @see Horde_Imap_Client_Base#copy()
     */
    protected function _copy()
    {
        global $injector;

        $args = func_get_args();
        $imap_factory = $injector->getInstance('IMP_Factory_Imap');
        $source_imap = $imap_factory->create($args[0]);
        $dest_imap = $imap_factory->create($args[1]);

        $create = !empty($args[2]['create']);
        $ids = isset($args[2]['ids'])
            ? $args[2]['ids']
            : $source_imap->getIdsOb(Horde_Imap_Client_Ids::ALL);
        $move = !empty($args[2]['move']);
        $retval = true;

        $query = new Horde_Imap_Client_Fetch_Query();
        $query->fullText(array(
            'peek' => true
        ));

        foreach ($this->getSlices($args[0], $ids) as $val) {
            try {
                $res = $source_imap->fetch($args[0], $query, array(
                    'ids' => $val,
                    'nocache' => true
                ));

                $append = array();
                foreach ($res as $msg) {
                    $append[] = array(
                        'data' => $msg->getFullMsg(true)
                    );
                }

                $dest_imap->append($args[1], $append, array(
                    'create' => $create
                ));

                if ($move) {
                    $source_imap->expunge($args[0], array(
                        'delete' => true,
                        'ids' => $val
                    ));
                }
            } catch (IMP_Imap_Exception $e) {
                $retval = false;
            }
        }

        return $retval;
    }

    /**
     * Handle copy() calls. This call may hit multiple servers, so
     * need to handle separately from other IMAP calls.
     *
     * @see Horde_Imap_Client_Base#renameMailbox()
     */
    protected function _renameMailbox()
    {
        $args = func_get_args();
        $source = IMP_Mailbox::get($args[0]);

        if ($source->create() && $this->copy($source, $args[1])) {
            $source->delete();
        } else {
            throw new IMP_Imap_Exception(_("Could not move all messages between mailboxes, so the original mailbox was not removed."));
        }
    }

    /* Static methods. */

    /**
     * Loads the IMP server configuration from backends.php.
     *
     * @param string $server  Returns this labeled entry only.
     *
     * @return mixed  If $server is set return this entry; else, return the
     *                entire servers array. Returns false on error.
     */
    static public function loadServerConfig($server = null)
    {
        global $registry;

        if (empty(self::$_backends)) {
            try {
                $s = $registry->loadConfigFile('backends.php', 'servers', 'imp')->config['servers'];
            } catch (Horde_Exception $e) {
                Horde::log($e, 'ERR');
                return false;
            }

            foreach ($s as $key => $val) {
                if (empty($val['disabled'])) {
                    self::$_backends[$key] = new IMP_Imap_Config($val);
                }
            }
        }

        return is_null($server)
            ? self::$_backends
            : (isset(self::$_backends[$server]) ? self::$_backends[$server] : false);
    }

    /* Serializable methods. */

    /**
     */
    public function serialize()
    {
        return $GLOBALS['injector']->getInstance('Horde_Pack')->pack(
            array(
                $this->_ob,
                $this->_id,
                $this->_config
            ),
            array(
                'compression' => false,
                'phpob' => true
            )
        );
    }

    /**
     */
    public function unserialize($data)
    {
        list(
            $this->_ob,
            $this->_id,
            $this->_config
        ) = $GLOBALS['injector']->getInstance('Horde_Pack')->unpack($data);
    }

}
