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