1<?php
2/**
3 * Copyright 1999-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 1999-2017 Horde LLC
10 * @license   http://www.horde.org/licenses/gpl21 GPL
11 * @package   IMP
12 */
13
14/**
15 * Compose page for the basic view.
16 *
17 * Copyright 1999-2017 Horde LLC (http://www.horde.org/)
18 *
19 * See the enclosed file COPYING for license information (GPL). If you
20 * did not receive this file, see http://www.horde.org/licenses/gpl.
21 *
22 * @author    Chuck Hagenbuch <chuck@horde.org>
23 * @author    Michael Slusarz <slusarz@horde.org>
24 * @category  Horde
25 * @copyright 1999-2017 Horde LLC
26 * @license   http://www.horde.org/licenses/gpl21 GPL
27 * @package   IMP
28 */
29class IMP_Basic_Compose extends IMP_Basic_Base
30{
31    /**
32     */
33    protected function _init()
34    {
35        global $browser, $injector, $notification, $page_output, $prefs, $registry, $session;
36
37        /* Mailto link handler: redirect based on current view. */
38        if ($this->vars->actionID == 'mailto_link') {
39            switch ($registry->getView()) {
40            case Horde_Registry::VIEW_DYNAMIC:
41                IMP_Dynamic_Compose::url()->add($_GET)->redirect();
42                exit;
43
44            case Horde_Registry::VIEW_MINIMAL:
45                IMP_Minimal_Compose::url()->add($_GET)->redirect();
46                exit;
47            }
48        }
49
50        /* The message headers and text. */
51        $header = array();
52        $msg = '';
53
54        $redirect = $resume = $spellcheck = false;
55        $oldrtemode = $rtemode = null;
56
57        /* Is this a popup window? */
58        if ($isPopup = ($prefs->getValue('compose_popup') || $this->vars->popup)) {
59            $page_output->topbar = $page_output->sidebar = false;
60        }
61
62        /* Set the current identity. */
63        $identity = $injector->getInstance('IMP_Identity');
64        if (!$prefs->isLocked('default_identity') &&
65            !is_null($this->vars->identity)) {
66            $identity->setDefault($this->vars->identity);
67        }
68
69        if ($this->vars->actionID) {
70            switch ($this->vars->actionID) {
71            case 'draft':
72            case 'editasnew':
73            case 'forward_attach':
74            case 'forward_auto':
75            case 'forward_body':
76            case 'forward_both':
77            case 'fwd_digest':
78            case 'mailto':
79            case 'mailto_link':
80            case 'reply':
81            case 'reply_all':
82            case 'reply_auto':
83            case 'reply_list':
84            case 'redirect_compose':
85            case 'template':
86            case 'template_edit':
87            case 'template_new':
88                /* These are all safe actions that might be invoked without a
89                 * token. */
90                break;
91
92            default:
93                try {
94                    $session->checkToken($this->vars->compose_requestToken);
95                } catch (Horde_Exception $e) {
96                    $notification->push($e);
97                    $this->vars->actionID = null;
98                }
99            }
100        }
101
102        /* Check for duplicate submits. */
103        if ($reload = $this->vars->compose_formToken) {
104            try {
105                $session->checkNonce($reload);
106            } catch (Horde_Exception $e) {
107                $notification->push(_("You have already submitted this page."), 'horde.error');
108                $this->vars->actionID = null;
109            }
110        }
111
112        /* Determine if compose mode is disabled. */
113        $compose_disable = !IMP_Compose::canCompose();
114
115        /* Determine if mailboxes are readonly. */
116        $draft = IMP_Mailbox::getPref(IMP_Mailbox::MBOX_DRAFTS);
117        $readonly_drafts = $draft && $draft->readonly;
118
119        $sent_mail = $identity->getValue(IMP_Mailbox::MBOX_SENT);
120        if (!$sent_mail) {
121            $readonly_sentmail = $save_sent_mail = false;
122        } elseif ($sent_mail->readonly) {
123            $readonly_sentmail = true;
124            $save_sent_mail = false;
125        } else {
126            $readonly_sentmail = false;
127            $save_sent_mail = $reload
128                ? (bool)$this->vars->save_sent_mail
129                : true;
130        }
131
132        /* Initialize the IMP_Compose:: object. */
133        $imp_compose = $injector->getInstance('IMP_Factory_Compose')->create($this->vars->composeCache);
134
135        /* Init objects. */
136        $imp_imap = $injector->getInstance('IMP_Factory_Imap')->create();
137        $imp_ui = new IMP_Compose_Ui();
138
139        /* Determine the composition type - text or HTML.
140           $rtemode is null if browser does not support it. */
141        if ($session->get('imp', 'rteavail')) {
142            if ($prefs->isLocked('compose_html')) {
143                $rtemode = $prefs->getValue('compose_html');
144            } else {
145                $rtemode = $this->vars->rtemode;
146                if (is_null($rtemode)) {
147                    $rtemode = $prefs->getValue('compose_html');
148                } else {
149                    $rtemode = intval($rtemode);
150                    $oldrtemode = intval($this->vars->oldrtemode);
151                }
152            }
153        }
154
155        /* Update the file attachment information. */
156        $attach_upload = $imp_compose->canUploadAttachment();
157        if ($attach_upload) {
158            /* Only notify if we are reloading the compose screen. */
159            $notify = !in_array($this->vars->actionID, array('send_message', 'save_draft'));
160
161            $deleteList = Horde_Util::getPost('delattachments', array());
162
163            /* Update the attachment information. */
164            foreach ($imp_compose as $key => $val) {
165                if (!in_array($key, $deleteList)) {
166                    $val->getPart()->setDescription($this->vars->filter('file_description_' . $key));
167                    $imp_compose[$key] = $val;
168                }
169            }
170
171            /* Delete attachments. */
172            foreach ($deleteList as $val) {
173                if ($notify) {
174                    $notification->push(sprintf(_("Deleted attachment \"%s\"."), $imp_compose[$val]->getPart()->getName(true)), 'horde.success');
175                }
176                unset($imp_compose[$val]);
177            }
178
179            /* Add attachments. */
180            for ($i = 1, $fcount = count($_FILES); $i <= $fcount; ++$i) {
181                if (isset($_FILES['upload_' . $i]) &&
182                    strlen($_FILES['upload_' . $i]['name'])) {
183                    try {
184                        $atc_ob = $imp_compose->addAttachmentFromUpload('upload_' . $i);
185                        if ($atc_ob[0] instanceof IMP_Compose_Exception) {
186                            throw $atc_ob[0];
187                        }
188                        if ($notify) {
189                            $notification->push(sprintf(_("Added \"%s\" as an attachment."), $atc_ob[0]->getPart()->getName()), 'horde.success');
190                        }
191                    } catch (IMP_Compose_Exception $e) {
192                        /* Any error will cancel the current action. */
193                        $this->vars->actionID = null;
194                        $notification->push($e, 'horde.error');
195                    }
196                }
197            }
198        }
199
200        /* Get message priority. */
201        $priority = $this->vars->get('priority', 'normal');
202
203        /* Request read receipt? */
204        $request_read_receipt = (bool)$this->vars->request_read_receipt;
205
206        /* Run through the action handlers. */
207        $this->title = _("New Message");
208        switch ($this->vars->actionID) {
209        case 'mailto':
210            try {
211                $contents = $this->_getContents();
212            } catch (IMP_Exception $e) {
213                $notification->push($e, 'horde.error');
214                break;
215            }
216
217            $imp_headers = $contents->getHeader();
218            $header['to'] = '';
219            if ($this->vars->mailto) {
220                $header['to'] = $imp_headers->getValue('to');
221            }
222            if (empty($header['to'])) {
223                ($header['to'] = strval($imp_headers->getOb('from'))) ||
224                ($header['to'] = strval($imp_headers->getOb('reply-to')));
225            }
226            break;
227
228        case 'mailto_link':
229            $clink = new IMP_Compose_Link($this->vars);
230            if (isset($clink->args['body'])) {
231                $msg = $clink->args['body'];
232            }
233            foreach (array('to', 'cc', 'bcc', 'subject') as $val) {
234                if (isset($clink->args[$val])) {
235                    $header[$val] = $clink->args[$val];
236                }
237            }
238            break;
239
240        case 'draft':
241        case 'editasnew':
242        case 'template':
243        case 'template_edit':
244            try {
245                switch ($this->vars->actionID) {
246                case 'draft':
247                    $result = $imp_compose->resumeDraft($this->indices);
248                    $resume = true;
249                    break;
250
251                case 'editasnew':
252                    $result = $imp_compose->editAsNew($this->indices);
253                    break;
254
255                case 'template':
256                    $result = $imp_compose->useTemplate($this->indices);
257                    break;
258
259                case 'template_edit':
260                    $result = $imp_compose->editTemplate($this->indices);
261                    $this->vars->template_mode = true;
262                    break;
263                }
264
265                if (!is_null($rtemode)) {
266                    $rtemode = ($result['format'] == 'html');
267                }
268                $msg = $result['body'];
269                $header = array_merge(
270                    $header,
271                    $this->_convertToHeader($result)
272                );
273                if (!is_null($result['identity']) &&
274                    ($result['identity'] != $identity->getDefault()) &&
275                    !$prefs->isLocked('default_identity')) {
276                    $identity->setDefault($result['identity']);
277                    $sent_mail = $identity->getValue(IMP_Mailbox::MBOX_SENT);
278                }
279                $priority = $result['priority'];
280                $request_read_receipt = $result['readreceipt'];
281            } catch (IMP_Compose_Exception $e) {
282                $notification->push($e);
283            }
284            break;
285
286        case 'reply':
287        case 'reply_all':
288        case 'reply_auto':
289        case 'reply_list':
290            try {
291                $contents = $this->_getContents();
292            } catch (IMP_Exception $e) {
293                $notification->push($e, 'horde.error');
294                break;
295            }
296
297            $reply_map = array(
298                'reply' => IMP_Compose::REPLY_SENDER,
299                'reply_all' => IMP_Compose::REPLY_ALL,
300                'reply_auto' => IMP_Compose::REPLY_AUTO,
301                'reply_list' => IMP_Compose::REPLY_LIST
302            );
303
304            $reply_msg = $imp_compose->replyMessage($reply_map[$this->vars->actionID], $contents, array(
305                'to' => $this->vars->to
306            ));
307            $msg = $reply_msg['body'];
308            $header = $this->_convertToHeader($reply_msg);
309            $format = $reply_msg['format'];
310
311            switch ($reply_msg['type']) {
312            case IMP_Compose::REPLY_SENDER:
313                $this->vars->actionID = 'reply';
314                $this->title = _("Reply:");
315                break;
316
317            case IMP_Compose::REPLY_ALL:
318                if ($this->vars->actionID == 'reply_auto') {
319                    $recip_list = $imp_compose->recipientList($header);
320                    if (!empty($recip_list['list'])) {
321                        $replyauto_all = count($recip_list['list']);
322                    }
323                }
324
325                $this->vars->actionID = 'reply_all';
326                $this->title = _("Reply to All:");
327                break;
328
329            case IMP_Compose::REPLY_LIST:
330                if ($this->vars->actionID == 'reply_auto') {
331                    $replyauto_list = true;
332                    if (($parse_list = $injector->getInstance('Horde_ListHeaders')->parse('list-id', $contents->getHeader()->getValue('list-id'))) &&
333                        !is_null($parse_list->label)) {
334                        $replyauto_list_id = $parse_list->label;
335                    }
336                }
337
338                $this->vars->actionID = 'reply_list';
339                $this->title = _("Reply to List:");
340                break;
341            }
342
343            if (!empty($reply_msg['lang'])) {
344                $reply_lang = array_values($reply_msg['lang']);
345            }
346
347            $this->title .= ' ' . $header['subject'];
348
349            if (!is_null($rtemode)) {
350                $rtemode = ($rtemode || ($format == 'html'));
351            }
352            break;
353
354        case 'replyall_revert':
355        case 'replylist_revert':
356            try {
357                $reply_msg = $imp_compose->replyMessage(
358                    IMP_Compose::REPLY_SENDER,
359                    $imp_compose->getContentsOb()
360                );
361                $header = $this->_convertToHeader($reply_msg);
362            } catch (IMP_Exception $e) {
363                $notification->push($e, 'horde.error');
364            }
365            break;
366
367        case 'forward_attach':
368        case 'forward_auto':
369        case 'forward_body':
370        case 'forward_both':
371            $fwd_map = array(
372                'forward_attach' => IMP_Compose::FORWARD_ATTACH,
373                'forward_auto' => IMP_Compose::FORWARD_AUTO,
374                'forward_body' => IMP_Compose::FORWARD_BODY,
375                'forward_both' => IMP_Compose::FORWARD_BOTH
376            );
377
378            try {
379                $fwd_msg = $imp_compose->forwardMessage(
380                    $fwd_map[$this->vars->actionID],
381                    $this->_getContents()
382                );
383            } catch (IMP_Exception $e) {
384                $notification->push($e, 'horde.error');
385                break;
386            }
387
388            $msg = $fwd_msg['body'];
389            $header = $this->_convertToHeader($fwd_msg);
390            $format = $fwd_msg['format'];
391            $rtemode = ($rtemode || (!is_null($rtemode) && ($format == 'html')));
392            $this->title = $fwd_msg['title'];
393            break;
394
395        case 'redirect_send':
396            try {
397                $num_msgs = $imp_compose->sendRedirectMessage($this->vars->to);
398                $imp_compose->destroy('send');
399                if ($isPopup) {
400                    if ($prefs->getValue('compose_confirm')) {
401                        $notification->push(ngettext("Message redirected successfully.", "Messages redirected successfully", count($num_msgs)), 'horde.success');
402                        $this->_popupSuccess();
403                        return;
404                    }
405                    echo Horde::wrapInlineScript(array('window.close();'));
406                } else {
407                    $notification->push(ngettext("Message redirected successfully.", "Messages redirected successfully", count($num_msgs)), 'horde.success');
408                    $this->_mailboxReturnUrl()->redirect();
409                }
410                exit;
411            } catch (Horde_Exception $e) {
412                $notification->push($e);
413                $this->vars->actionID = 'redirect_compose';
414            }
415            // Fall through.
416
417        case 'redirect_compose':
418            try {
419                $imp_compose->redirectMessage($this->indices);
420                $redirect = true;
421                $this->title = ngettext("Redirect", "Redirect Messages", count($this->indices));
422            } catch (IMP_Compose_Exception $e) {
423                $notification->push($e, 'horde.error');
424            }
425            break;
426
427        case 'auto_save_draft':
428        case 'save_draft':
429        case 'save_template':
430        case 'send_message':
431            // Drafts readonly is handled below.
432            if ($compose_disable &&
433                ($this->vars->actionID == 'send_message')) {
434                break;
435            }
436
437            try {
438                $header['from'] = strval($identity->getFromLine(null, $this->vars->from));
439            } catch (Horde_Exception $e) {
440                $header['from'] = '';
441                $notification->push($e);
442                break;
443            }
444
445            $header['to'] = $this->vars->to;
446            $header['cc'] = $this->vars->cc;
447            $header['bcc'] = $this->vars->bcc;
448
449            $header['subject'] = strval($this->vars->subject);
450            $message = strval($this->vars->message);
451
452            /* Save the draft. */
453            switch ($this->vars->actionID) {
454            case 'auto_save_draft':
455            case 'save_draft':
456            case 'save_template':
457                if (!$readonly_drafts ||
458                    ($this->vars->actionID == 'save_template')) {
459                    $save_opts = array(
460                        'html' => $rtemode,
461                        'priority' => $priority,
462                        'readreceipt' => $request_read_receipt
463                    );
464
465                    try {
466                        switch ($this->vars->actionID) {
467                        case 'save_template':
468                            $result = $imp_compose->saveTemplate($header, $message, $save_opts);
469                            break;
470
471                        default:
472                            $result = $imp_compose->saveDraft($header, $message, $save_opts);
473                            break;
474                        }
475
476                        /* Closing draft if requested by preferences. */
477                        switch ($this->vars->actionID) {
478                        case 'save_draft':
479                            if ($isPopup) {
480                                if ($prefs->getValue('close_draft')) {
481                                    $imp_compose->destroy('save_draft');
482                                    echo Horde::wrapInlineScript(array('window.close();'));
483                                    exit;
484                                }
485                                $notification->push($result, 'horde.success');
486                            } else {
487                                $notification->push($result, 'horde.success');
488                                if ($prefs->getValue('close_draft')) {
489                                    $imp_compose->destroy('save_draft');
490                                    $this->_mailboxReturnUrl()->redirect();
491                                }
492                            }
493                            break;
494
495                        case 'save_template':
496                            if ($isPopup) {
497                                echo Horde::wrapInlineScript(array('window.close();'));
498                                exit;
499                            }
500
501                            $notification->push($result, 'horde.success');
502                            $this->_mailboxReturnUrl()->redirect();
503                            break;
504                        }
505                    } catch (IMP_Compose_Exception $e) {
506                        if ($this->vars->actionID == 'save_draft') {
507                            $notification->push($e);
508                        }
509                    }
510                }
511
512                if ($this->vars->actionID == 'auto_save_draft') {
513                    $r = new stdClass;
514                    $r->requestToken = $session->getToken();
515                    $r->formToken = $session->getNonce();
516
517                    $response = new Horde_Core_Ajax_Response_HordeCore($r);
518                    $response->sendAndExit();
519                }
520                break;
521
522            default:
523                $header['replyto'] = $identity->getValue('replyto_addr');
524
525                if ($this->vars->sent_mail) {
526                    $sent_mail = IMP_Mailbox::formFrom($this->vars->sent_mail);
527                }
528
529                try {
530                    $imp_compose->buildAndSendMessage(
531                        $message,
532                        $header,
533                        $identity,
534                        array(
535                            'encrypt' => $prefs->isLocked('default_encrypt') ? $prefs->getValue('default_encrypt') : $this->vars->encrypt_options,
536                            'html' => $rtemode,
537                            'pgp_attach_pubkey' => $this->vars->pgp_attach_pubkey,
538                            'priority' => $priority,
539                            'save_sent' => $save_sent_mail,
540                            'sent_mail' => $sent_mail,
541                            'signature' => $this->vars->signature,
542                            'strip_attachments' => !$this->vars->save_attachments_select,
543                            'readreceipt' => $request_read_receipt,
544                            'vcard_attach' => $this->vars->vcard ? $identity->getValue('fullname') : null
545                        )
546                    );
547                    $imp_compose->destroy('send');
548
549                    if ($isPopup) {
550                        if ($prefs->getValue('compose_confirm')) {
551                            $notification->push(_("Message sent successfully."), 'horde.success');
552                            $this->_popupSuccess();
553                            return;
554                        }
555                        echo Horde::wrapInlineScript(array('window.close();'));
556                    } else {
557                        $notification->push(_("Message sent successfully."), 'horde.success');
558                        $this->_mailboxReturnUrl()->redirect();
559                    }
560                    exit;
561                } catch (IMP_Compose_Exception $e) {
562                    $code = $e->getCode();
563                    $notification->push($e->getMessage(), strpos($code, 'horde.') === 0 ? $code : 'horde.error');
564
565                    /* Switch to tied identity. */
566                    if (!is_null($e->tied_identity)) {
567                        $identity->setDefault($e->tied_identity);
568                        $notification->push(_("Your identity has been switched to the identity associated with the current recipient address. The identity will not be checked again during this compose action."));
569                    }
570
571                    switch ($e->encrypt) {
572                    case 'pgp_symmetric_passphrase_dialog':
573                        $imp_ui->passphraseDialog('pgp_symm', $imp_compose->getCacheId());
574                        break;
575
576                    case 'pgp_passphrase_dialog':
577                        $imp_ui->passphraseDialog('pgp');
578                        break;
579
580                    case 'smime_passphrase_dialog':
581                        $imp_ui->passphraseDialog('smime');
582                        break;
583                    }
584                }
585                break;
586            }
587            break;
588
589        case 'fwd_digest':
590            if (count($this->indices)) {
591                try {
592                    $res = $imp_compose->forwardMultipleMessages($this->indices);
593                    $header['subject'] = $res['subject'];
594                    $fwd_msg = array('type' => IMP_Compose::FORWARD_ATTACH);
595                } catch (IMP_Compose_Exception $e) {
596                    $notification->push($e, 'horde.error');
597                }
598            }
599            break;
600
601        case 'cancel_compose':
602        case 'discard_compose':
603            $imp_compose->destroy($this->vars->actionID == 'cancel_compose' ? 'cancel' : 'discard');
604            if ($isPopup) {
605                echo Horde::wrapInlineScript(array('window.close();'));
606            } else {
607                $this->_mailboxReturnUrl()->redirect();
608            }
609            exit;
610
611        case 'template_new':
612            $this->vars->template_mode = true;
613            break;
614        }
615
616        /* Get the message cache ID. */
617        $composeCacheID = filter_var($imp_compose->getCacheId(), FILTER_SANITIZE_STRING);
618
619        /* Attach autocompleters to the compose form elements. */
620        if ($redirect) {
621            $imp_ui->attachAutoCompleter(array('to'));
622        } else {
623            $imp_ui->attachAutoCompleter(array('to', 'cc', 'bcc'));
624            $spellcheck = $imp_ui->attachSpellChecker();
625            $page_output->addScriptFile('ieescguard.js', 'horde');
626        }
627
628        $max_attach = $imp_compose->additionalAttachmentsAllowed();
629
630        /* Get the URL to use for the cancel action. If the attachments cache
631         * is not empty, or this is the resume drafts page, we must reload
632         * this page and delete the attachments and/or the draft message. */
633        if ($isPopup) {
634            if ($resume || count($imp_compose)) {
635                $cancel_url = self::url()->setRaw(true)->add(array(
636                    'actionID' => 'cancel_compose',
637                    'compose_requestToken' => $session->getToken(),
638                    'composeCache' => $composeCacheID,
639                    'popup' => 1
640                ));
641                $discard_url = clone $cancel_url;
642                $discard_url->add('actionID', 'discard_compose');
643            } else {
644                $cancel_url = $discard_url = '';
645            }
646        } elseif ($resume || count($imp_compose)) {
647            $cancel_url = $this->_mailboxReturnUrl(self::url()->setRaw(true))->setRaw(true)->add(array(
648                'actionID' => 'cancel_compose',
649                'compose_requestToken' => $session->getToken(),
650                'composeCache' => $composeCacheID
651            ));
652            $discard_url = clone $cancel_url;
653            $discard_url->add('actionID', 'discard_compose');
654        } else {
655            $cancel_url = $discard_url = $this->_mailboxReturnUrl(false)->setRaw(true);
656        }
657
658        /* Grab any data that we were supplied with. */
659        if (!strlen($msg)) {
660            $msg = $this->vars->get('message', strval($this->vars->body));
661            if ($browser->hasQuirk('double_linebreak_textarea')) {
662                $msg = preg_replace('/(\r?\n){3}/', '$1', $msg);
663            }
664            $msg = "\n" . $msg;
665        }
666        if (isset($this->vars->signature)) {
667            $signature = $this->vars->signature;
668            if ($browser->hasQuirk('double_linebreak_textarea')) {
669                $signature = preg_replace('/(\r?\n){3}/', '$1', $signature);
670            }
671            $signatureChanged = $signature != $identity->getSignature($oldrtemode ? 'html' : 'text');
672        } else {
673            $signatureChanged = false;
674        }
675
676        /* Convert from Text -> HTML or vice versa if RTE mode changed. */
677        if (!is_null($oldrtemode) && ($oldrtemode != $rtemode)) {
678            $msg = $imp_ui->convertComposeText($msg, $rtemode ? 'html' : 'text');
679            if ($signatureChanged) {
680                $signature = $imp_ui->convertComposeText($signature, $rtemode ? 'html' : 'text');
681            }
682        }
683
684        /* If this is the first page load for this compose item, add auto BCC
685         * addresses. */
686        if (!$reload && !$resume) {
687            $header['bcc'] = strval($identity->getBccAddresses());
688        }
689
690        foreach (array('to', 'cc', 'bcc') as $val) {
691            if (!isset($header[$val])) {
692                $header[$val] = $this->vars->$val;
693            }
694        }
695
696        if (!isset($header['subject'])) {
697            $header['subject'] = $this->vars->subject;
698        }
699
700        /* If PGP encryption is set by default, and we have a recipient list
701         * on first load, make sure we have public keys for all recipients. */
702        $encrypt_options = $prefs->isLocked('default_encrypt')
703            ? $prefs->getValue('default_encrypt')
704            : $this->vars->encrypt_options;
705        if ($prefs->getValue('use_pgp') &&
706            !$prefs->isLocked('default_encrypt') &&
707            $prefs->getValue('pgp_reply_pubkey')) {
708            $default_encrypt = $prefs->getValue('default_encrypt');
709            if (!$reload &&
710                in_array($default_encrypt, array(IMP_Crypt_Pgp::ENCRYPT, IMP_Crypt_Pgp::SIGNENC))) {
711                $addrs = $imp_compose->recipientList($header);
712                if (!empty($addrs['list'])) {
713                    $imp_pgp = $injector->getInstance('IMP_Crypt_Pgp');
714                    try {
715                        foreach ($addrs['list'] as $val) {
716                            $imp_pgp->getPublicKey(strval($val));
717                        }
718                    } catch (Horde_Exception $e) {
719                        $notification->push(_("PGP encryption cannot be used by default as public keys cannot be found for all recipients."), 'horde.warning');
720                        $encrypt_options = ($default_encrypt == IMP_Crypt_Pgp::ENCRYPT) ? IMP::ENCRYPT_NONE : IMP_Crypt_Pgp::SIGN;
721                    }
722                }
723            }
724        }
725
726        /* Define some variables used in the javascript code. */
727        $js_vars = array(
728            'ImpComposeBase.editor_on' => $rtemode,
729            'ImpCompose.auto_save' => intval($prefs->getValue('auto_save_drafts')),
730            'ImpCompose.cancel_url' => strval($cancel_url),
731            'ImpCompose.cursor_pos' => ($rtemode ? null : $prefs->getValue('compose_cursor')),
732            'ImpCompose.discard_url' => strval($discard_url),
733            'ImpCompose.max_attachments' => (($max_attach === true) ? null : $max_attach),
734            'ImpCompose.popup' => intval($isPopup),
735            'ImpCompose.redirect' => intval($redirect),
736            'ImpCompose.reloaded' => intval($reload),
737            'ImpCompose.sm_check' => intval(!$prefs->isLocked(IMP_Mailbox::MBOX_SENT)),
738            'ImpCompose.spellcheck' => intval($spellcheck && $prefs->getValue('compose_spellcheck')),
739            'ImpCompose.text' => array(
740                'cancel' => _("Cancelling this message will permanently discard its contents.") . "\n" . _("Are you sure you want to do this?"),
741                'change_identity' => _("You have edited your signature. Change the identity and lose your changes?"),
742                'discard' => _("Doing so will discard this message permanently."),
743                'file' => _("File"),
744                'nosubject' => _("The message does not have a Subject entered.") . "\n" . _("Send message without a Subject?"),
745                'recipient' => _("You must specify a recipient.")
746            )
747        );
748
749        /* Set up the base view now. */
750        $view = $injector->createInstance('Horde_View');
751        $view->addHelper('FormTag');
752        $view->addHelper('Horde_Core_View_Helper_Accesskey');
753        $view->addHelper('Horde_Core_View_Helper_Help');
754        $view->addHelper('Horde_Core_View_Helper_Image');
755        $view->addHelper('Horde_Core_View_Helper_Label');
756        $view->addHelper('Tag');
757
758        $view->allow_compose = !$compose_disable;
759        $view->post_action = self::url();
760
761        $blank_url = new Horde_Url('#');
762
763        if ($redirect) {
764            /* Prepare the redirect template. */
765            $view->cacheid = $composeCacheID;
766            $view->title = $this->title;
767            $view->token = $session->getToken();
768
769            if ($registry->hasMethod('contacts/search')) {
770                $view->abook = $blank_url->copy()->link(array(
771                    'class' => 'widget',
772                    'id' => 'redirect_abook',
773                    'title' => _("Address Book")
774                ));
775                $js_vars['ImpCompose.redirect_contacts'] = strval(IMP_Basic_Contacts::url()->add(array('to_only' => 1))->setRaw(true));
776            }
777
778            $view->input_value = $header['to'];
779
780            $this->output = $view->render('basic/compose/redirect');
781        } else {
782            /* Prepare the compose template. */
783            $view->file_upload = $attach_upload;
784
785            $hidden = array(
786                'actionID' => '',
787                'attachmentAction' => '',
788                'compose_formToken' => $session->getNonce(),
789                'compose_requestToken' => $session->getToken(),
790                'composeCache' => $composeCacheID,
791                'composeHmac' => $imp_compose->getHmac(),
792                'oldrtemode' => $rtemode,
793                'rtemode' => $rtemode,
794                'user' => $registry->getAuth()
795            );
796
797            if ($attach_upload) {
798                $hidden['MAX_FILE_SIZE'] = $session->get('imp', 'file_upload');
799            }
800            foreach (array('page', 'start', 'popup', 'template_mode') as $val) {
801                $hidden[$val] = $this->vars->$val;
802            }
803
804            $view->hidden = $hidden;
805            $view->tabindex = 1;
806            $view->title = $this->title;
807
808            if (!$this->vars->template_mode) {
809                $view->send_msg = true;
810                $view->save_draft = ($imp_imap->access(IMP_Imap::ACCESS_DRAFTS) && !$readonly_drafts);
811            }
812
813            $view->resume = $resume;
814
815            $view->di_locked = $prefs->isLocked('default_identity');
816            if ($view->di_locked) {
817                $view->fromaddr_locked = $prefs->isLocked('from_addr');
818                try {
819                    $view->from = $identity->getFromLine(null, $this->vars->from);
820                } catch (Horde_Exception $e) {}
821            } else {
822                $select_list = $identity->getSelectList();
823                $view->last_identity = $identity->getDefault();
824
825                if (count($select_list) > 1) {
826                    $view->count_select_list = true;
827                    $t_select_list = array();
828                    foreach ($select_list as $key => $select) {
829                        $t_select_list[] = array(
830                            'label' => $select,
831                            'selected' => ($key == $identity->getDefault()),
832                            'value' => $key
833                        );
834                    }
835                    $view->select_list = $t_select_list;
836                } else {
837                    $view->identity_default = $identity->getDefault();
838                    $view->identity_text = $select_list[0];
839                }
840            }
841            $view->signature = $identity->hasSignature(true);
842
843            $addr_array = array(
844                'to' => _("_To"),
845                'cc' => _("_Cc"),
846                'bcc' => _("_Bcc")
847            );
848
849            $address_array = array();
850            foreach ($addr_array as $val => $label) {
851                $address_array[] = array(
852                    'id' => $val,
853                    'label' => $label,
854                    'val' => $header[$val]
855                );
856            }
857            $view->addr = $address_array;
858
859            $view->subject = $header['subject'];
860
861            if ($prefs->getValue('set_priority')) {
862                $view->set_priority = true;
863
864                $priorities = array(
865                    'high' => _("High"),
866                    'normal' => _("Normal"),
867                    'low' => _("Low")
868                );
869
870                $priority_option = array();
871                foreach ($priorities as $key => $val) {
872                    $priority_option[] = array(
873                        'label' => $val,
874                        'selected' => ($priority == $key),
875                        'val' => $key
876                    );
877                }
878                $view->pri_opt = $priority_option;
879            }
880
881            $compose_options = array();
882
883            if ($registry->hasMethod('contacts/search')) {
884                $compose_options[] = array(
885                    'url' => $blank_url->copy()->link(array(
886                        'class' => 'widget',
887                        'id' => 'addressbook_popup'
888                    )),
889                    'img' => Horde_Themes_Image::tag('addressbook_browse.png'),
890                    'label' => _("Address Book")
891                );
892                $js_vars['ImpCompose.contacts_url'] = strval(IMP_Basic_Contacts::url()->setRaw(true));
893            }
894            if ($spellcheck) {
895                $compose_options[] = array(
896                    'url' => $blank_url->copy()->link(array(
897                        'class' => 'widget',
898                        'id' => 'spellcheck'
899                    )),
900                    'img' => '',
901                    'label' => ''
902                );
903            }
904            if ($attach_upload) {
905                $url = new Horde_Url('#attachments');
906                $compose_options[] = array(
907                    'url' => $url->link(array('class' => 'widget')),
908                    'img' => Horde_Themes_Image::tag('attachment.png'),
909                    'label' => _("Attachments")
910                );
911            }
912            $view->compose_options = $compose_options;
913
914            if ($imp_imap->access(IMP_Imap::ACCESS_FOLDERS) &&
915                !$prefs->isLocked('save_sent_mail')) {
916                $view->ssm = true;
917                if ($readonly_sentmail) {
918                    $notification->push(sprintf(_("Cannot save sent-mail message to \"%s\" as that mailbox is read-only.", $sent_mail->display), 'horde.warning'));
919                }
920                $view->ssm_selected = $reload
921                    ? $save_sent_mail
922                    : ($sent_mail && $identity->saveSentmail());
923                if ($this->vars->sent_mail) {
924                    $sent_mail = IMP_Mailbox::formFrom($this->vars->sent_mail);
925                }
926                if (!$prefs->isLocked(IMP_Mailbox::MBOX_SENT)) {
927                    $iterator = new IMP_Ftree_IteratorFilter(
928                        $injector->getInstance('IMP_Ftree')
929                    );
930                    $iterator->add($iterator::NONIMAP);
931                    $iterator->mboxes = array('INBOX');
932
933                    $ssm_options = array(
934                        'abbrev' => false,
935                        'iterator' => $iterator,
936                        'selected' => $sent_mail
937                    );
938
939                    /* Check to make sure the sent-mail mailbox is created -
940                     * it needs to exist to show up in drop-down list. */
941                    if ($sent_mail) {
942                        $sent_mail->create();
943                    }
944
945                    $view->ssm_mboxes = new IMP_Ftree_Select($ssm_options);
946                } else {
947                    if ($sent_mail) {
948                        $sent_mail = '&quot;' . $sent_mail->display_html . '&quot;';
949                    }
950                    $view->ssm_mbox = $sent_mail;
951                }
952            }
953
954            $view->rrr_selected = $prefs->isLocked('request_mdn')
955                ? null
956                : (($prefs->getValue('request_mdn') == 'always') || $request_read_receipt);
957
958            if (!is_null($rtemode) && !$prefs->isLocked('compose_html')) {
959                $view->compose_html = true;
960                $view->html_switch = $blank_url->copy()->link(array(
961                    'id' => 'rte_toggle',
962                    'title' => _("Switch Composition Method")
963                ));
964                $view->rtemode = $rtemode;
965            }
966
967            if (isset($replyauto_all)) {
968                $view->replyauto_all = $replyauto_all;
969            } elseif (isset($replyauto_list)) {
970                $view->replyauto_list = true;
971                if (isset($replyauto_list_id)) {
972                    $view->replyauto_list_id = $replyauto_list_id;
973                }
974            }
975
976            if (isset($reply_lang)) {
977                $view->reply_lang = implode(',', $reply_lang);
978            }
979
980            $view->message = $msg;
981            if ($signatureChanged) {
982                $view->signatureContent = $signature;
983            }
984
985            if ($prefs->getValue('use_pgp') || $prefs->getValue('use_smime')) {
986                if ($prefs->isLocked('default_encrypt')) {
987                    $view->use_encrypt = false;
988                } else {
989                    $view->use_encrypt = true;
990                    $view->encrypt_options = $imp_ui->encryptList($encrypt_options);
991                }
992
993                if ($prefs->getValue('use_pgp') && $prefs->getValue('pgp_public_key')) {
994                    $view->pgp_options = true;
995                    $view->pgp_attach_pubkey = $reload
996                        ? $this->vars->pgp_attach_pubkey
997                        : $prefs->getValue('pgp_attach_pubkey');
998                }
999            }
1000
1001            if ($registry->hasMethod('contacts/ownVCard')) {
1002                $view->vcard = true;
1003                $view->attach_vcard = $this->vars->vcard;
1004            }
1005
1006            if ($attach_upload) {
1007                $view->attach_size = IMP::numberFormat($imp_compose->maxAttachmentSize(), 0);
1008                $view->maxattachmentnumber = !$max_attach;
1009
1010                $save_attach = $prefs->getValue('save_attachments');
1011
1012                if ($view->ssm && !$prefs->isLocked('save_attachments')) {
1013                    $view->show_link_save_attach = true;
1014                    $view->attach_options = array(array(
1015                        'label' => _("Save attachments with message in sent-mail mailbox?"),
1016                        'name' => 'save_attachments_select',
1017                        'val' => ($reload ? $this->vars->save_attachments_select : ($save_attach == 'always'))
1018                    ));
1019                }
1020
1021                if (count($imp_compose)) {
1022                    $view->numberattach = true;
1023
1024                    $atc = array();
1025                    $v = $injector->getInstance('IMP_Factory_MimeViewer');
1026                    foreach ($imp_compose as $data) {
1027                        $mime = $data->getPart();
1028                        $type = $mime->getType();
1029
1030                        $entry = array(
1031                            'name' => $mime->getName(true),
1032                            'icon' => $v->getIcon($type),
1033                            'number' => $data->id,
1034                            'type' => $type,
1035                            'size' => $mime->getSize(),
1036                            'description' => $mime->getDescription(true)
1037                        );
1038
1039                        if (!(isset($fwd_msg) &&
1040                              ($fwd_msg['type'] != IMP_Compose::FORWARD_BODY)) &&
1041                            ($type != 'application/octet-stream')) {
1042                            $entry['name'] = $data->viewUrl()->link(array(
1043                                'class' => 'link',
1044                                'target' => 'compose_preview_window',
1045                                'title' => _("Preview")
1046                            )) . htmlspecialchars($entry['name']) . '</a>';
1047                        }
1048
1049                        $atc[] = $entry;
1050                    }
1051                    $view->atc = $atc;
1052                }
1053            }
1054
1055            $this->output = $view->render('basic/compose/compose');
1056        }
1057
1058        $page_output->addScriptPackage('IMP_Script_Package_ComposeBase');
1059        $page_output->addScriptFile('compose.js');
1060        $page_output->addScriptFile('editor.js');
1061        $page_output->addScriptFile('imp.js');
1062        $page_output->addInlineJsVars($js_vars);
1063        if (!$redirect) {
1064            $imp_ui->addIdentityJs();
1065        }
1066
1067        if ($rtemode && !$redirect) {
1068            $page_output->addScriptPackage('IMP_Script_Package_Editor');
1069        }
1070    }
1071
1072    /**
1073     * @param array $opts
1074     *   - full: (boolean) If true, output full URL.
1075     */
1076    static public function url(array $opts = array())
1077    {
1078        return Horde::url('basic.php', !empty($opts['full']))->add('page', 'compose')->unique();
1079    }
1080
1081    /**
1082     * Create the IMP_Contents objects needed to create a message.
1083     *
1084     * @return IMP_Contents  The IMP_Contents object.
1085     * @throws IMP_Exception
1086     */
1087    protected function _getContents()
1088    {
1089        $ob = null;
1090
1091        if (count($this->indices)) {
1092            try {
1093                $ob = $GLOBALS['injector']->getInstance('IMP_Factory_Contents')->create($this->indices);
1094            } catch (Horde_Exception $e) {}
1095        }
1096
1097        if (!is_null($ob)) {
1098            return $ob;
1099        }
1100
1101        $this->vars->buid = null;
1102        $this->vars->type = 'new';
1103        throw new IMP_Exception(_("Could not retrieve message data from the mail server."));
1104    }
1105
1106    /**
1107     * Generate mailbox return URL.
1108     *
1109     * @param string $url  The URL to use instead of the default.
1110     *
1111     * @return string  The mailbox return URL.
1112     */
1113    protected function _mailboxReturnUrl($url = null)
1114    {
1115        $url = $this->indices->mailbox->url('mailbox');
1116
1117        foreach (array('start', 'page') as $key) {
1118            if (isset($vars->$key)) {
1119                $url->add($key, $vars->$key);
1120            }
1121        }
1122
1123        return $url;
1124    }
1125
1126    /**
1127     * Generate a popup success window.
1128     */
1129    protected function _popupSuccess()
1130    {
1131        global $page_output;
1132
1133        $page_output->topbar = $page_output->sidebar = false;
1134        $page_output->addInlineScript(array(
1135            '$("close_success").observe("click", function() { window.close(); })'
1136        ), true);
1137
1138        $this->title =_("Message Successfully Sent");
1139
1140        $view = new Horde_View(array(
1141            'templatePath' => IMP_TEMPLATES . '/basic/compose'
1142        ));
1143
1144        $view->close = Horde::widget(array(
1145            'id' => 'close_success',
1146            'url' => new Horde_Url('#'),
1147            'title' => _("Close this window")
1148        ));
1149        $view->new = Horde::widget(array(
1150            'url' => self::url(),
1151            'title' => _("New Message")
1152        ));
1153
1154        $this->output = $view->render('success');
1155    }
1156
1157    /**
1158     * Convert a compose response object to header values.
1159     *
1160     * @param array $in  Compose response object.
1161     *
1162     * @return array  Header entry.
1163     */
1164    protected function _convertToHeader($in)
1165    {
1166        $out = array();
1167
1168        if (isset($in['addr'])) {
1169            $out['to'] = strval($in['addr']['to']);
1170            $out['cc'] = strval($in['addr']['cc']);
1171            $out['bcc'] = strval($in['addr']['bcc']);
1172        }
1173
1174        if (isset($in['subject'])) {
1175            $out['subject'] = $in['subject'];
1176        }
1177
1178        return $out;
1179    }
1180
1181}
1182