1<?php
2/**
3 * Copyright 2012-2017 Horde LLC (http://www.horde.org/)
4 *
5 * See the enclosed file COPYING for license information (GPL). If you
6 * did not receive this file, see http://www.horde.org/licenses/gpl.
7 *
8 * @category  Horde
9 * @copyright 2012-2017 Horde LLC
10 * @license   http://www.horde.org/licenses/gpl GPL
11 * @package   IMP
12 */
13
14/**
15 * Defines common (i.e. used in dynamic and smartmobile views) AJAX actions
16 * used in IMP.
17 *
18 * @author    Michael Slusarz <slusarz@horde.org>
19 * @category  Horde
20 * @copyright 2012-2017 Horde LLC
21 * @license   http://www.horde.org/licenses/gpl GPL
22 * @package   IMP
23 */
24class IMP_Ajax_Application_Handler_Common extends Horde_Core_Ajax_Application_Handler
25{
26    /**
27     * AJAX action: Poll mailboxes.
28     *
29     * See the list of variables needed for IMP_Ajax_Application#changed() and
30     * IMP_Ajax_Application#viewPortData().
31     *
32     * @return boolean  True.
33     */
34    public function poll()
35    {
36        /* Actual polling handled by the global 'poll' handler. Still need
37         * separate poll action because there are other tasks done when
38         * specifically requesting a poll. */
39
40        $this->_base->queue->quota($this->_base->indices->mailbox, false);
41
42        if ($this->_base->changed()) {
43            $this->_base->addTask('viewport', $this->_base->viewPortData(true));
44        }
45
46        return true;
47    }
48
49    /**
50     * AJAX action: Output ViewPort data.
51     *
52     * See the list of variables needed for IMP_Ajax_Appication#changed() and
53     * IMP_Ajax_Application#viewPortData().
54     * Additional variables used (contained in 'viewport' parameter):
55     *   - checkcache: (integer) If 1, only send data if cache has been
56     *                 invalidated.
57     *   - rangeslice: (string) Range slice. See js/viewport.js.
58     *   - sortby: (integer) The Horde_Imap_Client sort constant.
59     *   - sortdir: (integer) 0 for ascending, 1 for descending.
60     *
61     * @return boolean  True on success, false on failure.
62     */
63    public function viewPort()
64    {
65        global $notification, $session;
66
67        if (!$this->_base->indices->mailbox) {
68            /* Sanity checking only - this would only happen by direct
69             * access, so don't worry about clean error handling. */
70            return false;
71        }
72
73        $vp_vars = $this->vars->viewport;
74
75        /* Change sort preferences if necessary. */
76        if (isset($vp_vars->sortby) || isset($vp_vars->sortdir)) {
77            $this->_base->indices->mailbox->setSort(
78                isset($vp_vars->sortby) ? $vp_vars->sortby : null,
79                isset($vp_vars->sortdir) ? $vp_vars->sortdir : null
80            );
81
82            /* Ensure that results are updated for search mailboxes. */
83            $this->vars->forceUpdate = true;
84        }
85
86        /* Toggle hide deleted preference if necessary. */
87        if (isset($vp_vars->delhide)) {
88            $this->_base->indices->mailbox->setHideDeletedMsgs($vp_vars->delhide);
89        }
90
91        $changed = $this->_base->changed(true);
92
93        if (is_null($changed)) {
94            $this->_base->addTask('viewport', new IMP_Ajax_Application_Viewport($this->_base->indices->mailbox));
95            return true;
96        }
97
98        $this->_base->queue->poll($this->_base->indices->mailbox);
99
100        $result = false;
101        if ($changed || $vp_vars->rangeslice || !$vp_vars->checkcache) {
102            /* Ticket #7422: Listing messages may be a long-running operation
103             * so close the session while we are doing it to prevent
104             * deadlocks. */
105            $session->close();
106
107            try {
108                $vp = $this->_base->viewPortData($changed);
109                $result = true;
110            } catch (Exception $e) {
111                $vp = new IMP_Ajax_Application_Viewport_Error($this->_base->indices->mailbox);
112            }
113
114            /* Reopen the session. */
115            $session->start();
116
117            if ($result === false) {
118                $notification->push($e, 'horde.error');
119            }
120
121            $this->_base->addTask('viewport', $vp);
122        }
123
124        $this->_base->queue->quota($this->_base->indices->mailbox, $vp_vars->checkcache);
125
126        return $result;
127    }
128
129    /**
130     * AJAX action: Move messages.
131     *
132     * See the list of variables needed for IMP_Ajax_Application#changed(),
133     * IMP_Ajax_Application#deleteMsgs(), and
134     * IMP_Ajax_Application#checkUidvalidity(). Mailbox/indices form
135     * parameters needed. Additional variables used:
136     *   - mboxto: (string) Mailbox to move the message to (base64url
137     *             encoded).
138     *
139     * @return boolean  True on success, false on failure.
140     */
141    public function moveMessages()
142    {
143        if ((!isset($this->vars->mboxto) && !isset($this->vars->newmbox)) ||
144            !count($this->_base->indices)) {
145            $this->_base->queue->flagReplace($this->_base->indices);
146            return false;
147        }
148
149        $change = $this->_base->changed(true);
150
151        if (is_null($change)) {
152            $this->_base->queue->flagReplace($this->_base->indices);
153            return false;
154        }
155
156        if (isset($this->vars->newmbox)) {
157            $mbox = IMP_Mailbox::prefFrom($this->vars->newmbox);
158            $newMbox = true;
159        } else {
160            $mbox = IMP_Mailbox::formFrom($this->vars->mboxto);
161            $newMbox = false;
162        }
163
164        $result = $GLOBALS['injector']
165            ->getInstance('IMP_Message')
166            ->copy($mbox, 'move', $this->_base->indices, array('create' => $newMbox));
167
168        if ($result) {
169            $this->_base->deleteMsgs($this->_base->indices, $change, true);
170            $this->_base->queue->poll($mbox);
171            return true;
172        }
173
174        $this->_base->checkUidvalidity();
175        $this->_base->queue->flagReplace($this->_base->indices);
176
177        return false;
178    }
179
180    /**
181     * AJAX action: Copy messages.
182     *
183     * See the list of variables needed for
184     * IMP_Ajax_Application#_checkUidvalidity(). Mailbox/indices form
185     * parameters needed. Additional variables used:
186     *   - mboxto: (string) Mailbox to copy the message to (base64url
187     *             encoded).
188     *
189     * @return boolean  True on success, false on failure.
190     */
191    public function copyMessages()
192    {
193        if ((!isset($this->vars->mboxto) && !isset($this->vars->newmbox)) ||
194            !count($this->_base->indices)) {
195            return false;
196        }
197
198        if (isset($this->vars->newmbox)) {
199            $mbox = IMP_Mailbox::prefFrom($this->vars->newmbox);
200            $newMbox = true;
201        } else {
202            $mbox = IMP_Mailbox::formFrom($this->vars->mboxto);
203            $newMbox = false;
204        }
205
206        $result = $GLOBALS['injector']
207            ->getInstance('IMP_Message')
208            ->copy($mbox, 'copy', $this->_base->indices, array('create' => $newMbox));
209
210        if ($result) {
211            $this->_base->queue->poll($mbox);
212            return true;
213        }
214
215        $this->_base->checkUidvalidity();
216
217        return false;
218    }
219
220    /**
221     * AJAX action: Delete messages.
222     *
223     * See the list of variables needed for IMP_Ajax_Application#changed(),
224     * IMP_Ajax_Application#deleteMsgs(), and
225     * IMP_Ajax_Application@checkUidvalidity(). Mailbox/indices form
226     * parameters needed.
227     *
228     * @return boolean  True on success, false on failure.
229     */
230    public function deleteMessages()
231    {
232        if (count($this->_base->indices)) {
233            $change = $this->_base->changed(true);
234
235            if ($GLOBALS['injector']->getInstance('IMP_Message')->delete($this->_base->indices)) {
236                $this->_base->deleteMsgs($this->_base->indices, $change);
237                return true;
238            }
239
240            if (!is_null($change)) {
241                $this->_base->checkUidvalidity();
242            }
243        }
244
245        $this->_base->queue->flagReplace($this->_base->indices);
246
247        return false;
248    }
249
250    /**
251     * AJAX action: Report message as [not]spam.
252     *
253     * See the list of variables needed for IMP_Ajax_Application#changed(),
254     * IMP_Ajax_Application#deleteMsgs(), and
255     * IMP_Ajax_Application#checkUidvalidity(). Mailbox/indices form
256     * parameters needed. Additional variables used:
257     *   - spam: (integer) 1 to mark as spam, 0 to mark as innocent.
258     *
259     * @return boolean  True on success.
260     */
261    public function reportSpam()
262    {
263        global $injector;
264
265        $change = $this->_base->changed(true);
266
267        if ($injector->getInstance('IMP_Factory_Spam')->create($this->vars->spam ? IMP_Spam::SPAM : IMP_Spam::INNOCENT)->report($this->_base->indices)) {
268            $this->_base->deleteMsgs($this->_base->indices, $change);
269            return true;
270        }
271
272        if (!is_null($change)) {
273            $this->_base->checkUidvalidity();
274        }
275
276        return false;
277    }
278
279    /**
280     * AJAX action: Get reply data.
281     *
282     * See the list of variables needed for
283     * IMP_Ajax_Application#checkUidvalidity(). Mailbox/indices form
284     * parameters needed. Additional variables used:
285     *   - headeronly: (boolean) Only return header information (DEFAULT:
286     *                 false).
287     *   - format: (string) The format to force to ('text' or 'html')
288     *             (DEFAULT: Auto-determined).
289     *   - imp_compose: (string) The IMP_Compose cache identifier.
290     *   - type: (string) See IMP_Compose::replyMessage().
291     *
292     * @return mixed  False on failure, or an object with the following
293     *                entries:
294     *   - addr: (array) List of addresses (to, cc, bcc).
295     *   - body: (string) The body text of the message.
296     *   - format: (string) Either 'text' or 'html'.
297     *   - identity: (integer) The identity ID to use for this message.
298     *   - opts: (array) Additional options needed for DimpCompose.fillForm().
299     *   - subject: (string) Subject value.
300     *   - type: (string) The input 'type' value.
301     */
302    public function getReplyData()
303    {
304        /* Can't open session read-only since we need to store the message
305         * cache id. */
306
307        try {
308            $compose = $this->_base->initCompose();
309
310            $reply_msg = $compose->compose->replyMessage($compose->ajax->reply_map[$this->vars->type], $compose->contents, array(
311                'format' => $this->vars->format
312            ));
313
314            $result = $this->vars->headeronly
315                ? $compose->ajax->getBaseResponse($reply_msg)
316                : $compose->ajax->getResponse($reply_msg);
317        } catch (Horde_Exception $e) {
318            $GLOBALS['notification']->push($e);
319            $this->_base->checkUidvalidity();
320            $result = false;
321        }
322
323        return $result;
324    }
325
326    /**
327     * Get forward compose data.
328     *
329     * See the list of variables needed for checkUidvalidity().
330     * Mailbox/indices form parameters needed.  Additional variables used:
331     *   - dataonly: (boolean) Only return data information (DEFAULT: false).
332     *   - format: (string) The format to force to ('text' or 'html')
333     *             (DEFAULT: Auto-determined).
334     *   - imp_compose: (string) The IMP_Compose cache identifier.
335     *   - type: (string) Forward type.
336     *
337     * @return mixed  False on failure, or an object with the following
338     *                entries:
339     *   - body: (string) The body text of the message.
340     *   - format: (string) Either 'text' or 'html'.
341     *   - header: (array) The headers of the message.
342     *   - identity: (integer) The identity ID to use for this message.
343     *   - opts: (array) Additional options needed for DimpCompose.fillForm().
344     *   - type: (string) The input 'type' value.
345     */
346    public function getForwardData()
347    {
348        global $notification;
349
350        /* Can't open session read-only since we need to store the message
351         * cache id. */
352
353        try {
354            $compose = $this->_base->initCompose();
355
356            $type = $compose->ajax->forward_map[$this->vars->type];
357            $fwd_msg = $compose->compose->forwardMessage($type, $compose->contents, true, array(
358                'format' => $this->vars->format
359            ));
360
361            if ($this->vars->dataonly) {
362                $result = $compose->ajax->getBaseResponse($fwd_msg);
363                $result->body = $fwd_msg['body'];
364                $result->format = $fwd_msg['format'];
365                $atc = ($type != IMP_Compose::FORWARD_BODY);
366            } else {
367                $result = $compose->ajax->getResponse($fwd_msg);
368                $atc = true;
369            }
370
371            if ($atc) {
372                $this->_base->queue->attachment($compose->compose, $fwd_msg['type']);
373            }
374        } catch (Horde_Exception $e) {
375            $notification->push($e);
376            $this->_base->checkUidvalidity();
377            $result = false;
378        }
379
380        return $result;
381    }
382
383    /**
384     * AJAX action: Get compose redirect data.
385     *
386     * Mailbox/indices form parameters needed.
387     *
388     * @return mixed  False on failure, or an object with the following
389     *                entries:
390     *   - imp_compose: (string) The IMP_Compose cache identifier.
391     *   - type: (string) The input 'type' value.
392     */
393    public function getRedirectData()
394    {
395        $compose = $this->_base->initCompose();
396
397        $compose->compose->redirectMessage($compose->contents->getIndicesOb());
398
399        $ob = new stdClass;
400        $ob->type = $this->vars->type;
401
402        return $ob;
403    }
404
405    /**
406     * AJAX action: Get resume data.
407     *
408     * See the list of variables needed for
409     * IMP_Ajax_Application#checkUidvalidity(). Mailbox/indices form
410     * parameters needed. Additional variables used:
411     *   - format: (string) The format to force to ('text' or 'html')
412     *             (DEFAULT: Auto-determined).
413     *   - imp_compose: (string) The IMP_Compose cache identifier.
414     *   - type: (string) Resume type: one of 'editasnew', 'resume',
415     *           'template', 'template_edit'.
416     *
417     * @return mixed  False on failure, or an object with the following
418     *                entries:
419     *   - addr: (array) List of addresses (to, cc, bcc).
420     *   - body: (string) The body text of the message.
421     *   - format: (string) Either 'text' or 'html'.
422     *   - identity: (integer) The identity ID to use for this message.
423     *   - opts: (array) Additional options (atc, priority, readreceipt).
424     *   - subject: (string) Subject value.
425     *   - type: (string) The input 'type' value.
426     */
427    public function getResumeData()
428    {
429        try {
430            $compose = $this->_base->initCompose();
431
432            switch ($this->vars->type) {
433            case 'editasnew':
434                $resume = $compose->compose->editAsNew($compose->contents->getIndicesOb(), array(
435                    'format' => $this->vars->format
436                ));
437                break;
438
439            case 'resume':
440                $resume = $compose->compose->resumeDraft($compose->contents->getIndicesOb(), array(
441                    'format' => $this->vars->format
442                ));
443                break;
444
445            case 'template':
446                $resume = $compose->compose->useTemplate($compose->contents->getIndicesOb(), array(
447                    'format' => $this->vars->format
448                ));
449                break;
450
451            case 'template_edit':
452                $resume = $compose->compose->editTemplate($compose->contents->getIndicesOb());
453                break;
454            }
455
456            $result = $compose->ajax->getResponse($resume);
457            $this->_base->queue->attachment($compose->compose, $this->vars->type);
458        } catch (Horde_Exception $e) {
459            $GLOBALS['notification']->push($e);
460            $this->_base->checkUidvalidity();
461            $result = false;
462        }
463
464        return $result;
465    }
466
467    /**
468     * AJAX action: Cancel compose.
469     *
470     * Variables used:
471     *   - discard: (boolean) If true, discard draft.
472     *   - imp_compose: (string) The IMP_Compose cache identifier.
473     *
474     * @return boolean  True.
475     */
476    public function cancelCompose()
477    {
478        $GLOBALS['injector']->getInstance('IMP_Factory_Compose')->create($this->vars->imp_compose)->destroy($this->vars->discard ? 'discard' : 'cancel');
479        return true;
480    }
481
482    /**
483     * AJAX action: Send a message.
484     *
485     * See the list of variables needed for
486     * IMP_Ajax_Application#composeSetup(). Additional variables used:
487     *   - addr_ac: (string) TODO
488     *   - encrypt: (integer) The encryption method to use (IMP ENCRYPT
489     *              constants).
490     *   - html: (integer) In HTML compose mode?
491     *   - message: (string) The message text.
492     *   - pgp_attach_pubkey: (boolean) True if PGP public key should be
493     *                        attached to the message.
494     *   - priority: (string) The priority of the message.
495     *   - request_read_receipt: (boolean) Add request read receipt header?
496     *   - save_attachments_select: (boolean) Whether to save attachments.
497     *   - save_sent_mail: (boolean) True if saving sent mail.
498     *   - save_sent_mail_mbox: (string) base64url encoded version of sent
499     *                          mail mailbox to use.
500     *   - vcard_attach: (boolean) Attach user's vCard to the message?
501     *
502     * @return object  An object with the following entries:
503     *   - action: (string) The AJAX action string
504     *   - draft_delete: (integer) If set, remove auto-saved drafts.
505     *   - encryptjs: (array) Javascript to run after encryption failure.
506     *   - flag: (array) See IMP_Ajax_Queue::add().
507     *   - identity: (integer) If set, this is the identity that is tied to
508     *               the current recipient address.
509     *   - success: (integer) 1 on success, 0 on failure.
510     */
511    public function sendMessage()
512    {
513        global $injector, $notification, $page_output, $prefs;
514
515        try {
516            list($result, $imp_compose, $headers, $identity) = $this->_base->composeSetup('sendMessage');
517            if (!IMP_Compose::canCompose()) {
518                $result->success = 0;
519                return $result;
520            }
521        } catch (Horde_Exception $e) {
522            $notification->push($e);
523
524            $result = new stdClass;
525            $result->action = 'sendMessage';
526            $result->success = 0;
527            return $result;
528        }
529
530        $headers['replyto'] = $identity->getValue('replyto_addr');
531
532        $sm_displayed = !$prefs->isLocked(IMP_Mailbox::MBOX_SENT);
533
534        try {
535            $imp_compose->buildAndSendMessage(
536                $this->vars->message,
537                $headers,
538                $identity,
539                array(
540                    'encrypt' => ($prefs->isLocked('default_encrypt')
541                        ? $prefs->getValue('default_encrypt')
542                        : $this->vars->encrypt),
543                    'html' => $this->vars->html,
544                    'pgp_attach_pubkey' => $this->vars->pgp_attach_pubkey,
545                    'priority' => $this->vars->priority,
546                    'readreceipt' => $this->vars->request_read_receipt,
547                    'save_sent' => ($sm_displayed
548                        ? (bool)$this->vars->save_sent_mail
549                        : $identity->getValue('save_sent_mail')),
550                    'sent_mail' => ($sm_displayed
551                        ? (isset($this->vars->save_sent_mail_mbox)
552                            ? IMP_Mailbox::formFrom($this->vars->save_sent_mail_mbox)
553                            : $identity->getValue(IMP_Mailbox::MBOX_SENT))
554                        : $identity->getValue(IMP_Mailbox::MBOX_SENT)),
555                    'signature' => $this->vars->signature,
556                    'strip_attachments' =>
557                        (isset($this->vars->save_attachments_select) &&
558                         !$this->vars->save_attachments_select) ||
559                        (!isset($this->vars->save_attachments_select) &&
560                         strcasecmp($prefs->getValue('save_attachments'), 'always') !== 0),
561                    'vcard_attach' => ($this->vars->vcard_attach
562                        ? $identity->getValue('fullname')
563                        : null)
564                )
565            );
566            $notification->push(
567                empty($headers['subject'])
568                    ? _("Message sent successfully.")
569                    : sprintf(
570                        _("Message \"%s\" sent successfully."),
571                        Horde_String::truncate($headers['subject'])
572                    ),
573                'horde.success'
574            );
575        } catch (IMP_Compose_Exception_Address $e) {
576            $this->_handleBadComposeAddr($e);
577            $result->success = 0;
578            return $result;
579        } catch (IMP_Compose_Exception $e) {
580            $result->success = 0;
581            $this->_base->queue->compose($imp_compose);
582            if (is_null($e->tied_identity)) {
583                $notify_level = 'horde.error';
584            } else {
585                $result->identity = $e->tied_identity;
586                $notify_level = 'horde.warning';
587            }
588
589            if ($e->encrypt) {
590                $imp_ui = $injector->getInstance('IMP_Compose_Ui');
591                switch ($e->encrypt) {
592                case 'pgp_symmetric_passphrase_dialog':
593                    $imp_ui->passphraseDialog('pgp_symm', $imp_compose->getCacheId());
594                    break;
595
596                case 'pgp_passphrase_dialog':
597                    $imp_ui->passphraseDialog('pgp');
598                    break;
599
600                case 'smime_passphrase_dialog':
601                    $imp_ui->passphraseDialog('smime');
602                    break;
603                }
604
605                Horde::startBuffer();
606                $page_output->outputInlineScript(true);
607                if ($js_inline = Horde::endBuffer()) {
608                    $result->encryptjs = array($js_inline);
609                }
610            } else {
611                /* Don't push notification if showing passphrase dialog -
612                 * passphrase dialog contains the necessary information. */
613                $notification->push($e, $notify_level);
614            }
615
616            return $result;
617        }
618
619        /* Remove any auto-saved drafts. */
620        if ($imp_compose->hasDrafts()) {
621            $result->draft_delete = 1;
622        }
623
624        if ($indices = $imp_compose->getMetadata('indices')) {
625            /* Update maillog information. */
626            $this->_base->queue->maillog($indices);
627        }
628
629        $imp_compose->destroy('send');
630
631        return $result;
632    }
633
634    /**
635     * Redirect the message.
636     *
637     * Variables used:
638     *   - composeCache: (string) The IMP_Compose cache identifier.
639     *   - redirect_to: (string) The address(es) to redirect to.
640     *
641     * @return object  An object with the following entries:
642     *   - action: (string) 'redirectMessage'.
643     *   - success: (integer) 1 on success, 0 on failure.
644     */
645    public function redirectMessage()
646    {
647        $result = new stdClass;
648        $result->action = 'redirectMessage';
649        $result->success = 1;
650
651        try {
652            $imp_compose = $GLOBALS['injector']->getInstance('IMP_Factory_Compose')->create($this->vars->composeCache);
653            $res = $imp_compose->sendRedirectMessage($this->vars->redirect_to);
654
655            foreach ($res as $val) {
656                $subject = $val->headers->getValue('subject');
657                $GLOBALS['notification']->push(empty($subject) ? _("Message redirected successfully.") : sprintf(_("Message \"%s\" redirected successfully."), Horde_String::truncate($subject)), 'horde.success');
658
659                $this->_base->queue->maillog(
660                    new IMP_Indices($val->mbox, $val->uid)
661                );
662            }
663        } catch (Horde_Exception $e) {
664            $GLOBALS['notification']->push($e);
665            $result->success = 0;
666        }
667
668        return $result;
669    }
670
671    /**
672     * Generate data necessary to display a message.
673     *
674     * See the list of variables needed for changed() and
675     * checkUidvalidity(). Mailbox/indices form parameters needed. Additional
676     * variables used:
677     *   - peek: (integer) If set, don't set seen flag.
678     *   - preview: (integer) If set, return preview data. Otherwise, return
679     *              full data.
680     *
681     * @return object  Object with the following entries:
682     *   - buid: (integer) The message BUID.
683     *   - error: (string) On error, the error string.
684     *   - errortype: (string) On error, the error type.
685     *   - view: (string) The view ID.
686     */
687    public function showMessage()
688    {
689        $result = new stdClass;
690        $result->buid = intval($this->vars->buid);
691        $result->view = $this->vars->view;
692
693        try {
694            $change = $this->_base->changed(true);
695            if (is_null($change)) {
696                throw new IMP_Exception(_("Could not open mailbox."));
697            }
698
699            $this->_base->queue->message($this->_base->indices, $this->vars->preview, $this->vars->peek);
700
701            /* Explicitly load the message here; non-existent messages are
702             * ignored when the Ajax queue is processed. Place the check AFTER
703             * the message() command, as the previous command will open the
704             * mailbox R/W, an optimization. */
705            $GLOBALS['injector']->getInstance('IMP_Factory_Contents')->create($this->_base->indices);
706        } catch (Exception $e) {
707            $result->error = $e->getMessage();
708            $result->errortype = 'horde.error';
709
710            $change = true;
711        }
712
713        if ($this->vars->preview || $this->vars->viewport->force) {
714            if ($change) {
715                $this->_base->addTask('viewport', $this->_base->viewPortData(true));
716            } elseif ($this->_base->indices->mailbox->cacheid_date != $this->vars->viewport->cacheid) {
717                /* Cache ID has changed due to viewing this message. So update
718                 * the cacheid in the ViewPort. */
719                $this->_base->addTask('viewport', new IMP_Ajax_Application_Viewport($this->_base->indices->mailbox));
720            }
721
722            if ($this->vars->preview) {
723                $this->_base->queue->poll(array_keys($this->_base->indices->indices()));
724            }
725        }
726
727        return $result;
728    }
729
730    /* Internal methods. */
731
732    /**
733     * Handle bad addresses entered during a compose.
734     *
735     * @param IMP_Compose_Exception_Address $e  The address exception.
736     */
737    protected function _handleBadComposeAddr(IMP_Compose_Exception_Address $e)
738    {
739        global $notification;
740
741        $addr_ac = $this->vars->addr_ac
742            ? json_decode($this->vars->addr_ac, true)
743            : array();
744
745        foreach ($e as $val) {
746            $addr = strval($val->address);
747            $notification->push($val->error, 'horde.warning');
748
749            foreach ($addr_ac as $val2) {
750                if ($addr == $val2['addr']) {
751                    $this->_base->queue->compose_addr(
752                        $val2['id'],
753                        $val2['itemid'],
754                        ($val->level == $e::BAD) ? 'impACListItemBad' : 'impACListItemWarn'
755                    );
756                }
757            }
758        }
759    }
760
761}
762