1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14:
15: class IMP_Compose implements ArrayAccess, Countable, Iterator, Serializable
16: {
17:
18: const VFS_ATTACH_PATH = '.horde/imp/compose';
19:
20:
21: const VFS_LINK_ATTACH_PATH = '.horde/imp/attachments';
22:
23:
24: const VFS_DRAFTS_PATH = '.horde/imp/drafts';
25:
26:
27: const COMPOSE = 0;
28: const REPLY = 1;
29: const REPLY_ALL = 2;
30: const REPLY_AUTO = 3;
31: const REPLY_LIST = 4;
32: const REPLY_SENDER = 5;
33: const FORWARD = 6;
34: const FORWARD_ATTACH = 7;
35: const FORWARD_AUTO = 8;
36: const FORWARD_BODY = 9;
37: const FORWARD_BOTH = 10;
38: const REDIRECT = 11;
39:
40:
41: const HTML_BLOCKQUOTE = '<blockquote type="cite" style="border-left:2px solid blue;margin-left:8px;padding-left:8px;">';
42:
43: 44: 45: 46: 47: 48:
49: public $changed = '';
50:
51: 52: 53: 54: 55:
56: public $charset;
57:
58: 59: 60: 61: 62:
63: protected $_attachVCard = false;
64:
65: 66: 67: 68: 69:
70: protected $_cache = array();
71:
72: 73: 74: 75: 76:
77: protected $_cacheid;
78:
79: 80: 81: 82: 83:
84: protected $_linkAttach = false;
85:
86: 87: 88: 89: 90:
91: protected $_metadata = array();
92:
93: 94: 95: 96: 97: 98:
99: protected $_pgpAttachPubkey = false;
100:
101: 102: 103: 104: 105:
106: protected $_replytype = self::COMPOSE;
107:
108: 109: 110: 111: 112:
113: protected $_size = 0;
114:
115: 116: 117: 118: 119:
120: public function __construct($cacheid)
121: {
122: $this->_cacheid = $cacheid;
123: $this->charset = $GLOBALS['registry']->getEmailCharset();
124: }
125:
126: 127: 128: 129: 130: 131: 132:
133: public function destroy($action)
134: {
135: $uids = new IMP_Indices();
136:
137: switch ($action) {
138: case 'save_draft':
139:
140: break;
141:
142: case 'send':
143:
144: $uids->add($this->getMetadata('draft_uid_resume'));
145:
146:
147: case 'cancel':
148:
149: $uids->add($this->getMetadata('draft_uid'));
150: break;
151: }
152:
153: $GLOBALS['injector']->getInstance('IMP_Message')->delete($uids, array('nuke' => true));
154:
155: $this->deleteAllAttachments();
156:
157: $this->changed = 'deleted';
158: }
159:
160: 161: 162: 163: 164: 165: 166:
167: public function getMetadata($name)
168: {
169: return isset($this->_metadata[$name])
170: ? $this->_metadata[$name]
171: : null;
172: }
173:
174: 175: 176: 177: 178: 179: 180: 181: 182: 183: 184: 185: 186: 187: 188: 189:
190: public function saveDraft($headers, $message, array $opts = array())
191: {
192: $body = $this->_saveDraftMsg($headers, $message, $opts);
193: return $this->_saveDraftServer($body);
194: }
195:
196: 197: 198: 199: 200: 201: 202: 203: 204: 205: 206: 207: 208: 209: 210: 211:
212: protected function _saveDraftMsg($headers, $message, $opts)
213: {
214: $has_session = (bool)$GLOBALS['registry']->getAuth();
215:
216:
217: $base = $this->_createMimeMessage(array(null), $message, array(
218: 'html' => !empty($opts['html']),
219: 'noattach' => !$has_session,
220: 'nofinal' => true
221: ));
222: $base->isBasePart(true);
223:
224: if ($has_session) {
225: foreach (array('to', 'cc', 'bcc') as $v) {
226: if (isset($headers[$v])) {
227: try {
228: Horde_Mime::encodeAddress(self::formatAddr($headers[$v]), $this->charset, $GLOBALS['session']->get('imp', 'maildomain'));
229: } catch (Horde_Mime_Exception $e) {
230: throw new IMP_Compose_Exception(sprintf(_("Saving the draft failed. The %s header contains an invalid e-mail address: %s."), $v, $e->getMessage()), $e->getCode());
231: }
232: }
233: }
234: }
235:
236:
237: $draft_headers = $this->_prepareHeaders($headers, array_merge($opts, array('bcc' => true)));
238:
239: 240:
241: if ($this->_replytype) {
242: $imp_imap = $GLOBALS['injector']->getInstance('IMP_Factory_Imap')->create();
243: try {
244: $imap_url = $imp_imap->getUtils()->createUrl(array(
245: 'type' => $imp_imap->pop3 ? 'pop' : 'imap',
246: 'username' => $imp_imap->getParam('username'),
247: 'hostspec' => $imp_imap->getParam('hostspec'),
248: 'mailbox' => $this->getMetadata('mailbox'),
249: 'uid' => $this->getMetadata('uid'),
250: 'uidvalidity' => $this->getMetadata('mailbox')->uidvalid
251: ));
252:
253: switch ($this->replyType(true)) {
254: case self::FORWARD:
255: $draft_headers->addHeader('X-IMP-Draft-Forward', '<' . $imap_url . '>');
256: break;
257:
258: case self::REPLY:
259: $draft_headers->addHeader('X-IMP-Draft-Reply', '<' . $imap_url . '>');
260: $draft_headers->addHeader('X-IMP-Draft-Reply-Type', $this->_replytype);
261: break;
262: }
263: } catch (Horde_Exception $e) {}
264: } else {
265: $draft_headers->addHeader('X-IMP-Draft', 'Yes');
266: }
267:
268: return $base->toString(array(
269: 'defserver' => $has_session ? $GLOBALS['session']->get('imp', 'maildomain') : null,
270: 'headers' => $draft_headers
271: ));
272: }
273:
274: 275: 276: 277: 278: 279: 280: 281: 282:
283: protected function _saveDraftServer($data)
284: {
285: if (!$drafts_mbox = IMP_Mailbox::getPref('drafts_folder')) {
286: throw new IMP_Compose_Exception(_("Saving the draft failed. No draft folder specified."));
287: }
288:
289:
290: if (!$drafts_mbox->create()) {
291: throw new IMP_Compose_Exception(_("Saving the draft failed. Could not create a drafts folder."));
292: }
293:
294: $append_flags = array(Horde_Imap_Client::FLAG_DRAFT);
295: if (!$GLOBALS['prefs']->getValue('unseen_drafts')) {
296: $append_flags[] = Horde_Imap_Client::FLAG_SEEN;
297: }
298:
299: 300: 301:
302:
303: $old_uid = $this->getMetadata('draft_uid');
304:
305:
306: try {
307: $ids = $GLOBALS['injector']->getInstance('IMP_Factory_Imap')->create()->append($drafts_mbox, array(array('data' => $data, 'flags' => $append_flags)));
308:
309: if ($old_uid) {
310: $GLOBALS['injector']->getInstance('IMP_Message')->delete($old_uid, array('nuke' => true));
311: }
312:
313: $this->_metadata['draft_uid'] = $drafts_mbox->getIndicesOb($ids);
314: $this->changed = 'changed';
315: return sprintf(_("The draft has been saved to the \"%s\" folder."), $drafts_mbox->display);
316: } catch (IMP_Imap_Exception $e) {
317: return _("The draft was not successfully saved.");
318: }
319: }
320:
321: 322: 323: 324: 325: 326: 327: 328: 329: 330: 331: 332: 333: 334: 335: 336:
337: public function resumeDraft($indices, $addheaders = true)
338: {
339: global $injector, $prefs;
340:
341: try {
342: $contents = $injector->getInstance('IMP_Factory_Contents')->create($indices);
343: } catch (IMP_Exception $e) {
344: throw new IMP_Compose_Exception($e);
345: }
346:
347: $header = array();
348: $headers = $contents->getHeader();
349: $imp_draft = false;
350: $reply_type = null;
351:
352: if ($draft_url = $headers->getValue('x-imp-draft-reply')) {
353: if (!($reply_type = $headers->getValue('x-imp-draft-reply-type'))) {
354: $reply_type = self::REPLY;
355: }
356: $imp_draft = self::REPLY;
357: } elseif ($draft_url = $headers->getValue('x-imp-draft-forward')) {
358: $imp_draft = $reply_type = self::FORWARD;
359: } elseif ($headers->getValue('x-imp-draft')) {
360: $imp_draft = self::COMPOSE;
361: }
362:
363: if (IMP::getViewMode() == 'mimp') {
364: $compose_html = false;
365: } elseif ($prefs->getValue('compose_html')) {
366: $compose_html = true;
367: } else {
368: switch ($reply_type) {
369: case self::FORWARD:
370: case self::FORWARD_BODY:
371: case self::FORWARD_BOTH:
372: $compose_html = $prefs->getValue('forward_format');
373: break;
374:
375: case self::REPLY:
376: case self::REPLY_ALL:
377: case self::REPLY_LIST:
378: case self::REPLY_SENDER:
379: $compose_html = $prefs->getValue('reply_format');
380: break;
381:
382: default:
383: 384: 385:
386: $compose_html = ($imp_draft !== false);
387: break;
388: }
389: }
390:
391: $msg_text = $this->_getMessageText($contents, array(
392: 'html' => $compose_html,
393: 'imp_msg' => $imp_draft,
394: 'toflowed' => false
395: ));
396:
397: if (empty($msg_text)) {
398: $charset = $this->charset;
399: $message = '';
400: $mode = 'text';
401: $text_id = 0;
402: } else {
403: $charset = $msg_text['charset'];
404: $message = $msg_text['text'];
405: $mode = $msg_text['mode'];
406: $text_id = $msg_text['id'];
407: }
408:
409: $mime_message = $contents->getMIMEMessage();
410:
411:
412: if (($mime_message->getPrimaryType() == 'multipart') &&
413: ($mime_message->getType() != 'multipart/alternative')) {
414: for ($i = 1; ; ++$i) {
415: if (intval($text_id) == $i) {
416: continue;
417: }
418:
419: if (!($part = $contents->getMIMEPart($i))) {
420: break;
421: }
422:
423: try {
424: $this->addMimePartAttachment($part);
425: } catch (IMP_Compose_Exception $e) {
426: $GLOBALS['notification']->push($e, 'horde.warning');
427: }
428: }
429: }
430:
431: $identity_id = null;
432: if (($fromaddr = Horde_Mime_Address::bareAddress($headers->getValue('from')))) {
433: $identity = $injector->getInstance('IMP_Identity');
434: $identity_id = $identity->getMatchingIdentity($fromaddr);
435: }
436:
437: if ($addheaders) {
438: $header = array(
439: 'to' => Horde_Mime_Address::addrArray2String($headers->getOb('to')),
440: 'cc' => Horde_Mime_Address::addrArray2String($headers->getOb('cc')),
441: 'bcc' => Horde_Mime_Address::addrArray2String($headers->getOb('bcc')),
442: 'subject' => $headers->getValue('subject')
443: );
444:
445: if ($val = $headers->getValue('references')) {
446: $this->_metadata['references'] = $val;
447:
448: if ($val = $headers->getValue('in-reply-to')) {
449: $this->_metadata['in_reply_to'] = $val;
450: }
451: }
452:
453: if ($draft_url) {
454: $imp_imap = $injector->getInstance('IMP_Factory_Imap')->create();
455: $imap_url = $imp_imap->getUtils()->parseUrl(rtrim(ltrim($draft_url, '<'), '>'));
456: $protocol = $imp_imap->pop3 ? 'pop' : 'imap';
457:
458: try {
459: if (($imap_url['type'] == $protocol) &&
460: ($imap_url['username'] == $imp_imap->getParam('username')) &&
461:
462:
463:
464: (IMP_Mailbox::get($imap_url['mailbox'])->uidvalid == $imap_url['uidvalidity']) &&
465: $injector->getInstance('IMP_Factory_Contents')->create(new IMP_Indices($imap_url['mailbox'], $imap_url['uid']))) {
466: $this->_metadata['mailbox'] = IMP_Mailbox::get($imap_url['mailbox']);
467: $this->_metadata['uid'] = $imap_url['uid'];
468: $this->_replytype = $reply_type;
469: }
470: } catch (Exception $e) {}
471: }
472:
473: $this->_metadata['draft_uid_resume'] = $indices;
474: }
475:
476: $imp_ui_hdrs = new IMP_Ui_Headers();
477: $priority = $imp_ui_hdrs->getPriority($headers);
478:
479: $mdn = new Horde_Mime_Mdn($headers);
480: $readreceipt = (bool)$mdn->getMdnReturnAddr();
481:
482: $this->charset = $charset;
483: $this->changed = 'changed';
484:
485: return array(
486: 'header' => $header,
487: 'identity' => $identity_id,
488: 'mode' => $mode,
489: 'msg' => $message,
490: 'priority' => $priority,
491: 'readreceipt' => $readreceipt
492: );
493: }
494:
495: 496: 497: 498: 499:
500: public function hasDrafts()
501: {
502: return (!empty($this->_metadata['draft_uid']) ||
503: !empty($this->_metadata['draft_uid_resume']));
504: }
505:
506: 507: 508: 509: 510: 511: 512: 513: 514: 515: 516: 517: 518: 519: 520: 521: 522: 523: 524: 525: 526: 527: 528: 529: 530: 531: 532: 533: 534: 535: 536: 537: 538: 539: 540: 541: 542: 543: 544: 545: 546: 547: 548: 549: 550: 551: 552: 553: 554: 555: 556: 557:
558: public function buildAndSendMessage($body, $header, array $opts = array())
559: {
560: global $conf, $injector, $notification, $prefs, $session, $registry;
561:
562: 563:
564: $recip = $this->recipientList($header);
565: if (!count($recip['list'])) {
566: if ($recip['has_input']) {
567: throw new IMP_Compose_Exception(_("Invalid e-mail address."));
568: }
569: throw new IMP_Compose_Exception(_("Need at least one message recipient."));
570: }
571: $header = array_merge($header, $recip['header']);
572:
573:
574: if (!$this->getMetadata('identity_check') &&
575: (count($recip['list']) === 1) &&
576: isset($opts['identity'])) {
577: $identity_search = $opts['identity']->getMatchingIdentity($recip['recips'], false);
578: if (!is_null($identity_search) &&
579: ($opts['identity']->getDefault() != $identity_search)) {
580: $this->_metadata['identity_check'] = true;
581: $e = new IMP_Compose_Exception(_("Recipient address does not match the currently selected identity."));
582: $e->tied_identity = $identity_search;
583: throw $e;
584: }
585: }
586:
587: $barefrom = Horde_Mime_Address::bareAddress($header['from'], $session->get('imp', 'maildomain'));
588: $encrypt = empty($opts['encrypt']) ? 0 : $opts['encrypt'];
589:
590: 591: 592:
593: $send_msgs = array();
594: $msg_options = array(
595: 'encrypt' => $encrypt,
596: 'html' => !empty($opts['html'])
597: );
598:
599:
600: if ($prefs->getValue('use_smime') &&
601: in_array($encrypt, array(IMP_Crypt_Smime::ENCRYPT, IMP_Crypt_Smime::SIGNENC))) {
602: foreach ($recip['list'] as $val) {
603: $tmp = Horde_Mime_Address::addrObject2String($val);
604: $send_msgs[] = array(
605: 'base' => $this->_createMimeMessage(array($tmp), $body, $msg_options),
606: 'recipientob' => array($val),
607: 'recipients' => $tmp
608: );
609: }
610:
611: 612:
613: $save_msg = $this->_createMimeMessage(array($header['from']), $body, $msg_options);
614: } else {
615: 616:
617: $msg_options['from'] = $barefrom;
618: $save_msg = $this->_createMimeMessage($recip['recips'], $body, $msg_options);
619: $send_msgs[] = array(
620: 'base' => $save_msg,
621: 'recipientob' => $recip['list'],
622: 'recipients' => $recip['recips']
623: );
624: }
625:
626:
627: $headers = $this->_prepareHeaders($header, $opts);
628:
629:
630: $headers->addReceivedHeader(array(
631: 'dns' => $injector->getInstance('Net_DNS2_Resolver'),
632: 'server' => $conf['server']['name']
633: ));
634:
635:
636: if (!empty($header['replyto']) &&
637: ($header['replyto'] != $barefrom)) {
638: $headers->addHeader('Reply-to', $header['replyto']);
639: }
640:
641:
642: if (empty($opts['useragent'])) {
643: $headers->setUserAgent('Internet Messaging Program (IMP) ' . $registry->getVersion());
644: } else {
645: $headers->setUserAgent($opts['useragent']);
646: }
647: $headers->addUserAgentHeader();
648:
649:
650: if ($lang = @unserialize($prefs->getValue('reply_lang'))) {
651: $headers->addHeader('Accept-Language', implode(',', $lang));
652: }
653:
654:
655: $sentmail = $injector->getInstance('IMP_Sentmail');
656:
657: foreach ($send_msgs as $val) {
658: switch ($this->_replytype) {
659: case self::COMPOSE:
660: $senttype = IMP_Sentmail::NEWMSG;
661: break;
662:
663: case self::REPLY:
664: case self::REPLY_ALL:
665: case self::REPLY_LIST:
666: case self::REPLY_SENDER:
667: $senttype = IMP_Sentmail::REPLY;
668: break;
669:
670: case self::FORWARD:
671: case self::FORWARD_ATTACH:
672: case self::FORWARD_BODY:
673: case self::FORWARD_BOTH:
674: $senttype = IMP_Sentmail::FORWARD;
675: break;
676:
677: case self::REDIRECT:
678: $senttype = IMP_Sentmail::REDIRECT;
679: break;
680: }
681:
682: try {
683: $this->_prepSendMessageAssert($val['recipientob'], $headers, $val['base']);
684: $this->sendMessage($val['recipientob'], $headers, $val['base']);
685:
686:
687: $sentmail->log($senttype, $headers->getValue('message-id'), $val['recipients'], true);
688: } catch (IMP_Compose_Exception $e) {
689:
690: if ($e->log()) {
691: $sentmail->log($senttype, $headers->getValue('message-id'), $val['recipients'], false);
692: }
693: throw new IMP_Compose_Exception(sprintf(_("There was an error sending your message: %s"), $e->getMessage()));
694: }
695:
696: }
697:
698: $recipients = implode(', ', $recip['recips']);
699: $sent_saved = true;
700:
701: if ($this->_replytype) {
702:
703: if ($this->getMetadata('in_reply_to') &&
704: !empty($conf['maillog']['use_maillog'])) {
705: IMP_Maillog::log($this->_replytype, $this->getMetadata('in_reply_to'), $recipients);
706: }
707:
708: $imp_message = $injector->getInstance('IMP_Message');
709: $reply_uid = new IMP_Indices($this);
710:
711: switch ($this->replyType(true)) {
712: case self::FORWARD:
713: 714:
715: $imp_message->flag(array(Horde_Imap_Client::FLAG_FORWARDED), $reply_uid);
716: break;
717:
718: case self::REPLY:
719: 720:
721: $imp_message->flag(array(Horde_Imap_Client::FLAG_ANSWERED), $reply_uid);
722: $imp_message->flag(array(Horde_Imap_Client::FLAG_FLAGGED), $reply_uid, false);
723: break;
724: }
725: }
726:
727: $entry = sprintf("%s Message sent to %s from %s", $_SERVER['REMOTE_ADDR'], $recipients, $registry->getAuth());
728: Horde::logMessage($entry, 'INFO');
729:
730:
731: if (!empty($opts['sent_folder']) &&
732: ((!$prefs->isLocked('save_sent_mail') && !empty($opts['save_sent'])) ||
733: ($prefs->isLocked('save_sent_mail') &&
734: $prefs->getValue('save_sent_mail')))) {
735:
736: if (!empty($header['bcc'])) {
737: $headers->addHeader('Bcc', $header['bcc']);
738: }
739:
740:
741: $save_attach = $prefs->getValue('save_attachments');
742: if (($save_attach == 'never') ||
743: ((strpos($save_attach, 'prompt') === 0) &&
744: empty($opts['save_attachments']))) {
745: $save_msg->buildMimeIds();
746:
747: 748:
749: if ($save_msg->getType() != 'multipart/alternative') {
750: for ($i = 2;; ++$i) {
751: if (!($oldPart = $save_msg->getPart($i))) {
752: break;
753: }
754:
755: $replace_part = new Horde_Mime_Part();
756: $replace_part->setType('text/plain');
757: $replace_part->setCharset($this->charset);
758: $replace_part->setLanguage($GLOBALS['language']);
759: $replace_part->setContents('[' . _("Attachment stripped: Original attachment type") . ': "' . $oldPart->getType() . '", ' . _("name") . ': "' . $oldPart->getName(true) . '"]');
760: $save_msg->alterPart($i, $replace_part);
761: }
762: }
763: }
764:
765:
766: $fcc = $save_msg->toString(array('defserver' => $session->get('imp', 'maildomain'), 'headers' => $headers, 'stream' => true));
767:
768:
769: $sent_folder = IMP_Mailbox::get($opts['sent_folder']);
770: $sent_folder->create();
771:
772: $flags = array(Horde_Imap_Client::FLAG_SEEN);
773:
774:
775: if ($prefs->getValue('request_mdn') != 'never') {
776: $mdn = new Horde_Mime_Mdn($headers);
777: if ($mdn->getMdnReturnAddr()) {
778: $flags[] = Horde_Imap_Client::FLAG_MDNSENT;
779: }
780: }
781:
782: try {
783: $injector->getInstance('IMP_Factory_Imap')->create()->append($sent_folder, array(array('data' => $fcc, 'flags' => $flags)));
784: } catch (IMP_Imap_Exception $e) {
785: $notification->push(sprintf(_("Message sent successfully, but not saved to %s."), $sent_folder->display));
786: $sent_saved = false;
787: }
788: }
789:
790:
791: $this->deleteAllAttachments();
792:
793:
794: $this->_saveRecipients($recip['list']);
795:
796:
797: try {
798: Horde::callHook('post_sent', array($save_msg['msg'], $headers), 'imp');
799: } catch (Horde_Exception_HookNotSet $e) {}
800:
801: return $sent_saved;
802: }
803:
804: 805: 806: 807: 808: 809: 810: 811: 812: 813: 814: 815: 816:
817: protected function ($headers, array $opts = array())
818: {
819: $ob = new Horde_Mime_Headers();
820:
821: $ob->addHeader('Date', date('r'));
822: $ob->addMessageIdHeader();
823:
824: if (isset($headers['from']) && strlen($headers['from'])) {
825: $ob->addHeader('From', $headers['from']);
826: }
827:
828: if (isset($headers['to']) && strlen($headers['to'])) {
829: $ob->addHeader('To', $headers['to']);
830: } elseif (!isset($headers['cc'])) {
831: $ob->addHeader('To', 'undisclosed-recipients:;');
832: }
833:
834: if (isset($headers['cc']) && strlen($headers['cc'])) {
835: $ob->addHeader('Cc', $headers['cc']);
836: }
837:
838: if (!empty($opts['bcc']) &&
839: isset($headers['bcc']) &&
840: strlen($headers['bcc'])) {
841: $ob->addHeader('Bcc', $headers['bcc']);
842: }
843:
844: if (isset($headers['subject']) && strlen($headers['subject'])) {
845: $ob->addHeader('Subject', $headers['subject']);
846: }
847:
848: if ($this->replyType(true) == self::REPLY) {
849: if ($this->getMetadata('references')) {
850: $ob->addHeader('References', implode(' ', preg_split('|\s+|', trim($this->getMetadata('references')))));
851: }
852: if ($this->getMetadata('in_reply_to')) {
853: $ob->addHeader('In-Reply-To', $this->getMetadata('in_reply_to'));
854: }
855: }
856:
857:
858: if (!empty($opts['priority'])) {
859: switch ($opts['priority']) {
860: case 'high':
861: $ob->addHeader('Importance', 'High');
862: $ob->addHeader('X-Priority', '1 (Highest)');
863: break;
864:
865: case 'low':
866: $ob->addHeader('Importance', 'Low');
867: $ob->addHeader('X-Priority', '5 (Lowest)');
868: break;
869: }
870: }
871:
872:
873: if (!empty($opts['readreceipt']) &&
874: ($GLOBALS['prefs']->getValue('request_mdn') != 'never')) {
875: $mdn = new Horde_Mime_Mdn($ob);
876: $mdn->addMdnRequestHeaders(Horde_Mime_Address::bareAddress($ob->getValue('from'), $GLOBALS['session']->get('imp', 'maildomain')));
877: }
878:
879: return $ob;
880: }
881:
882: 883: 884: 885: 886: 887: 888: 889: 890: 891: 892:
893: public function sendMessage($email, $headers, $message)
894: {
895: $email = $this->_prepSendMessage($email, $message);
896:
897: $opts = array();
898: if ($this->getMetadata('encrypt_sign')) {
899:
900: $opts['encode'] = Horde_Mime_Part::ENCODE_7BIT;
901: }
902:
903: try {
904: $message->send($email, $headers, $GLOBALS['injector']->getInstance('IMP_Mail'), $opts);
905: } catch (Horde_Mime_Exception $e) {
906: throw new IMP_Compose_Exception($e);
907: }
908: }
909:
910: 911: 912: 913: 914: 915: 916: 917: 918: 919: 920:
921: protected function _prepSendMessage($email, $message = null)
922: {
923: 924: 925:
926: try {
927: return $this->_prepSendMessageEncode($email, is_null($message) ? 'UTF-8' : $message->getHeaderCharset());
928: } catch (IMP_Compose_Exception $e) {
929: if (is_null($message)) {
930: throw $e;
931: }
932: }
933:
934: 935: 936:
937: $message->setHeaderCharset('UTF-8');
938: return $this->_prepSendMessageEncode($email, 'UTF-8');
939: }
940:
941: 942: 943: 944: 945: 946: 947: 948: 949: 950: 951:
952: protected function _prepSendMessageAssert($email, $headers = null,
953: $message = null)
954: {
955: global $conf, $injector, $registry;
956:
957: $perms = $injector->getInstance('Horde_Core_Perms');
958:
959: if (!IMP::hasPermission('max_timelimit', array('value' => count($email)))) {
960: Horde::permissionDeniedError('imp', 'max_timelimit');
961: throw new IMP_Compose_Exception(sprintf(_("You are not allowed to send messages to more than %d recipients within %d hours."), $injector->getInstance('Horde_Core_Perms')->hasAppPermission('max_timelimit'), $conf['sentmail']['params']['limit_period']));
962: }
963:
964: 965:
966: if (!IMP::hasPermission('max_recipients', array('value' => count($email)))) {
967: Horde::permissionDeniedError('imp', 'max_recipients');
968: throw new IMP_Compose_Exception(sprintf(_("You are not allowed to send messages to more than %d recipients."), $injector->getInstance('Horde_Core_Perms')->hasAppPermission('max_recipients')));
969: }
970:
971:
972: if (!is_null($message)) {
973: try {
974: Horde::callHook('pre_sent', array($message, $headers, $this), 'imp');
975: } catch (Horde_Exception_HookNotSet $e) {}
976: }
977: }
978:
979: 980: 981: 982: 983: 984: 985: 986: 987: 988:
989: protected function _prepSendMessageEncode($email, $charset)
990: {
991: $out = array();
992:
993:
994: foreach ($email as $val) {
995:
996: if (function_exists('idn_to_ascii')) {
997: $val['host'] = @idn_to_ascii(trim($val['host']));
998: } elseif (Horde_Mime::is8bit($val['mailbox'], 'UTF-8')) {
999: throw new IMP_Compose_Exception(sprintf(_("Invalid character in e-mail address: %s."), Horde_Mime_Address::addrObject2String($val)));
1000: }
1001:
1002:
1003: if (isset($val['personal'])) {
1004: $val['personal'] = Horde_Mime::encode($val['personal'], 'UTF-8');
1005: }
1006:
1007:
1008: $tmp = Horde_Mime_Address::writeAddress($val['mailbox'], trim($val['host']), isset($val['personal']) ? $val['personal'] : '');
1009:
1010:
1011: try {
1012: Horde_Mime_Address::parseAddressList($tmp, array(
1013: 'validate' => true
1014: ));
1015: } catch (Horde_Mime_Exception $e) {
1016: throw new IMP_Compose_Exception(sprintf(_("Invalid e-mail address (%s)."), $tmp));
1017: }
1018:
1019: $out[] = $tmp;
1020: }
1021:
1022: return implode(', ', $out);
1023: }
1024:
1025: 1026: 1027: 1028: 1029:
1030: protected function _saveRecipients($recipients)
1031: {
1032: global $notification, $prefs, $registry;
1033:
1034: if (empty($recipients) ||
1035: !$prefs->getValue('save_recipients') ||
1036: !$registry->hasMethod('contacts/import') ||
1037: !$registry->hasMethod('contacts/search')) {
1038: return;
1039: }
1040:
1041: $abook = $prefs->getValue('add_source');
1042: if (empty($abook)) {
1043: return;
1044: }
1045:
1046: 1047:
1048: $emails = array();
1049: foreach ($recipients as $recipient) {
1050: $emails[] = $recipient['mailbox'] . '@' . $recipient['host'];
1051: }
1052:
1053: try {
1054: $results = $registry->call('contacts/search', array($emails, array($abook), array($abook => array('email')), true, false, array('email')));
1055: } catch (Horde_Exception $e) {
1056: Horde::logMessage($e, 'ERR');
1057: $notification->push(_("Could not save recipients."));
1058: return;
1059: }
1060:
1061: foreach ($recipients as $recipient) {
1062:
1063: if (isset($results[$recipient['mailbox'] . '@' . $recipient['host']]) &&
1064: count($results[$recipient['mailbox'] . '@' . $recipient['host']])) {
1065: continue;
1066: }
1067:
1068: 1069:
1070: $name = '';
1071: if (isset($recipient['personal'])) {
1072: $name = trim($recipient['personal']);
1073: if (preg_match('/^(["\']).*\1$/', $name)) {
1074: $name = substr($name, 1, -1);
1075: }
1076: }
1077: if (empty($name)) {
1078: $name = $recipient['mailbox'];
1079: }
1080: $name = Horde_Mime::decode($name, 'UTF-8');
1081:
1082: try {
1083: $registry->call('contacts/import', array(array('name' => $name, 'email' => $recipient['mailbox'] . '@' . $recipient['host']), 'array', $abook));
1084: $notification->push(sprintf(_("Entry \"%s\" was successfully added to the address book"), $name), 'horde.success');
1085: } catch (Horde_Exception $e) {
1086: if ($e->getCode() == 'horde.error') {
1087: $notification->push($e, $e->getCode());
1088: }
1089: }
1090: }
1091: }
1092:
1093: 1094: 1095: 1096: 1097: 1098: 1099: 1100: 1101: 1102: 1103: 1104: 1105: 1106: 1107:
1108: public function recipientList($hdr)
1109: {
1110: $addrlist = $header = $recips = array();
1111:
1112: foreach (array('to', 'cc', 'bcc') as $key) {
1113: if (!isset($hdr[$key])) {
1114: continue;
1115: }
1116:
1117: $arr = array_filter(array_map('trim', Horde_Mime_Address::explode($hdr[$key], ',;')));
1118: $tmp = array();
1119:
1120: foreach ($arr as $email) {
1121: if (!strlen($email)) {
1122: continue;
1123: }
1124:
1125: try {
1126: $obs = Horde_Mime_Address::parseAddressList($email, array(
1127: 'defserver' => $GLOBALS['session']->get('imp', 'maildomain'),
1128: 'nestgroups' => true,
1129: 'validate' => false
1130: ));
1131: } catch (Horde_Mime_Exception $e) {
1132: throw new IMP_Compose_Exception(sprintf(_("Invalid e-mail address: %s."), $email));
1133: }
1134:
1135: foreach ($obs as $ob) {
1136: if (isset($ob['groupname'])) {
1137: $group_addresses = array();
1138: foreach ($ob['addresses'] as $ad) {
1139: $addrlist[] = $ad;
1140: $recips[] = $group_addresses[] = Horde_Mime_Address::writeAddress($ad['mailbox'], trim($ad['host']), isset($ad['personal']) ? $ad['personal'] : '');
1141: }
1142:
1143: $tmp[] = Horde_Mime_Address::writeGroupAddress($ob['groupname'], $group_addresses) . ' ';
1144: } else {
1145: $addrlist[] = $ob;
1146: $recips[] = $tmp[] = Horde_Mime_Address::writeAddress($ob['mailbox'], trim($ob['host']), isset($ob['personal']) ? $ob['personal'] : '');
1147: }
1148: }
1149: }
1150:
1151: $header[$key] = implode(', ', $tmp);
1152: }
1153:
1154: return array(
1155: 'has_input' => isset($obs),
1156: 'header' => $header,
1157: 'list' => $addrlist,
1158: 'recips' => $recips
1159: );
1160: }
1161:
1162: 1163: 1164: 1165: 1166: 1167: 1168: 1169: 1170: 1171: 1172: 1173: 1174: 1175: 1176: 1177: 1178: 1179:
1180: protected function _createMimeMessage($to, $body, array $options = array())
1181: {
1182: $body = Horde_String::convertCharset($body, 'UTF-8', $this->charset);
1183:
1184: if (!empty($options['html'])) {
1185: $body_html = $body;
1186: $body = $GLOBALS['injector']->getInstance('Horde_Core_Factory_TextFilter')->filter($body, 'Html2text', array('wrap' => false, 'charset' => $this->charset));
1187: }
1188:
1189:
1190: if (empty($options['nofinal'])) {
1191: try {
1192: if ($trailer = Horde::callHook('trailer', array(), 'imp')) {
1193: $body .= $trailer;
1194: if (!empty($options['html'])) {
1195: $body_html .= $this->text2html($trailer);
1196: }
1197: }
1198: } catch (Horde_Exception_HookNotSet $e) {}
1199: }
1200:
1201:
1202: $textBody = new Horde_Mime_Part();
1203: $textBody->setType('text/plain');
1204: $textBody->setCharset($this->charset);
1205: $textBody->setDisposition('inline');
1206:
1207:
1208: $flowed = new Horde_Text_Flowed($body, $this->charset);
1209: $flowed->setDelSp(true);
1210: $textBody->setContentTypeParameter('format', 'flowed');
1211: $textBody->setContentTypeParameter('DelSp', 'Yes');
1212: $textBody->setContents($flowed->toFlowed());
1213:
1214: 1215:
1216: if (!empty($options['html'])) {
1217: $htmlBody = new Horde_Mime_Part();
1218: $htmlBody->setType('text/html');
1219: $htmlBody->setCharset($this->charset);
1220: $htmlBody->setDisposition('inline');
1221: $htmlBody->setDescription(Horde_String::convertCharset(_("HTML Message"), 'UTF-8', $this->charset));
1222:
1223: 1224: 1225:
1226: $styles = array();
1227: if ($font_family = $GLOBALS['prefs']->getValue('compose_html_font_family')) {
1228: $styles[] = 'font-family:' . $font_family;
1229: }
1230: if ($font_size = intval($GLOBALS['prefs']->getValue('compose_html_font_size'))) {
1231: $styles[] = 'font-size:' . $font_size . 'px';
1232: }
1233:
1234: if (!empty($styles)) {
1235: $body_html = '<body style="' . implode(';', $styles) . '">' .
1236: $body_html .
1237: '</body>';
1238: }
1239:
1240: $htmlBody->setContents($GLOBALS['injector']->getInstance('Horde_Core_Factory_TextFilter')->filter($body_html, 'cleanhtml', array('charset' => $this->charset)));
1241:
1242: $textBody->setDescription(Horde_String::convertCharset(_("Plaintext Message"), 'UTF-8', $this->charset));
1243:
1244: $textpart = new Horde_Mime_Part();
1245: $textpart->setType('multipart/alternative');
1246: $textpart->addPart($textBody);
1247: $textpart->setHeaderCharset($this->charset);
1248:
1249: if (empty($options['nofinal'])) {
1250: try {
1251: $htmlBody = $this->_convertToMultipartRelated($htmlBody);
1252: } catch (Horde_Exception $e) {}
1253: }
1254:
1255: $textpart->addPart($htmlBody);
1256: } else {
1257: $textpart = $textBody;
1258: }
1259:
1260:
1261: $attach_flag = true;
1262: if (empty($options['noattach']) && count($this)) {
1263: if (($this->_linkAttach &&
1264: $GLOBALS['conf']['compose']['link_attachments']) ||
1265: !empty($GLOBALS['conf']['compose']['link_all_attachments'])) {
1266: $base = $this->linkAttachments($textpart);
1267:
1268: if ($this->_pgpAttachPubkey ||
1269: ($this->_attachVCard !== false)) {
1270: $new_body = new Horde_Mime_Part();
1271: $new_body->setType('multipart/mixed');
1272: $new_body->addPart($base);
1273: $base = $new_body;
1274: } else {
1275: $attach_flag = false;
1276: }
1277: } else {
1278: $base = new Horde_Mime_Part();
1279: $base->setType('multipart/mixed');
1280: $base->addPart($textpart);
1281: foreach ($this as $id => $val) {
1282: $base->addPart($this->buildAttachment($id));
1283: }
1284: }
1285: } elseif ($this->_pgpAttachPubkey ||
1286: ($this->_attachVCard !== false)) {
1287: $base = new Horde_Mime_Part();
1288: $base->setType('multipart/mixed');
1289: $base->addPart($textpart);
1290: } else {
1291: $base = $textpart;
1292: $attach_flag = false;
1293: }
1294:
1295: if ($attach_flag) {
1296: if ($this->_pgpAttachPubkey) {
1297: $imp_pgp = $GLOBALS['injector']->getInstance('IMP_Crypt_Pgp');
1298: $base->addPart($imp_pgp->publicKeyMIMEPart());
1299: }
1300:
1301: if ($this->_attachVCard !== false) {
1302: try {
1303: $vcard = $GLOBALS['registry']->call('contacts/ownVCard');
1304:
1305: $vpart = new Horde_Mime_Part();
1306: $vpart->setType('text/x-vcard');
1307: $vpart->setCharset('UTF-8');
1308: $vpart->setContents($vcard);
1309: $vpart->setName($this->_attachVCard);
1310:
1311: $base->addPart($vpart);
1312: } catch (Horde_Exception $e) {}
1313: }
1314: }
1315:
1316:
1317: $encrypt = empty($options['encrypt'])
1318: ? IMP::ENCRYPT_NONE
1319: : $options['encrypt'];
1320: if ($GLOBALS['prefs']->getValue('use_pgp') &&
1321: !empty($GLOBALS['conf']['gnupg']['path']) &&
1322: in_array($encrypt, array(IMP_Crypt_Pgp::ENCRYPT, IMP_Crypt_Pgp::SIGN, IMP_Crypt_Pgp::SIGNENC, IMP_Crypt_Pgp::SYM_ENCRYPT, IMP_Crypt_Pgp::SYM_SIGNENC))) {
1323: $imp_pgp = $GLOBALS['injector']->getInstance('IMP_Crypt_Pgp');
1324: $symmetric_passphrase = null;
1325:
1326: switch ($encrypt) {
1327: case IMP_Crypt_Pgp::SIGN:
1328: case IMP_Crypt_Pgp::SIGNENC:
1329: case IMP_Crypt_Pgp::SYM_SIGNENC:
1330:
1331: $passphrase = $imp_pgp->getPassphrase('personal');
1332: if (empty($passphrase)) {
1333: $e = new IMP_Compose_Exception(_("PGP: Need passphrase for personal private key."));
1334: $e->encrypt = 'pgp_passphrase_dialog';
1335: throw $e;
1336: }
1337: break;
1338:
1339: case IMP_Crypt_Pgp::SYM_ENCRYPT:
1340: case IMP_Crypt_Pgp::SYM_SIGNENC:
1341: 1342:
1343: $symmetric_passphrase = $imp_pgp->getPassphrase('symmetric', 'imp_compose_' . $this->_cacheid);
1344: if (empty($symmetric_passphrase)) {
1345: $e = new IMP_Compose_Exception(_("PGP: Need passphrase to encrypt your message with."));
1346: $e->encrypt = 'pgp_symmetric_passphrase_dialog';
1347: throw $e;
1348: }
1349: break;
1350: }
1351:
1352:
1353: try {
1354: switch ($encrypt) {
1355: case IMP_Crypt_Pgp::SIGN:
1356: $base = $imp_pgp->impSignMimePart($base);
1357: $this->_metadata['encrypt_sign'] = true;
1358: break;
1359:
1360: case IMP_Crypt_Pgp::ENCRYPT:
1361: case IMP_Crypt_Pgp::SYM_ENCRYPT:
1362: $to_list = empty($options['from'])
1363: ? $to
1364: : array_keys(array_flip(array_merge($to, array($options['from']))));
1365: $base = $imp_pgp->impEncryptMimePart($base, $to_list, ($encrypt == IMP_Crypt_Pgp::SYM_ENCRYPT) ? $symmetric_passphrase : null);
1366: break;
1367:
1368: case IMP_Crypt_Pgp::SIGNENC:
1369: case IMP_Crypt_Pgp::SYM_SIGNENC:
1370: $to_list = empty($options['from'])
1371: ? $to
1372: : array_keys(array_flip(array_merge($to, array($options['from']))));
1373: $base = $imp_pgp->impSignAndEncryptMimePart($base, $to_list, ($encrypt == IMP_Crypt_Pgp::SYM_SIGNENC) ? $symmetric_passphrase : null);
1374: break;
1375: }
1376: } catch (Horde_Exception $e) {
1377: throw new IMP_Compose_Exception(_("PGP Error: ") . $e->getMessage(), $e->getCode());
1378: }
1379: } elseif ($GLOBALS['prefs']->getValue('use_smime') &&
1380: in_array($encrypt, array(IMP_Crypt_Smime::ENCRYPT, IMP_Crypt_Smime::SIGN, IMP_Crypt_Smime::SIGNENC))) {
1381: $imp_smime = $GLOBALS['injector']->getInstance('IMP_Crypt_Smime');
1382:
1383:
1384: if (in_array($encrypt, array(IMP_Crypt_Smime::SIGN, IMP_Crypt_Smime::SIGNENC))) {
1385: $passphrase = $imp_smime->getPassphrase();
1386: if ($passphrase === false) {
1387: $e = new IMP_Compose_Exception(_("S/MIME Error: Need passphrase for personal private key."));
1388: $e->encrypt = 'smime_passphrase_dialog';
1389: throw $e;
1390: }
1391: }
1392:
1393:
1394: try {
1395: switch ($encrypt) {
1396: case IMP_Crypt_Smime::SIGN:
1397: $base = $imp_smime->IMPsignMIMEPart($base);
1398: $this->_metadata['encrypt_sign'] = true;
1399: break;
1400:
1401: case IMP_Crypt_Smime::ENCRYPT:
1402: $base = $imp_smime->IMPencryptMIMEPart($base, $to[0]);
1403: break;
1404:
1405: case IMP_Crypt_Smime::SIGNENC:
1406: $base = $imp_smime->IMPsignAndEncryptMIMEPart($base, $to[0]);
1407: break;
1408: }
1409: } catch (Horde_Exception $e) {
1410: throw new IMP_Compose_Exception(_("S/MIME Error: ") . $e->getMessage(), $e->getCode());
1411: }
1412: }
1413:
1414:
1415: $base->isBasePart(true);
1416:
1417: return $base;
1418: }
1419:
1420: 1421: 1422: 1423: 1424: 1425: 1426: 1427: 1428: 1429: 1430: 1431: 1432: 1433: 1434: 1435: 1436: 1437: 1438: 1439: 1440:
1441: public function replyMessage($type, $contents, $to = null)
1442: {
1443: global $prefs;
1444:
1445:
1446: $header = array(
1447: 'to' => '',
1448: 'cc' => '',
1449: 'bcc' => '',
1450: 'subject' => ''
1451: );
1452:
1453: $h = $contents->getHeader();
1454: $match_identity = $this->_getMatchingIdentity($h);
1455: $reply_type = self::REPLY_SENDER;
1456:
1457: if (!$this->_replytype) {
1458: $this->_metadata['mailbox'] = $contents->getMailbox();
1459: $this->_metadata['uid'] = $contents->getUid();
1460: $this->changed = 'changed';
1461:
1462:
1463: if (($msg_id = $h->getValue('message-id'))) {
1464: $this->_metadata['in_reply_to'] = chop($msg_id);
1465:
1466: if (($refs = $h->getValue('references'))) {
1467: $refs .= ' ' . $this->_metadata['in_reply_to'];
1468: } else {
1469: $refs = $this->_metadata['in_reply_to'];
1470: }
1471: $this->_metadata['references'] = $refs;
1472: }
1473: }
1474:
1475: $subject = $h->getValue('subject');
1476: $header['subject'] = empty($subject)
1477: ? 'Re: '
1478: : 'Re: ' . $GLOBALS['injector']->getInstance('IMP_Factory_Imap')->create()->getUtils()->getBaseSubject($subject, array('keepblob' => true));
1479:
1480: $force = false;
1481: if (in_array($type, array(self::REPLY_AUTO, self::REPLY_SENDER))) {
1482: if (($header['to'] = $to) ||
1483: ($header['to'] = Horde_Mime_Address::addrArray2String($h->getOb('reply-to')))) {
1484: $force = true;
1485: } else {
1486: $header['to'] = Horde_Mime_Address::addrArray2String($h->getOb('from'));
1487: }
1488: }
1489:
1490:
1491: if (in_array($type, array(self::REPLY_AUTO, self::REPLY_LIST))) {
1492: $imp_ui = new IMP_Ui_Message();
1493: $list_info = $imp_ui->getListInformation($h);
1494: } else {
1495: $list_info = null;
1496: }
1497:
1498: if (!is_null($list_info) && !empty($list_info['reply_list'])) {
1499: 1500:
1501: if (Horde_Mime_Address::bareAddress($list_info['reply_list']) != Horde_Mime_Address::bareAddress($header['to'])) {
1502: $header['to'] = $list_info['reply_list'];
1503: $reply_type = self::REPLY_LIST;
1504: }
1505: } elseif (in_array($type, array(self::REPLY_ALL, self::REPLY_AUTO))) {
1506:
1507: if ($type == self::REPLY_AUTO) {
1508: $header['to'] = '';
1509: }
1510:
1511:
1512: $identity = $GLOBALS['injector']->getInstance('IMP_Identity');
1513: $all_addrs = array_keys($identity->getAllFromAddresses(true));
1514:
1515: 1516: 1517: 1518:
1519: $cc_addrs = array();
1520: foreach (array('reply-to', 'from', 'to', 'cc') as $val) {
1521: 1522:
1523: if ($force && ($val == 'from')) {
1524: continue;
1525: }
1526:
1527: $ob = $h->getOb($val);
1528: if (!empty($ob)) {
1529: $addr_obs = Horde_Mime_Address::getAddressesFromObject($ob, array('filter' => $all_addrs));
1530: if (!empty($addr_obs)) {
1531: if (isset($addr_obs[0]['groupname'])) {
1532: $cc_addrs = array_merge($cc_addrs, $addr_obs);
1533: foreach ($addr_obs[0]['addresses'] as $addr_ob) {
1534: $all_addrs[] = $addr_ob['inner'];
1535: }
1536: } elseif (($val != 'to') ||
1537: is_null($list_info) ||
1538: !$force ||
1539: empty($list_info['exists'])) {
1540: 1541: 1542:
1543: if (in_array($val, array('from', 'reply-to'))) {
1544: 1545:
1546: if (!$addr_obs[0]['personal'] &&
1547: ($to_ob = $h->getOb('from')) &&
1548: $to_ob[0]['personal'] &&
1549: ($to_addr = Horde_Mime_Address::addrArray2String($to_ob)) &&
1550: Horde_Mime_Address::bareAddress($to_addr) == $addr_obs[0]['address']) {
1551: $header['to'] = $to_addr;
1552: } else {
1553: $header['to'] = $addr_obs[0]['address'];
1554: }
1555: } else {
1556: $cc_addrs = array_merge($cc_addrs, $addr_obs);
1557: }
1558:
1559: foreach ($addr_obs as $addr_ob) {
1560: $all_addrs[] = $addr_ob['inner'];
1561: }
1562: }
1563: }
1564: }
1565: }
1566:
1567: 1568: 1569: 1570:
1571: $hdr_cc = array();
1572: foreach ($cc_addrs as $ob) {
1573: if (isset($ob['groupname'])) {
1574: $hdr_cc[] = Horde_Mime_Address::writeGroupAddress($ob['groupname'], $ob['addresses']) . ' ';
1575: } else {
1576: $hdr_cc[] = $ob['address'] . ', ';
1577: }
1578: }
1579:
1580: if (count($hdr_cc)) {
1581: $reply_type = self::REPLY_ALL;
1582: }
1583: $header[empty($header['to']) ? 'to' : 'cc'] = rtrim(implode('', $hdr_cc), ' ,');
1584:
1585:
1586: $header['bcc'] = Horde_Mime_Address::addrArray2String($h->getOb('bcc') + $identity->getBccAddresses(), array('filter' => $all_addrs));
1587: }
1588:
1589: if (!$this->_replytype || ($reply_type != $this->_replytype)) {
1590: $this->_replytype = $reply_type;
1591: $this->changed = 'changed';
1592: }
1593:
1594: $ret = $this->replyMessageText($contents);
1595: if ($ret['charset'] != $this->charset) {
1596: $this->charset = $ret['charset'];
1597: $this->changed = 'changed';
1598: }
1599: unset($ret['charset']);
1600:
1601: if ($type == self::REPLY_AUTO) {
1602: switch ($reply_type) {
1603: case self::REPLY_ALL:
1604: try {
1605: $recip_list = $this->recipientList($header);
1606: $ret['reply_recip'] = count($recip_list['list']);
1607: } catch (IMP_Compose_Exception $e) {
1608: $ret['reply_recip'] = 0;
1609: }
1610: break;
1611:
1612: case self::REPLY_LIST:
1613: $addr_ob = Horde_Mime_Address::parseAddressList($h->getValue('list-id'));
1614: if (isset($addr_ob[0]['personal'])) {
1615: $ret['reply_list_id'] = $addr_ob[0]['personal'];
1616: }
1617: break;
1618: }
1619: }
1620:
1621: if (($lang = $h->getValue('accept-language')) ||
1622: ($lang = $h->getValue('x-accept-language'))) {
1623: $langs = array();
1624: foreach (explode(',', $lang) as $val) {
1625: if (($name = Horde_Nls::getLanguageISO($val)) !== null) {
1626: $langs[trim($val)] = $name;
1627: }
1628: }
1629: $ret['lang'] = array_unique($langs);
1630:
1631: 1632:
1633: if ((count($ret['lang']) == 1) &&
1634: reset($ret['lang']) &&
1635: (substr(key($ret['lang']), 0, 2) == substr($GLOBALS['language'], 0, 2))) {
1636: unset($ret['lang']);
1637: }
1638: }
1639:
1640: return array_merge(array(
1641: 'headers' => $header,
1642: 'identity' => $match_identity,
1643: 'type' => $reply_type
1644: ), $ret);
1645: }
1646:
1647: 1648: 1649: 1650: 1651: 1652: 1653: 1654: 1655: 1656: 1657: 1658: 1659:
1660: public function replyMessageText($contents, array $opts = array())
1661: {
1662: global $prefs;
1663:
1664: if (!$prefs->getValue('reply_quote')) {
1665: return array(
1666: 'body' => '',
1667: 'charset' => '',
1668: 'format' => 'text'
1669: );
1670: }
1671:
1672: $h = $contents->getHeader();
1673:
1674: $from = Horde_Mime_Address::addrArray2String($h->getOb('from'));
1675:
1676: if ($prefs->getValue('reply_headers') && !empty($h)) {
1677: $msg_pre = '----- ' .
1678: ($from ? sprintf(_("Message from %s"), $from) : _("Message")) .
1679:
1680: " ---------\n" .
1681: $this->_getMsgHeaders($h) . "\n\n";
1682:
1683: $msg_post = "\n\n----- " .
1684: ($from ? sprintf(_("End message from %s"), $from) : _("End message")) .
1685: " -----\n";
1686: } else {
1687: $msg_pre = $this->_expandAttribution($prefs->getValue('attrib_text'), $from, $h) . "\n\n";
1688: $msg_post = '';
1689: }
1690:
1691: list($compose_html, $force_html) = $this->_msgTextFormat($opts, 'reply_format');
1692:
1693: $msg_text = $this->_getMessageText($contents, array(
1694: 'html' => $compose_html,
1695: 'replylimit' => true,
1696: 'toflowed' => true
1697: ));
1698:
1699: if (!empty($msg_text) &&
1700: (($msg_text['mode'] == 'html') || $force_html)) {
1701: $msg = '<p>' . $this->text2html(trim($msg_pre)) . '</p>' .
1702: self::HTML_BLOCKQUOTE .
1703: (($msg_text['mode'] == 'text') ? $this->text2html($msg_text['flowed'] ? $msg_text['flowed'] : $msg_text['text']) : $msg_text['text']) .
1704: '</blockquote><br />' .
1705: ($msg_post ? $this->text2html($msg_post) : '') . '<br />';
1706: $msg_text['mode'] = 'html';
1707: } else {
1708: $msg = empty($msg_text['text'])
1709: ? '[' . _("No message body text") . ']'
1710: : $msg_pre . $msg_text['text'] . $msg_post;
1711: $msg_text['mode'] = 'text';
1712: }
1713:
1714:
1715:
1716: if (($msg_text['charset'] == 'us-ascii') &&
1717: (Horde_Mime::is8bit($msg_pre, 'UTF-8') ||
1718: Horde_Mime::is8bit($msg_post, 'UTF-8'))) {
1719: $msg_text['charset'] = 'UTF-8';
1720: }
1721:
1722: return array(
1723: 'body' => $msg . "\n",
1724: 'charset' => $msg_text['charset'],
1725: 'format' => $msg_text['mode']
1726: );
1727: }
1728:
1729: 1730: 1731: 1732: 1733: 1734: 1735: 1736:
1737: protected function _msgTextFormat($opts, $pref_name)
1738: {
1739: if (IMP::getViewMode() == 'mimp') {
1740: $compose_html = $force_html = false;
1741: } elseif (!empty($opts['format'])) {
1742: $compose_html = $force_html = ($opts['format'] == 'html');
1743: } elseif ($GLOBALS['prefs']->getValue('compose_html')) {
1744: $compose_html = $force_html = true;
1745: } else {
1746: $compose_html = $GLOBALS['prefs']->getValue($pref_name);
1747: $force_html = false;
1748: }
1749:
1750: return array($compose_html, $force_html);
1751: }
1752:
1753: 1754: 1755: 1756: 1757: 1758: 1759: 1760: 1761: 1762: 1763: 1764: 1765: 1766: 1767: 1768: 1769: 1770: 1771: 1772: 1773: 1774: 1775: 1776: 1777: 1778: 1779: 1780: 1781: 1782: 1783: 1784:
1785: public function forwardMessage($type, $contents, $attach = true)
1786: {
1787:
1788: $header = array(
1789: 'to' => '',
1790: 'cc' => '',
1791: 'bcc' => '',
1792: 'subject' => ''
1793: );
1794:
1795: if ($type == self::FORWARD_AUTO) {
1796: switch ($GLOBALS['prefs']->getValue('forward_default')) {
1797: case 'body':
1798: $type = self::FORWARD_BODY;
1799: break;
1800:
1801: case 'both':
1802: $type = self::FORWARD_BOTH;
1803: break;
1804:
1805: case 'attach':
1806: default:
1807: $type = self::FORWARD_ATTACH;
1808: break;
1809: }
1810: }
1811:
1812: $h = $contents->getHeader();
1813: $format = 'text';
1814: $msg = '';
1815:
1816: $this->_metadata['mailbox'] = $contents->getMailbox();
1817: $this->_metadata['uid'] = $contents->getUid();
1818:
1819: 1820:
1821: $this->_metadata['in_reply_to'] = trim($h->getValue('message-id'));
1822: $this->_replytype = $type;
1823: $this->changed = 'changed';
1824:
1825: $header['subject'] = $h->getValue('subject');
1826: if (!empty($header['subject'])) {
1827: $subject = $GLOBALS['injector']->getInstance('IMP_Factory_Imap')->create()->getUtils()->getBaseSubject($header['subject'], array('keepblob' => true));
1828: $header['title'] = _("Forward") . ': ' . $subject;
1829: $header['subject'] = 'Fwd: ' . $subject;
1830: } else {
1831: $header['title'] = _("Forward");
1832: $header['subject'] = 'Fwd:';
1833: }
1834:
1835: if ($attach &&
1836: in_array($type, array(self::FORWARD_ATTACH, self::FORWARD_BOTH))) {
1837: try {
1838: $this->attachImapMessage(new IMP_Indices($contents));
1839: } catch (IMP_Exception $e) {}
1840: }
1841:
1842: if (in_array($type, array(self::FORWARD_BODY, self::FORWARD_BOTH))) {
1843: $ret = $this->forwardMessageText($contents);
1844: $this->charset = $ret['charset'];
1845: unset($ret['charset']);
1846: } else {
1847: $ret = array(
1848: 'body' => '',
1849: 'format' => $GLOBALS['prefs']->getValue('compose_html') ? 'html' : 'text'
1850: );
1851: }
1852:
1853: return array_merge(array(
1854: 'headers' => $header,
1855: 'identity' => $this->_getMatchingIdentity($h),
1856: 'type' => $type
1857: ), $ret);
1858: }
1859:
1860: 1861: 1862: 1863: 1864: 1865: 1866: 1867: 1868: 1869: 1870: 1871: 1872:
1873: public function forwardMessageText($contents, array $opts = array())
1874: {
1875: global $prefs;
1876:
1877: $h = $contents->getHeader();
1878:
1879: $from = Horde_Mime_Address::addrArray2String($h->getOb('from'));
1880:
1881: $msg_pre = "\n----- " .
1882: ($from ? sprintf(_("Forwarded message from %s"), $from) : _("Forwarded message")) .
1883: " -----\n" . $this->_getMsgHeaders($h) . "\n";
1884: $msg_post = "\n\n----- " . _("End forwarded message") . " -----\n";
1885:
1886: list($compose_html, $force_html) = $this->_msgTextFormat($opts, 'forward_format');
1887:
1888: $msg_text = $this->_getMessageText($contents, array(
1889: 'html' => $compose_html
1890: ));
1891:
1892: if (!empty($msg_text) &&
1893: (($msg_text['mode'] == 'html') || $force_html)) {
1894: $msg = $this->text2html($msg_pre) .
1895: (($msg_text['mode'] == 'text') ? $this->text2html($msg_text['text']) : $msg_text['text']) .
1896: $this->text2html($msg_post);
1897: $format = 'html';
1898: } else {
1899: $msg = $msg_pre . $msg_text['text'] . $msg_post;
1900: $format = 'text';
1901: }
1902:
1903:
1904:
1905: if (($msg_text['charset'] == 'us-ascii') &&
1906: (Horde_Mime::is8bit($msg_pre, 'UTF-8') ||
1907: Horde_Mime::is8bit($msg_post, 'UTF-8'))) {
1908: $msg_text['charset'] = 'UTF-8';
1909: }
1910:
1911: return array(
1912: 'body' => $msg,
1913: 'charset' => $msg_text['charset'],
1914: 'format' => $format
1915: );
1916: }
1917:
1918: 1919: 1920: 1921: 1922:
1923: public function redirectMessage(IMP_Indices $indices)
1924: {
1925: $this->_metadata['redirect_indices'] = $indices;
1926: $this->_replytype = self::REDIRECT;
1927: $this->changed = 'changed';
1928: }
1929:
1930: 1931: 1932: 1933: 1934: 1935: 1936: 1937: 1938: 1939: 1940: 1941: 1942: 1943: 1944: 1945:
1946: public function sendRedirectMessage($to, $log = true)
1947: {
1948: $recip = $this->recipientList(array('to' => $to));
1949:
1950: $identity = $GLOBALS['injector']->getInstance('IMP_Identity');
1951: $from_addr = $identity->getFromAddress();
1952:
1953: $out = array();
1954:
1955: foreach ($this->getMetadata('redirect_indices') as $val) {
1956: foreach ($val->uids as $val2) {
1957: try {
1958: $contents = $GLOBALS['injector']->getInstance('IMP_Factory_Contents')->create($val->mbox->getIndicesOb($val2));
1959: } catch (IMP_Exception $e) {
1960: throw new IMP_Compose_Exception(_("Error when redirecting message."));
1961: }
1962:
1963: $headers = $contents->getHeader();
1964:
1965: 1966:
1967: $headers->removeHeader('return-path');
1968: $headers->addHeader('Return-Path', $from_addr);
1969:
1970: 1971:
1972: $resent_headers = new Horde_Mime_Headers();
1973: $resent_headers->addHeader('Resent-Date', date('r'));
1974: $resent_headers->addHeader('Resent-From', $from_addr);
1975: $resent_headers->addHeader('Resent-To', $recip['header']['to']);
1976: $resent_headers->addHeader('Resent-Message-ID', Horde_Mime::generateMessageId());
1977:
1978: $header_text = trim($resent_headers->toString(array('encode' => 'UTF-8'))) . "\n" . trim($contents->getHeader(IMP_Contents::HEADER_TEXT));
1979:
1980: $this->_prepSendMessageAssert($recip['list']);
1981: $to = $this->_prepSendMessage($recip['list']);
1982: $hdr_array = $headers->toArray(array('charset' => 'UTF-8'));
1983: $hdr_array['_raw'] = $header_text;
1984:
1985: try {
1986: $GLOBALS['injector']->getInstance('IMP_Mail')->send($to, $hdr_array, $contents->getBody());
1987: } catch (Horde_Mail_Exception $e) {
1988: throw new IMP_Compose_Exception($e);
1989: }
1990:
1991: $recipients = implode(', ', $recip['recips']);
1992:
1993: Horde::logMessage(sprintf("%s Redirected message sent to %s from %s", $_SERVER['REMOTE_ADDR'], $recipients, $GLOBALS['registry']->getAuth()), 'INFO');
1994:
1995: if ($log) {
1996:
1997: if (!empty($GLOBALS['conf']['maillog']['use_maillog'])) {
1998: IMP_Maillog::log(self::REDIRECT, $headers->getValue('message-id'), $recipients);
1999: }
2000:
2001: $GLOBALS['injector']->getInstance('IMP_Sentmail')->log(IMP_Sentmail::REDIRECT, $headers->getValue('message-id'), $recipients);
2002: }
2003:
2004: $tmp = new stdClass;
2005: $tmp->contents = $contents;
2006: $tmp->headers = $headers;
2007: $tmp->mbox = $val->mbox;
2008: $tmp->uid = $val2;
2009:
2010: $out[] = $tmp;
2011: }
2012: }
2013:
2014: return $out;
2015: }
2016:
2017: 2018: 2019: 2020: 2021: 2022: 2023:
2024: protected function _getMatchingIdentity($h)
2025: {
2026: $msgAddresses = array();
2027:
2028: 2029: 2030:
2031: foreach (array('from', 'to', 'cc', 'bcc') as $val) {
2032: $msgAddresses[] = $h->getValue($val);
2033: }
2034:
2035: return $GLOBALS['injector']->getInstance('IMP_Identity')->getMatchingIdentity($msgAddresses);
2036: }
2037:
2038: 2039: 2040: 2041: 2042: 2043: 2044: 2045: 2046: 2047:
2048: public function attachImapMessage($indices)
2049: {
2050: if (!count($indices)) {
2051: return false;
2052: }
2053:
2054: $attached = 0;
2055: foreach ($indices as $ob) {
2056: foreach ($ob->uids as $idx) {
2057: ++$attached;
2058: $contents = $GLOBALS['injector']->getInstance('IMP_Factory_Contents')->create(new IMP_Indices($ob->mbox, $idx));
2059: $headerob = $contents->getHeader();
2060:
2061: $part = new Horde_Mime_Part();
2062: $part->setCharset('UTF-8');
2063: $part->setType('message/rfc822');
2064: $part->setName(_("Forwarded Message"));
2065: $part->setContents($contents->fullMessageText(array(
2066: 'stream' => true
2067: )), array(
2068: 'usestream' => true
2069: ));
2070:
2071:
2072: $this->addMimePartAttachment($part);
2073: }
2074: }
2075:
2076: if ($attached > 1) {
2077: return 'Fwd: ' . sprintf(_("%u Forwarded Messages"), $attached);
2078: }
2079:
2080: if ($name = $headerob->getValue('subject')) {
2081: $name = Horde_String::truncate($name, 80);
2082: } else {
2083: $name = _("[No Subject]");
2084: }
2085:
2086: return 'Fwd: ' . $GLOBALS['injector']->getInstance('IMP_Factory_Imap')->create()->getUtils()->getBaseSubject($name, array('keepblob' => true));
2087: }
2088:
2089: 2090: 2091: 2092: 2093: 2094: 2095:
2096: protected function ($h)
2097: {
2098: $tmp = array();
2099:
2100: if (($ob = $h->getValue('date'))) {
2101: $tmp[_("Date")] = $ob;
2102: }
2103:
2104: if (($ob = Horde_Mime_Address::addrArray2String($h->getOb('from')))) {
2105: $tmp[_("From")] = $ob;
2106: }
2107:
2108: if (($ob = Horde_Mime_Address::addrArray2String($h->getOb('reply-to')))) {
2109: $tmp[_("Reply-To")] = $ob;
2110: }
2111:
2112: if (($ob = $h->getValue('subject'))) {
2113: $tmp[_("Subject")] = $ob;
2114: }
2115:
2116: if (($ob = Horde_Mime_Address::addrArray2String($h->getOb('to')))) {
2117: $tmp[_("To")] = $ob;
2118: }
2119:
2120: if (($ob = Horde_Mime_Address::addrArray2String($h->getOb('cc')))) {
2121: $tmp[_("Cc")] = $ob;
2122: }
2123:
2124: $max = max(array_map(array('Horde_String', 'length'), array_keys($tmp))) + 2;
2125: $text = '';
2126:
2127: foreach ($tmp as $key => $val) {
2128: $text .= Horde_String::pad($key . ': ', $max, ' ', STR_PAD_LEFT) . $val . "\n";
2129: }
2130:
2131: return $text;
2132: }
2133:
2134: 2135: 2136: 2137: 2138: 2139: 2140: 2141: 2142:
2143: public function addUploadAttachment($name)
2144: {
2145: global $conf;
2146:
2147: try {
2148: $GLOBALS['browser']->wasFileUploaded($name, _("attachment"));
2149: } catch (Horde_Browser_Exception $e) {
2150: throw new IMP_Compose_Exception($e);
2151: }
2152:
2153: $filename = Horde_Util::dispelMagicQuotes($_FILES[$name]['name']);
2154: $tempfile = $_FILES[$name]['tmp_name'];
2155:
2156:
2157: if (!empty($conf['compose']['attach_size_limit']) &&
2158: (($conf['compose']['attach_size_limit'] - $this->sizeOfAttachments() - $_FILES[$name]['size']) < 0)) {
2159: throw new IMP_Compose_Exception(sprintf(_("Attached file \"%s\" exceeds the attachment size limits. File NOT attached."), $filename));
2160: }
2161:
2162:
2163: $type = empty($_FILES[$name]['type'])
2164: ? 'application/octet-stream'
2165: : $_FILES[$name]['type'];
2166:
2167:
2168: try {
2169: $type = Horde::callHook('compose_attach', array($filename, $tempfile, $type), 'imp');
2170: } catch (Horde_Exception_HookNotSet $e) {}
2171:
2172: $part = new Horde_Mime_Part();
2173: $part->setType($type);
2174: if ($part->getPrimaryType() == 'text') {
2175: if ($analyzetype = Horde_Mime_Magic::analyzeFile($tempfile, empty($conf['mime']['magic_db']) ? null : $conf['mime']['magic_db'], array('nostrip' => true))) {
2176: $analyzetype = Horde_Mime::decodeParam('Content-Type', $analyzetype, 'UTF-8');
2177: $part->setCharset(isset($analyzetype['params']['charset']) ? $analyzetype['params']['charset'] : 'UTF-8');
2178: } else {
2179: $part->setCharset('UTF-8');
2180: }
2181: } else {
2182: $part->setHeaderCharset('UTF-8');
2183: }
2184: $part->setName($filename);
2185: $part->setBytes($_FILES[$name]['size']);
2186: $part->setDisposition('attachment');
2187:
2188: if ($conf['compose']['use_vfs']) {
2189: $attachment = $tempfile;
2190: } else {
2191: $attachment = Horde::getTempFile('impatt', false);
2192: if (move_uploaded_file($tempfile, $attachment) === false) {
2193: throw new IMP_Compose_Exception(sprintf(_("The file %s could not be attached."), $filename));
2194: }
2195: }
2196:
2197:
2198: $this->_storeAttachment($part, $attachment);
2199:
2200: return $filename;
2201: }
2202:
2203: 2204: 2205: 2206: 2207: 2208: 2209: 2210:
2211: public function addMimePartAttachment($part)
2212: {
2213: global $conf;
2214:
2215: $type = $part->getType();
2216: $vfs = $conf['compose']['use_vfs'];
2217:
2218: 2219:
2220: if ($type == 'application/octet-stream') {
2221: $type = Horde_Mime_Magic::filenameToMIME($part->getName(true), false);
2222: }
2223:
2224: 2225: 2226:
2227: if ($vfs) {
2228: $data = $part->getContents();
2229: if (($type == 'application/octet-stream') &&
2230: ($analyzetype = Horde_Mime_Magic::analyzeData($data, !empty($conf['mime']['magic_db']) ? $conf['mime']['magic_db'] : null))) {
2231: $type = $analyzetype;
2232: }
2233: } else {
2234: $data = Horde::getTempFile('impatt', false);
2235: $res = file_put_contents($data, $part->getContents());
2236: if ($res === false) {
2237: throw new IMP_Compose_Exception(sprintf(_("Could not attach %s to the message."), $part->getName()));
2238: }
2239:
2240: if (($type == 'application/octet-stream') &&
2241: ($analyzetype = Horde_Mime_Magic::analyzeFile($data, !empty($conf['mime']['magic_db']) ? $conf['mime']['magic_db'] : null))) {
2242: $type = $analyzetype;
2243: }
2244: }
2245:
2246: $part->setType($type);
2247:
2248: 2249:
2250: $bytes = $part->getBytes();
2251: $part->setBytes($bytes);
2252:
2253: 2254: 2255:
2256: $part->clearContents();
2257: $part->setTransferEncoding('binary');
2258:
2259:
2260: if (!empty($conf['compose']['attach_size_limit']) &&
2261: (($conf['compose']['attach_size_limit'] - $this->sizeOfAttachments() - $bytes) < 0)) {
2262: throw new IMP_Compose_Exception(sprintf(_("Attached file \"%s\" exceeds the attachment size limits. File NOT attached."), $part->getName()));
2263: }
2264:
2265:
2266: $this->_storeAttachment($part, $data, !$vfs);
2267: }
2268:
2269: 2270: 2271: 2272: 2273: 2274: 2275: 2276: 2277: 2278: 2279:
2280: protected function _storeAttachment($part, $data, $vfs_file = true)
2281: {
2282:
2283: if ($GLOBALS['conf']['compose']['use_vfs']) {
2284: try {
2285: $vfs = $GLOBALS['injector']->getInstance('Horde_Core_Factory_Vfs')->create();
2286: $cacheID = strval(new Horde_Support_Randomid());
2287:
2288: if ($vfs_file) {
2289: $vfs->write(self::VFS_ATTACH_PATH, $cacheID, $data, true);
2290: } else {
2291: $vfs->writeData(self::VFS_ATTACH_PATH, $cacheID, $data, true);
2292: }
2293: } catch (Horde_Vfs_Exception $e) {
2294: throw new IMP_Compose_Exception($e);
2295: }
2296:
2297: $this->_cache[] = array(
2298: 'filename' => $cacheID,
2299: 'filetype' => 'vfs',
2300: 'part' => $part
2301: );
2302: } else {
2303: chmod($data, 0600);
2304: $this->_cache[] = array(
2305: 'filename' => $data,
2306: 'filetype' => 'file',
2307: 'part' => $part
2308: );
2309: }
2310:
2311: $this->changed = 'changed';
2312:
2313:
2314: $this->_size += $part->getBytes();
2315: }
2316:
2317: 2318: 2319:
2320: public function deleteAllAttachments()
2321: {
2322: foreach ($this as $key => $val) {
2323: unset($this[$key]);
2324: }
2325: }
2326:
2327: 2328: 2329: 2330: 2331:
2332: public function sizeOfAttachments()
2333: {
2334: return $this->_size;
2335: }
2336:
2337: 2338: 2339: 2340: 2341: 2342: 2343:
2344: public function buildAttachment($id)
2345: {
2346: $atc = $this[$id];
2347:
2348: switch ($atc['filetype']) {
2349: case 'file':
2350: $fp = fopen($atc['filename'], 'r');
2351: $atc['part']->setContents($fp);
2352: fclose($fp);
2353: break;
2354:
2355: case 'vfs':
2356: try {
2357: $vfs = $GLOBALS['injector']->getInstance('Horde_Core_Factory_Vfs')->create();
2358: $atc['part']->setContents($vfs->read(self::VFS_ATTACH_PATH, $atc['filename']));
2359: } catch (Horde_Vfs_Exception $e) {}
2360: break;
2361: }
2362:
2363: return $atc['part'];
2364: }
2365:
2366: 2367: 2368: 2369: 2370: 2371: 2372: 2373: 2374:
2375: protected function _expandAttribution($line, $from, $h)
2376: {
2377: $addressList = $nameList = '';
2378:
2379: 2380: 2381:
2382: try {
2383: $addr_list = Horde_Mime_Address::parseAddressList($from);
2384: } catch (Horde_Mime_Exception $e) {
2385: $addr_list = array();
2386: }
2387:
2388: foreach ($addr_list as $entry) {
2389: if (isset($entry['mailbox'])) {
2390: if (strlen($addressList) > 0) {
2391: $addressList .= ', ';
2392: }
2393: $addressList .= $entry['mailbox'];
2394: if (isset($entry['host'])) {
2395: $addressList .= '@' . $entry['host'];
2396: }
2397: }
2398:
2399: if (isset($entry['personal'])) {
2400: if (strlen($nameList) > 0) {
2401: $nameList .= ', ';
2402: }
2403: $nameList .= $entry['personal'];
2404: } elseif (isset($entry['mailbox'])) {
2405: if (strlen($nameList) > 0) {
2406: $nameList .= ', ';
2407: }
2408: $nameList .= $entry['mailbox'];
2409: }
2410: }
2411:
2412:
2413: if (is_array($message_id = $h->getValue('message_id'))) {
2414: $message_id = reset($message_id);
2415: }
2416: if (!($subject = $h->getValue('subject'))) {
2417: $subject = _("[No Subject]");
2418: }
2419: $udate = strtotime($h->getValue('date'));
2420:
2421: $match = array(
2422:
2423: '/%n/' => "\n",
2424:
2425:
2426: '/%%/' => '%',
2427:
2428:
2429: '/%f/' => $from,
2430:
2431:
2432: '/%a/' => $addressList,
2433:
2434:
2435: '/%p/' => $nameList,
2436:
2437:
2438: '/%r/' => $h->getValue('date'),
2439:
2440:
2441: '/%d/' => strftime("%a, %d %b %Y", $udate),
2442:
2443:
2444: '/%x/' => strftime("%x", $udate),
2445:
2446:
2447: '/%c/' => strftime("%c", $udate),
2448:
2449:
2450: '/%m/' => $message_id,
2451:
2452:
2453: '/%s/' => $subject
2454: );
2455:
2456: return (preg_replace(array_keys($match), array_values($match), $line));
2457: }
2458:
2459: 2460: 2461: 2462: 2463:
2464: public function getCacheId()
2465: {
2466: return $this->_cacheid;
2467: }
2468:
2469: 2470: 2471: 2472: 2473: 2474: 2475:
2476: public function additionalAttachmentsAllowed()
2477: {
2478: return empty($GLOBALS['conf']['compose']['attach_count_limit']) ||
2479: ($GLOBALS['conf']['compose']['attach_count_limit'] - count($this));
2480: }
2481:
2482: 2483: 2484: 2485: 2486:
2487: public function maxAttachmentSize()
2488: {
2489: $size = $GLOBALS['session']->get('imp', 'file_upload');
2490:
2491: if (!empty($GLOBALS['conf']['compose']['attach_size_limit'])) {
2492: return min($size, max($GLOBALS['conf']['compose']['attach_size_limit'] - $this->sizeOfAttachments(), 0));
2493: }
2494:
2495: return $size;
2496: }
2497:
2498: 2499: 2500: 2501: 2502: 2503: 2504: 2505: 2506:
2507: protected function _convertToMultipartRelated($mime_part)
2508: {
2509: global $conf;
2510:
2511: 2512: 2513:
2514: if (empty($conf['compose']['convert_to_related']) ||
2515: ($mime_part->getType() != 'text/html') ||
2516: !preg_match_all('/<img[^>]+src\s*\=\s*([^\s]+)\s+/iU', $mime_part->getContents(), $results)) {
2517: return $mime_part;
2518: }
2519:
2520: $client = $GLOBALS['injector']
2521: ->getInstance('Horde_Core_Factory_HttpClient')
2522: ->create();
2523: $img_data = $img_parts = array();
2524:
2525: 2526:
2527: foreach ($results[1] as $url) {
2528:
2529: $response = $client->get(str_replace('&', '&', trim($url, '"\'')));
2530: if ($response->code == 200) {
2531: 2532: 2533: 2534:
2535: $part = new Horde_Mime_Part();
2536: $part->setType($response->getHeader('content-type'));
2537: $part->setContents($response->getBody());
2538: $part->setDisposition('attachment');
2539: $img_data[$url] = '"cid:' . $part->setContentID() . '"';
2540: $img_parts[] = $part;
2541: }
2542: }
2543:
2544: 2545:
2546: if (empty($img_data)) {
2547: return $mime_part;
2548: }
2549:
2550:
2551: $mime_part->setContents(str_replace(array_keys($img_data), array_values($img_data), $mime_part->getContents()));
2552:
2553:
2554: $related = new Horde_Mime_Part();
2555: $related->setType('multipart/related');
2556:
2557: 2558: 2559: 2560:
2561: $related->setContentTypeParameter('start', $mime_part->setContentID());
2562:
2563: 2564:
2565: $related->addPart($mime_part);
2566: foreach (array_keys($img_parts) as $val) {
2567: $related->addPart($img_parts[$val]);
2568: }
2569:
2570: return $related;
2571: }
2572:
2573: 2574: 2575: 2576: 2577: 2578: 2579: 2580: 2581: 2582: 2583: 2584:
2585: public function linkAttachments($part)
2586: {
2587: global $conf, $prefs;
2588:
2589: if (!$conf['compose']['link_attachments']) {
2590: throw new IMP_Compose_Exception(_("Linked attachments are forbidden."));
2591: }
2592:
2593: $auth = $GLOBALS['registry']->getAuth();
2594: $baseurl = Horde::url('attachment.php', true)->setRaw(true);
2595:
2596: try {
2597: $vfs = $GLOBALS['injector']->getInstance('Horde_Core_Factory_Vfs')->create();
2598: } catch (Horde_Vfs_Exception $e) {
2599: throw new IMP_Compose_Exception($e);
2600: }
2601:
2602: $ts = time();
2603: $fullpath = sprintf('%s/%s/%d', self::VFS_LINK_ATTACH_PATH, $auth, $ts);
2604: $charset = $part->getCharset();
2605:
2606: $trailer = Horde_String::convertCharset(_("Attachments"), 'UTF-8', $charset);
2607:
2608: if ($damk = $prefs->getValue('delete_attachments_monthly_keep')) {
2609: 2610: 2611:
2612: $del_time = mktime(0, 0, 0, date('n') + $damk + 1, 1, date('Y')) - 1;
2613: $trailer .= Horde_String::convertCharset(' (' . sprintf(_("Links will expire on %s"), strftime('%x', $del_time)) . ')', 'UTF-8', $charset);
2614: }
2615:
2616: foreach ($this as $att) {
2617: $trailer .= "\n" . $baseurl->copy()->add(array(
2618: 'f' => $att['part']->getName(),
2619: 't' => $ts,
2620: 'u' => $auth
2621: ));
2622:
2623: try {
2624: if ($att['filetype'] == 'vfs') {
2625: $vfs->rename(self::VFS_ATTACH_PATH, $att['filename'], $fullpath, escapeshellcmd($att['part']->getName()));
2626: } else {
2627: $data = file_get_contents($att['filename']);
2628: $vfs->writeData($fullpath, escapeshellcmd($att['part']->getName()), $data, true);
2629: }
2630: } catch (Horde_Vfs_Exception $e) {
2631: Horde::logMessage($e, 'ERR');
2632: return IMP_Compose_Exception($e);
2633: }
2634: }
2635:
2636: $this->deleteAllAttachments();
2637:
2638: if ($part->getPrimaryType() == 'multipart') {
2639: $mixed_part = new Horde_Mime_Part();
2640: $mixed_part->setType('multipart/mixed');
2641: $mixed_part->addPart($part);
2642:
2643: $link_part = new Horde_Mime_Part();
2644: $link_part->setType('text/plain');
2645: $link_part->setCharset($charset);
2646: $link_part->setLanguage($GLOBALS['language']);
2647: $link_part->setDisposition('inline');
2648: $link_part->setContents($trailer);
2649: $link_part->setDescription(_("Attachment Information"));
2650:
2651: $mixed_part->addPart($link_part);
2652: return $mixed_part;
2653: }
2654:
2655: $part->appendContents("\n-----\n" . $trailer);
2656:
2657: return $part;
2658: }
2659:
2660: 2661: 2662: 2663: 2664: 2665: 2666: 2667: 2668: 2669: 2670: 2671: 2672: 2673: 2674: 2675: 2676: 2677: 2678: 2679: 2680: 2681: 2682: 2683: 2684: 2685: 2686: 2687:
2688: protected function _getMessageText($contents, array $options = array())
2689: {
2690: $body_id = null;
2691: $mode = 'text';
2692: $options = array_merge(array(
2693: 'imp_msg' => self::COMPOSE
2694: ), $options);
2695:
2696: if (!empty($options['html']) &&
2697: $GLOBALS['session']->get('imp', 'rteavail') &&
2698: (($body_id = $contents->findBody('html')) !== null)) {
2699: $mime_message = $contents->getMIMEMessage();
2700:
2701: switch ($mime_message->getPrimaryType()) {
2702: case 'multipart':
2703: if (($mime_message->getSubType() == 'mixed') &&
2704: !Horde_Mime::isChild('1', $body_id)) {
2705: $body_id = null;
2706: } else {
2707: $mode = 'html';
2708: }
2709: break;
2710:
2711: default:
2712: if (strval($body_id) != '1') {
2713: $body_id = null;
2714: } else {
2715: $mode = 'html';
2716: }
2717: break;
2718: }
2719: }
2720:
2721: if (is_null($body_id)) {
2722: $body_id = $contents->findBody();
2723: if (is_null($body_id)) {
2724: return null;
2725: }
2726: }
2727:
2728: $part = $contents->getMIMEPart($body_id);
2729: $type = $part->getType();
2730: $part_charset = $part->getCharset();
2731:
2732: $msg = Horde_String::convertCharset($part->getContents(), $part_charset, 'UTF-8');
2733:
2734:
2735: if (!empty($options['replylimit']) &&
2736: !empty($GLOBALS['conf']['compose']['reply_limit'])) {
2737: $limit = $GLOBALS['conf']['compose']['reply_limit'];
2738: if (Horde_String::length($msg) > $limit) {
2739: $msg = Horde_String::substr($msg, 0, $limit) . "\n" . _("[Truncated Text]");
2740: }
2741: }
2742:
2743: if ($mode == 'html') {
2744: $msg = $GLOBALS['injector']->getInstance('Horde_Core_Factory_TextFilter')->filter($msg, array('Cleanhtml', 'Xss'), array(array('body_only' => true), array('strip_styles' => true, 'strip_style_attributes' => false)));
2745: } elseif ($type == 'text/html') {
2746: $msg = $GLOBALS['injector']->getInstance('Horde_Core_Factory_TextFilter')->filter($msg, 'Html2text');
2747: $type = 'text/plain';
2748: }
2749:
2750: 2751: 2752:
2753: $msg = trim($msg);
2754:
2755: if ($type == 'text/plain') {
2756: if ($part->getContentTypeParameter('format') == 'flowed') {
2757: $flowed = new Horde_Text_Flowed($msg, 'UTF-8');
2758: if (Horde_String::lower($part->getContentTypeParameter('delsp')) == 'yes') {
2759: $flowed->setDelSp(true);
2760: }
2761: $flowed->setMaxLength(0);
2762: $msg = $flowed->toFixed(false);
2763: } else {
2764: 2765:
2766: $msg = preg_replace("/\s*\n/U", "\n", $msg);
2767: }
2768:
2769: if (isset($options['toflowed'])) {
2770: $flowed = new Horde_Text_Flowed($msg, 'UTF-8');
2771: $msg = $options['toflowed']
2772: ? $flowed->toFlowed(true)
2773: : $flowed->toFlowed(false, array('nowrap' => true));
2774: }
2775: }
2776:
2777: if (strcasecmp($part->getCharset(), 'windows-1252') === 0) {
2778: $part_charset = 'ISO-8859-1';
2779: }
2780:
2781: return array(
2782: 'charset' => $part_charset,
2783: 'flowed' => isset($flowed) ? $flowed : null,
2784: 'id' => $body_id,
2785: 'mode' => $mode,
2786: 'text' => $msg
2787: );
2788: }
2789:
2790: 2791: 2792: 2793: 2794: 2795:
2796: public function pgpAttachPubkey($attach)
2797: {
2798: $this->_pgpAttachPubkey = (bool)$attach;
2799: }
2800:
2801: 2802: 2803: 2804: 2805: 2806: 2807: 2808:
2809: public function attachVCard($name)
2810: {
2811: $this->_attachVCard = ($name === false)
2812: ? false
2813: : ((strlen($name) ? $name : 'vcard') . '.vcf');
2814: }
2815:
2816: 2817: 2818: 2819: 2820: 2821:
2822: public function userLinkAttachments($attach)
2823: {
2824: $this->_linkAttach = (bool)$attach;
2825: }
2826:
2827: 2828: 2829: 2830: 2831: 2832: 2833: 2834: 2835:
2836: public function addFilesFromUpload($field, $notify = false)
2837: {
2838: $success = true;
2839:
2840:
2841: for ($i = 1, $fcount = count($_FILES); $i <= $fcount; ++$i) {
2842: $key = $field . $i;
2843: if (isset($_FILES[$key]) && ($_FILES[$key]['error'] != 4)) {
2844: $filename = Horde_Util::dispelMagicQuotes($_FILES[$key]['name']);
2845: if (!empty($_FILES[$key]['error'])) {
2846: switch ($_FILES[$key]['error']) {
2847: case UPLOAD_ERR_INI_SIZE:
2848: case UPLOAD_ERR_FORM_SIZE:
2849: $GLOBALS['notification']->push(sprintf(_("Did not attach \"%s\" as the maximum allowed upload size has been exceeded."), $filename), 'horde.warning');
2850: break;
2851:
2852: case UPLOAD_ERR_PARTIAL:
2853: $GLOBALS['notification']->push(sprintf(_("Did not attach \"%s\" as it was only partially uploaded."), $filename), 'horde.warning');
2854: break;
2855:
2856: default:
2857: $GLOBALS['notification']->push(sprintf(_("Did not attach \"%s\" as the server configuration did not allow the file to be uploaded."), $filename), 'horde.warning');
2858: break;
2859: }
2860: $success = false;
2861: } elseif ($_FILES[$key]['size'] == 0) {
2862: $GLOBALS['notification']->push(sprintf(_("Did not attach \"%s\" as the file was empty."), $filename), 'horde.warning');
2863: $success = false;
2864: } else {
2865: try {
2866: $result = $this->addUploadAttachment($key);
2867: if ($notify) {
2868: $GLOBALS['notification']->push(sprintf(_("Added \"%s\" as an attachment."), $result), 'horde.success');
2869: }
2870: } catch (IMP_Compose_Exception $e) {
2871: $GLOBALS['notification']->push($e, 'horde.error');
2872: $success = false;
2873: }
2874: }
2875: }
2876: }
2877:
2878: return $success;
2879: }
2880:
2881: 2882: 2883: 2884: 2885: 2886: 2887:
2888: static public function text2html($msg)
2889: {
2890: return $GLOBALS['injector']->getInstance('Horde_Core_Factory_TextFilter')->filter($msg, 'Text2html', array(
2891: 'always_mailto' => true,
2892: 'flowed' => self::HTML_BLOCKQUOTE,
2893: 'parselevel' => Horde_Text_Filter_Text2html::MICRO
2894: ));
2895: }
2896:
2897: 2898: 2899: 2900: 2901:
2902: public function sessionExpireDraft($vars)
2903: {
2904: if (empty($GLOBALS['conf']['compose']['use_vfs'])) {
2905: return;
2906: }
2907:
2908: $imp_ui = new IMP_Ui_Compose();
2909:
2910: $headers = array();
2911: foreach (array('to', 'cc', 'bcc', 'subject') as $val) {
2912: $headers[$val] = $imp_ui->getAddressList($vars->$val);
2913: }
2914:
2915: if ($vars->charset) {
2916: $this->charset = $vars->charset;
2917: }
2918:
2919: try {
2920: $body = $this->_saveDraftMsg($headers, $vars->message, array(
2921: 'html' => $vars->rtemode,
2922: 'priority' => $vars->priority,
2923: 'readreceipt' => $vars->request_read_receipt
2924: ));
2925: } catch (IMP_Compose_Exception $e) {
2926: return;
2927: }
2928:
2929: try {
2930: $vfs = $GLOBALS['injector']->getInstance('Horde_Core_Factory_Vfs')->create();
2931: $vfs->writeData(self::VFS_DRAFTS_PATH, hash('md5', $vars->user), $body, true);
2932:
2933: $GLOBALS['notification']->push(_("The message you were composing has been saved as a draft. The next time you login, you may resume composing your message."));
2934: } catch (Horde_Vfs_Exception $e) {}
2935: }
2936:
2937: 2938: 2939:
2940: public function recoverSessionExpireDraft()
2941: {
2942: if (empty($GLOBALS['conf']['compose']['use_vfs'])) {
2943: return;
2944: }
2945:
2946: $filename = hash('md5', $GLOBALS['registry']->getAuth());
2947:
2948: try {
2949: $vfs = $GLOBALS['injector']->getInstance('Horde_Core_Factory_Vfs')->create();
2950: } catch (Horde_Vfs_Exception $e) {
2951: return;
2952: }
2953:
2954: if ($vfs->exists(self::VFS_DRAFTS_PATH, $filename)) {
2955: try {
2956: $data = $vfs->read(self::VFS_DRAFTS_PATH, $filename);
2957: $vfs->deleteFile(self::VFS_DRAFTS_PATH, $filename);
2958: } catch (Horde_Vfs_Exception $e) {
2959: return;
2960: }
2961:
2962: try {
2963: $this->_saveDraftServer($data);
2964: $GLOBALS['notification']->push(_("A message you were composing when your session expired has been recovered. You may resume composing your message by going to your Drafts folder."));
2965: } catch (IMP_Compose_Exception $e) {}
2966: }
2967: }
2968:
2969: 2970: 2971: 2972: 2973: 2974:
2975: public function getContentsOb()
2976: {
2977: return ($this->_replytype && $this->getMetadata('mailbox'))
2978: ? $GLOBALS['injector']->getInstance('IMP_Factory_Contents')->create(new IMP_Indices($this->getMetadata('mailbox'), $this->getMetadata('uid')))
2979: : null;
2980: }
2981:
2982: 2983: 2984: 2985: 2986: 2987: 2988:
2989: public function replyType($base = false)
2990: {
2991: switch ($this->_replytype) {
2992: case self::FORWARD:
2993: case self::FORWARD_ATTACH:
2994: case self::FORWARD_BODY:
2995: case self::FORWARD_BOTH:
2996: return $base
2997: ? self::FORWARD
2998: : $this->_replytype;
2999:
3000: case self::REPLY:
3001: case self::REPLY_ALL:
3002: case self::REPLY_LIST:
3003: case self::REPLY_SENDER:
3004: return $base
3005: ? self::REPLY
3006: : $this->_replytype;
3007:
3008: case self::REDIRECT:
3009: return $this->_replytype;
3010:
3011: default:
3012: return null;
3013: }
3014: }
3015:
3016:
3017:
3018: 3019: 3020: 3021: 3022: 3023: 3024:
3025: static public function formatAddr($addr)
3026: {
3027: 3028:
3029: return (!empty($addr) &&
3030: (strpos($addr, '>') === false) &&
3031: (strpos($addr, ':') === false))
3032: ? preg_replace('|\s+|', ', ', trim(strtr($addr, ';,', ' ')))
3033: : $addr;
3034: }
3035:
3036: 3037: 3038: 3039: 3040: 3041: 3042: 3043: 3044: 3045: 3046: 3047:
3048: static public function expandAddresses($addrString, $options = array())
3049: {
3050: if (!preg_match('|[^\s]|', $addrString)) {
3051: return array();
3052: }
3053:
3054: $addrString = reset(array_filter(array_map('trim', Horde_Mime_Address::explode($addrString, ',;'))));
3055: $addr_list = self::getAddressList($addrString);
3056:
3057: if (empty($options['levenshtein'])) {
3058: return $addr_list;
3059: }
3060:
3061: $sort_list = array();
3062: foreach ($addr_list as $val) {
3063:
3064: $sort_list[$val] = @levenshtein($addrString, $val);
3065: }
3066: asort($sort_list, SORT_NUMERIC);
3067:
3068: return array_keys($sort_list);
3069: }
3070:
3071: 3072: 3073: 3074: 3075: 3076: 3077: 3078: 3079: 3080: 3081: 3082:
3083: static public function getAddressList($search = '', $email = false, $count = false)
3084: {
3085: $sparams = IMP::getAddressbookSearchParams();
3086: try {
3087: $res = $GLOBALS['registry']->call(
3088: 'contacts/search', array($search, $sparams['sources'], $sparams['fields'], false, false, array('name', 'email'), $count));
3089: } catch (Horde_Exception $e) {
3090: Horde::logMessage($e, 'ERR');
3091: return array();
3092: }
3093:
3094: if ($count && is_array($res)) {
3095: return count($res);
3096: } elseif ($count) {
3097: return $res;
3098: }
3099:
3100: if (!count($res)) {
3101: return array();
3102: }
3103:
3104: 3105:
3106: $search = array();
3107: foreach (reset($res) as $val) {
3108: if (!empty($val['email'])) {
3109: if (!$email && (strpos($val['email'], ',') !== false)) {
3110: $search[] = Horde_Mime_Address::encode($val['name'], 'personal') . ': ' . $val['email'] . ';';
3111: } else {
3112: $mbox_host = explode('@', $val['email']);
3113: if (isset($mbox_host[1])) {
3114: $search[] = Horde_Mime_Address::writeAddress($mbox_host[0], $mbox_host[1], $email ? '' : $val['name']);
3115: }
3116: }
3117: }
3118: }
3119:
3120: return $search;
3121: }
3122:
3123:
3124:
3125: public function offsetExists($offset)
3126: {
3127: return isset($this->_cache[$offset]);
3128: }
3129:
3130: public function offsetGet($offset)
3131: {
3132: return isset($this->_cache[$offset])
3133: ? $this->_cache[$offset]
3134: : null;
3135: }
3136:
3137: public function offsetSet($offset, $value)
3138: {
3139: $this->_cache[$offset] = $value;
3140: $this->changed = 'changed';
3141: }
3142:
3143: public function offsetUnset($offset)
3144: {
3145: if (!isset($this->_cache[$offset])) {
3146: return;
3147: }
3148:
3149: $atc = &$this->_cache[$offset];
3150:
3151: switch ($atc['filetype']) {
3152: case 'file':
3153:
3154: @unlink($atc['filename']);
3155: break;
3156:
3157: case 'vfs':
3158:
3159: try {
3160: $vfs = $GLOBALS['injector']->getInstance('Horde_Core_Factory_Vfs')->create();
3161: $vfs->deleteFile(self::VFS_ATTACH_PATH, $atc['filename']);
3162: } catch (Horde_Vfs_Exception $e) {}
3163: break;
3164: }
3165:
3166:
3167: $this->_size -= $atc['part']->getBytes();
3168:
3169: unset($this->_cache[$offset]);
3170:
3171: $this->changed = 'changed';
3172: }
3173:
3174:
3175:
3176: 3177: 3178:
3179: public function __toString()
3180: {
3181: return $this->getCacheId();
3182: }
3183:
3184:
3185:
3186: 3187: 3188: 3189: 3190:
3191: public function count()
3192: {
3193: return count($this->_cache);
3194: }
3195:
3196:
3197:
3198: public function current()
3199: {
3200: return current($this->_cache);
3201: }
3202:
3203: public function key()
3204: {
3205: return key($this->_cache);
3206: }
3207:
3208: public function next()
3209: {
3210: next($this->_cache);
3211: }
3212:
3213: public function rewind()
3214: {
3215: reset($this->_cache);
3216: }
3217:
3218: public function valid()
3219: {
3220: return (key($this->_cache) !== null);
3221: }
3222:
3223:
3224:
3225: 3226:
3227: public function serialize()
3228: {
3229:
3230: $atc = array();
3231: foreach ($this->_cache as $key => $val) {
3232: $val['part'] = clone($val['part']);
3233: $val['part']->clearContents();
3234: $atc[$key] = $val;
3235: }
3236:
3237: return serialize(array(
3238: $this->charset,
3239: $this->_attachVCard,
3240: $atc,
3241: $this->_cacheid,
3242: $this->_linkAttach,
3243: $this->_metadata,
3244: $this->_pgpAttachPubkey,
3245: $this->_replytype,
3246: $this->_size
3247: ));
3248: }
3249:
3250: 3251:
3252: public function unserialize($data)
3253: {
3254: list(
3255: $this->charset,
3256: $this->_attachVCard,
3257: $this->_cache,
3258: $this->_cacheid,
3259: $this->_linkAttach,
3260: $this->_metadata,
3261: $this->_pgpAttachPubkey,
3262: $this->_replytype,
3263: $this->_size
3264: ) = unserialize($data);
3265: }
3266:
3267: }
3268: