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

/**
 * Defines the AJAX interface for IMP.
 *
 * Global tasks:
 *   - msgload: (string) BUID of a message to load in the background (mailbox
 *              is located in 'mailbox' parameter).
 *   - poll: (string) The list of mailboxes to process (JSON encoded
 *           array; mailboxes are base64url encoded). If an empty array, polls
 *           all mailboxes.
 *
 * Global parameters (in viewport parameter):
 *   - force: (integer) If set, always return viewport information if changed.
 *
 * @author    Michael Slusarz <slusarz@horde.org>
 * @category  Horde
 * @copyright 2010-2017 Horde LLC
 * @license   http://www.horde.org/licenses/gpl GPL
 * @package   IMP
 */
class IMP_Ajax_Application extends Horde_Core_Ajax_Application
{
    /**
     * The IMP_Indices_Mailbox object based on form data.
     *
     * @var IMP_Indices_Mailbox
     */
    public $indices;

    /**
     * Queue object.
     *
     * @var IMP_Ajax_Queue
     */
    public $queue;

    /**
     */
    protected function _init()
    {
        global $injector, $registry;

        $this->queue = $injector->getInstance('IMP_Ajax_Queue');

        switch ($registry->getView()) {
        case $registry::VIEW_BASIC:
            $this->addHandler('IMP_Ajax_Application_Handler_Mboxtoggle');
            $this->addHandler('IMP_Ajax_Application_Handler_Passphrase');
            $this->addHandler('IMP_Ajax_Application_Handler_Search');
            if ($injector->getInstance('IMP_Factory_Imap')->create()->access(IMP_Imap::ACCESS_REMOTE)) {
                $this->addHandler('IMP_Ajax_Application_Handler_RemotePrefs');
            }
            break;

        case $registry::VIEW_DYNAMIC:
            $this->addHandler('IMP_Ajax_Application_Handler_Common');
            $this->addHandler('IMP_Ajax_Application_Handler_ComposeAttach');
            $this->addHandler('IMP_Ajax_Application_Handler_Draft');
            $this->addHandler('IMP_Ajax_Application_Handler_Dynamic');
            $this->addHandler('IMP_Ajax_Application_Handler_Mboxtoggle');
            $this->addHandler('IMP_Ajax_Application_Handler_Passphrase');
            $this->addHandler('IMP_Ajax_Application_Handler_Search');
            if ($injector->getInstance('IMP_Factory_Imap')->create()->access(IMP_Imap::ACCESS_REMOTE)) {
                $this->addHandler('IMP_Ajax_Application_Handler_Remote');
                $this->addHandler('IMP_Ajax_Application_Handler_RemotePrefs');
            }
            break;

        case $registry::VIEW_SMARTMOBILE:
            $this->addHandler('IMP_Ajax_Application_Handler_Common');
            $this->addHandler('IMP_Ajax_Application_Handler_ComposeAttach');
            $this->addHandler('IMP_Ajax_Application_Handler_Draft')->disabled = array(
                'autoSaveDraft',
                'saveTemplate'
            );
            $this->addHandler('IMP_Ajax_Application_Handler_Smartmobile');
            break;
        }

        $this->addHandler('IMP_Ajax_Application_Handler_ImageUnblock');
        $this->addHandler('Horde_Core_Ajax_Application_Handler_Imple');
        $this->addHandler('Horde_Core_Ajax_Application_Handler_Prefs');

        /* Copy 'view' paramter to 'mailbox', because this is what
         * IMP_Indices_Mailbox expects. */
        if (isset($this->_vars->view)) {
            $this->_vars->mailbox = $this->_vars->view;
        }

        $this->indices = new IMP_Indices_Mailbox($this->_vars);

        /* Make sure the viewport entry is initialized. */
        $vp = isset($this->_vars->viewport)
            ? json_decode($this->_vars->viewport)
            : new stdClass;
        $this->_vars->viewport = new Horde_Support_ObjectStub($vp);

        /* GLOBAL TASKS */

        /* Check for global msgload task. */
        if (isset($this->_vars->msgload)) {
            $this->queue->message($this->indices->mailbox->fromBuids(array($this->_vars->msgload)), true, true);
        }

        /* Check for global poll task. */
        if (isset($this->_vars->poll)) {
            $poll = json_decode($this->_vars->poll);
            $this->queue->poll(
                empty($poll)
                    ? $injector->getInstance('IMP_Ftree')->poll->getPollList()
                    : IMP_Mailbox::formFrom($poll),
                true
            );
        }
    }

    /**
     */
    public function send()
    {
        $this->getTasks();
        parent::send();
    }

    /**
     * Get the list of tasks.
     *
     * @return array  Task list.
     */
    public function getTasks()
    {
        $this->queue->add($this);

        /* Convert viewport to output format. */
        $name = 'imp:viewport';
        if (isset($this->tasks->$name)) {
            $this->tasks->$name = $this->tasks->$name->toObject();
        }

        return $this->tasks;
    }

    /* Shared code between handlers. */

    /**
     * Initialize the objects needed to compose.
     *
     * @return object  Object with the following properties:
     *   - ajax: IMP_Ajax_Application_Compose object
     *   - compose: IMP_Compose object
     *   - contents: IMP_Contents object
     */
    public function initCompose()
    {
        global $injector;

        $ob = new stdClass;

        $ob->compose = $injector->getInstance('IMP_Factory_Compose')->create($this->_vars->imp_compose);
        $ob->ajax = new IMP_Ajax_Application_Compose($ob->compose, $this->_vars->type);

        if (!($ob->contents = $ob->compose->getContentsOb()) &&
            count($this->indices)) {
            $ob->contents = $injector->getInstance('IMP_Factory_Contents')->create($this->indices);
        }

        $this->queue->compose($ob->compose);

        return $ob;
    }

    /**
     * Check the UID validity of the mailbox.
     *
     * See the list of variables needed for viewPortData().
     */
    public function checkUidvalidity()
    {
        try {
            $this->indices->mailbox->uidvalid;
        } catch (IMP_Exception $e) {
            $this->addTask('viewport', $this->viewPortData(true));
        }
    }

    /**
     * Generate the information necessary for a ViewPort request from/to the
     * browser.
     *
     * Variables used (contained in 'viewport' object):
     *   - applyfilter
     *   - cache
     *   - cacheid
     *   - delhide
     *   - initial
     *   - qsearch
     *   - qsearchfield
     *   - qsearchfilter
     *   - qsearchflag
     *   - qsearchflagnot
     *   - qsearchmbox
     *   - rangeslice
     *   - sortby
     *   - sortdir
     *
     * @param boolean $change  True if cache information has changed.
     *
     * @return IMP_Ajax_Application_Viewport  Viewport data object.
     */
    public function viewPortData($change)
    {
        $args = array(
            'change' => $change,
            'mbox' => strval($this->indices->mailbox)
        );

        $params = array(
            'applyfilter', 'cache', 'cacheid', 'delhide', 'initial', 'qsearch',
            'qsearchfield', 'qsearchfilter', 'qsearchflag', 'qsearchflagnot',
            'qsearchmbox', 'rangeslice', 'sortby', 'sortdir'
        );

        $vp = $this->_vars->viewport;

        foreach ($params as $val) {
            $args[$val] = $vp->$val;
        }

        if ($vp->search || $args['initial']) {
            $args += array(
                'after' => intval($vp->after),
                'before' => intval($vp->before)
            );
        }

        if ($vp->search) {
            $search = json_decode($vp->search);
            $args += array(
                'search_buid' => isset($search->buid) ? $search->buid : null,
                'search_unseen' => isset($search->unseen) ? $search->unseen : null
            );
        } else {
            list($slice_start, $slice_end) = explode(':', $vp->slice, 2);
            $args += array(
                'slice_start' => intval($slice_start),
                'slice_end' => intval($slice_end)
            );
        }

        return $GLOBALS['injector']->getInstance('IMP_Ajax_Application_ListMessages')->listMessages($args);
    }

    /**
     * Determine if the cache information has changed.
     *
     * Variables used:
     *   - cacheid: (string) The browser (ViewPort) cache identifier.
     *   - forceUpdate: (integer) If 1, forces an update.
     *
     * @param boolean $rw  Open mailbox as READ+WRITE?
     *
     * @return boolean  True if the server state differs from the browser
     *                  state.
     */
    public function changed($rw = null)
    {
        /* Forced viewport return. */
        if ($this->_vars->viewport->force) {
            return true;
        }

        /* Only update search mailboxes on forced refreshes. */
        if ($this->indices->mailbox->search) {
            return !empty($this->_vars->forceUpdate);
        }

        if (!$this->_vars->viewport->cacheid) {
            return false;
        }

        /* We know we are going to be dealing with this mailbox, so select it
         * on the IMAP server (saves some STATUS calls). */
        if (!is_null($rw)) {
            try {
                $this->indices->mailbox->imp_imap->openMailbox($this->indices->mailbox, $rw ? Horde_Imap_Client::OPEN_READWRITE : Horde_Imap_Client::OPEN_AUTO);
            } catch (IMP_Imap_Exception $e) {
                $e->notify();
                return null;
            }
        }

        return ($this->indices->mailbox->cacheid_date != $this->_vars->viewport->cacheid);
    }

    /**
     * Setup environment for compose actions.
     *
     * Variables used:
     *   - composeCache: (string) The IMP_Compose cache identifier.
     *   - from: (string) From address to use.
     *   - identity: (integer) The identity to use
     *
     * @param string $action  AJAX action.
     *
     * @return array  An array with the following values:
     *   - (object) AJAX base return object (with action and success
     *     parameters defined).
     *   - (IMP_Compose) The IMP_Compose object for the message.
     *   - (array) The list of headers for the object.
     *   - (Horde_Prefs_Identity) The identity used for the composition.
     *
     * @throws Horde_Exception
     */
    public function composeSetup($action)
    {
        global $injector, $prefs;

        /* Set up identity. */
        $identity = $injector->getInstance('IMP_Identity');
        if (isset($this->_vars->identity) &&
            !$prefs->isLocked('default_identity')) {
            $identity->setDefault($this->_vars->identity);
        }

        /* Set up the From address based on the identity. */
        $headers = array(
            'from' => strval($identity->getFromLine(null, $this->_vars->from)),
            'to' => $this->_vars->to,
            'cc' => $this->_vars->cc,
            'bcc' => $this->_vars->bcc,
            'subject' => $this->_vars->subject
        );

        $imp_compose = $injector->getInstance('IMP_Factory_Compose')->create($this->_vars->composeCache);

        $result = new stdClass;
        $result->action = $action;
        $result->success = 1;

        return array($result, $imp_compose, $headers, $identity);
    }

    /**
     * Processes delete message requests.
     * See the list of variables needed for viewPortData().
     *
     * @param IMP_Indices $indices  An indices object.
     * @param boolean $changed      If true, add full ViewPort information.
     * @param boolean $force        If true, forces addition of disappear
     *                              information.
     */
    public function deleteMsgs(IMP_Indices $indices, $changed, $force = false)
    {
        /* Check if we need to update thread information. */
        if (!$changed) {
            $changed = ($this->indices->mailbox->getSort()->sortby == Horde_Imap_Client::SORT_THREAD);
        }

        if ($changed) {
            $vp = $this->viewPortData(true);
            $this->addTask('viewport', $vp);
        } elseif (($indices instanceof IMP_Indices_Mailbox) &&
                  ($force || $this->indices->mailbox->hideDeletedMsgs(true))) {
            $vp = new IMP_Ajax_Application_Viewport($this->indices->mailbox);
            $vp->disappear = $indices->buids[strval($this->indices->mailbox)];
            $this->addTask('viewport', $vp);
        }

        $this->queue->poll(array_keys($indices->indices()));
    }

}
