f6c7a3047ece10e533b20a0eb3ccc8d87c69d94e
[squirrelmail.git] / src / compose.php
1 <?php
2 /**
3 * compose.php
4 *
5 * This code sends a mail.
6 *
7 * There are 4 modes of operation:
8 * - Start new mail
9 * - Add an attachment
10 * - Send mail
11 * - Save As Draft
12 *
13 * @copyright &copy; 1999-2007 The SquirrelMail Project Team
14 * @license http://opensource.org/licenses/gpl-license.php GNU Public License
15 * @version $Id$
16 * @package squirrelmail
17 */
18
19 /** This is the compose page */
20 define('PAGE_NAME', 'compose');
21
22 /**
23 * Include the SquirrelMail initialization file.
24 */
25 require('../include/init.php');
26
27 /* If email_address not set and admin wants us to ask user for it,
28 * redirect to options page. */
29 if ( $ask_user_info && getPref($data_dir, $username,'email_address') == "" ) {
30 header("Location: " . get_location() . "/options.php?optpage=personal");
31 exit;
32 }
33
34 /* SquirrelMail required files. */
35 require_once(SM_PATH . 'functions/imap_general.php');
36 require_once(SM_PATH . 'functions/imap_messages.php');
37 require_once(SM_PATH . 'functions/date.php');
38 require_once(SM_PATH . 'functions/mime.php');
39 require_once(SM_PATH . 'functions/compose.php');
40 require_once(SM_PATH . 'class/deliver/Deliver.class.php');
41 require_once(SM_PATH . 'functions/addressbook.php');
42 require_once(SM_PATH . 'functions/forms.php');
43 require_once(SM_PATH . 'functions/identity.php');
44
45 /* --------------------- Get globals ------------------------------------- */
46
47 /** SESSION VARS */
48 sqgetGlobalVar('delimiter', $delimiter, SQ_SESSION);
49
50 sqgetGlobalVar('delayed_errors', $delayed_errors, SQ_SESSION);
51 sqgetGlobalVar('composesession', $composesession, SQ_SESSION);
52 sqgetGlobalVar('compose_messages', $compose_messages, SQ_SESSION);
53
54 // compose_messages only useful in SESSION when a forward-as-attachment
55 // has been preconstructed for us and passed in via that mechanism; once
56 // we have it, we can clear it from the SESSION
57 sqsession_unregister('compose_messages');
58
59 // Turn on delayed error handling in case we wind up redirecting below
60 $oErrorHandler->setDelayedErrors(true);
61
62 /** SESSION/POST/GET VARS */
63 sqgetGlobalVar('send', $send, SQ_POST);
64 // Send can only be achieved by setting $_POST var. If Send = true then
65 // retrieve other form fields from $_POST
66 if (isset($send) && $send) {
67 $SQ_GLOBAL = SQ_POST;
68 } else {
69 $SQ_GLOBAL = SQ_FORM;
70 }
71 sqgetGlobalVar('session',$session, $SQ_GLOBAL);
72 sqgetGlobalVar('mailbox',$mailbox, $SQ_GLOBAL);
73 if(!sqgetGlobalVar('identity',$identity, $SQ_GLOBAL)) {
74 $identity=0;
75 }
76 sqgetGlobalVar('send_to',$send_to, $SQ_GLOBAL);
77 sqgetGlobalVar('send_to_cc',$send_to_cc, $SQ_GLOBAL);
78 sqgetGlobalVar('send_to_bcc',$send_to_bcc, $SQ_GLOBAL);
79 sqgetGlobalVar('subject',$subject, $SQ_GLOBAL);
80 sqgetGlobalVar('body',$body, $SQ_GLOBAL);
81 sqgetGlobalVar('mailprio',$mailprio, $SQ_GLOBAL);
82 sqgetGlobalVar('request_mdn',$request_mdn, $SQ_GLOBAL);
83 sqgetGlobalVar('request_dr',$request_dr, $SQ_GLOBAL);
84 sqgetGlobalVar('html_addr_search',$html_addr_search, $SQ_GLOBAL);
85 sqgetGlobalVar('mail_sent',$mail_sent, $SQ_GLOBAL);
86 sqgetGlobalVar('passed_id',$passed_id, $SQ_GLOBAL);
87 sqgetGlobalVar('passed_ent_id',$passed_ent_id, $SQ_GLOBAL);
88
89 sqgetGlobalVar('attach',$attach, SQ_POST);
90 sqgetGlobalVar('draft',$draft, SQ_POST);
91 sqgetGlobalVar('draft_id',$draft_id, $SQ_GLOBAL);
92 sqgetGlobalVar('ent_num',$ent_num, $SQ_GLOBAL);
93 sqgetGlobalVar('saved_draft',$saved_draft, SQ_FORM);
94
95 if ( sqgetGlobalVar('delete_draft',$delete_draft) ) {
96 $delete_draft = (int)$delete_draft;
97 }
98
99 if ( sqgetGlobalVar('startMessage',$startMessage) ) {
100 $startMessage = (int)$startMessage;
101 } else {
102 $startMessage = 1;
103 }
104
105
106 /** POST VARS */
107 sqgetGlobalVar('sigappend', $sigappend, SQ_POST);
108 sqgetGlobalVar('from_htmladdr_search', $from_htmladdr_search, SQ_POST);
109 sqgetGlobalVar('addr_search_done', $html_addr_search_done, SQ_POST);
110 sqgetGlobalVar('addr_search_cancel', $html_addr_search_cancel, SQ_POST);
111 sqgetGlobalVar('send_to_search', $send_to_search, SQ_POST);
112 sqgetGlobalVar('do_delete', $do_delete, SQ_POST);
113 sqgetGlobalVar('delete', $delete, SQ_POST);
114 sqgetGlobalVar('attachments', $attachments, SQ_POST);
115 if ( sqgetGlobalVar('return', $temp, SQ_POST) ) {
116 $html_addr_search_done = 'Use Addresses';
117 }
118
119 /** GET VARS */
120 if ( sqgetGlobalVar('account', $temp, SQ_GET) ) {
121 $iAccount = (int) $temp;
122 } else {
123 $iAccount = 0;
124 }
125
126
127 /** get smaction */
128 if ( !sqgetGlobalVar('smaction',$action) )
129 {
130 if ( sqgetGlobalVar('smaction_reply',$tmp) ) $action = 'reply';
131 if ( sqgetGlobalVar('smaction_reply_all',$tmp) ) $action = 'reply_all';
132 if ( sqgetGlobalVar('smaction_forward',$tmp) ) $action = 'forward';
133 if ( sqgetGlobalVar('smaction_attache',$tmp) ) $action = 'forward_as_attachment';
134 if ( sqgetGlobalVar('smaction_draft',$tmp) ) $action = 'draft';
135 if ( sqgetGlobalVar('smaction_edit_new',$tmp) ) $action = 'edit_as_new';
136 }
137
138 /**
139 * Here we decode the data passed in from mailto.php.
140 */
141 if ( sqgetGlobalVar('mailtodata', $mailtodata, SQ_GET) ) {
142 $trtable = array('to' => 'send_to',
143 'cc' => 'send_to_cc',
144 'bcc' => 'send_to_bcc',
145 'body' => 'body',
146 'subject' => 'subject');
147 $mtdata = unserialize($mailtodata);
148
149 foreach ($trtable as $f => $t) {
150 if ( !empty($mtdata[$f]) ) {
151 $$t = $mtdata[$f];
152 }
153 }
154 unset($mailtodata,$mtdata, $trtable);
155 }
156
157 /* Location (For HTTP 1.1 header("Location: ...") redirects) */
158 $location = get_location();
159 /* Identities (fetch only once) */
160 $idents = get_identities();
161
162 /* --------------------- Specific Functions ------------------------------ */
163
164 function replyAllString($header) {
165 global $include_self_reply_all, $idents;
166 $excl_ar = array();
167 /**
168 * 1) Remove the addresses we'll be sending the message 'to'
169 */
170 if (isset($header->reply_to)) {
171 $excl_ar = $header->getAddr_a('reply_to');
172 }
173 /**
174 * 2) Remove our identities from the CC list (they still can be in the
175 * TO list) only if $include_self_reply_all is turned off
176 */
177 if (!$include_self_reply_all) {
178 foreach($idents as $id) {
179 $excl_ar[strtolower(trim($id['email_address']))] = '';
180 }
181 }
182
183 /**
184 * 3) get the addresses.
185 */
186 $url_replytoall_ar = $header->getAddr_a(array('to','cc'), $excl_ar);
187
188 /**
189 * 4) generate the string.
190 */
191 $url_replytoallcc = '';
192 foreach( $url_replytoall_ar as $email => $personal) {
193 if ($personal) {
194 // if personal name contains address separator then surround
195 // the personal name with double quotes.
196 if (strpos($personal,',') !== false) {
197 $personal = '"'.$personal.'"';
198 }
199 $url_replytoallcc .= ", $personal <$email>";
200 } else {
201 $url_replytoallcc .= ', '. $email;
202 }
203 }
204 $url_replytoallcc = substr($url_replytoallcc,2);
205
206 return $url_replytoallcc;
207 }
208
209 /**
210 * creates top line in reply citations
211 *
212 * Line style depends on user preferences.
213 * $orig_date argument is available only from 1.4.3 and 1.5.1 version.
214 * @param object $orig_from From: header object.
215 * @param integer $orig_date email's timestamp
216 * @return string reply citation
217 */
218 function getReplyCitation($orig_from, $orig_date) {
219 global $reply_citation_style, $reply_citation_start, $reply_citation_end;
220
221 if (!is_object($orig_from)) {
222 $sOrig_from = '';
223 } else {
224 $sOrig_from = decodeHeader($orig_from->getAddress(false),false,false,true);
225 }
226
227 /* First, return an empty string when no citation style selected. */
228 if (($reply_citation_style == '') || ($reply_citation_style == 'none')) {
229 return '';
230 }
231
232 /* Make sure our final value isn't an empty string. */
233 if ($sOrig_from == '') {
234 return '';
235 }
236
237 /* Otherwise, try to select the desired citation style. */
238 switch ($reply_citation_style) {
239 case 'author_said':
240 // i18n: %s is for author's name
241 $full_reply_citation = sprintf(_("%s wrote:"),$sOrig_from);
242 break;
243 case 'quote_who':
244 $start = '<quote who="';
245 $end = '">';
246 $full_reply_citation = $start . $sOrig_from . $end;
247 break;
248 case 'date_time_author':
249 // i18n:
250 // The first %s is for date string, the second %s is for author's name.
251 // The date uses formating from "D, F j, Y g:i a" and "D, F j, Y H:i"
252 // translations.
253 // Example string:
254 // "On Sat, December 24, 2004 23:59, Santa wrote:"
255 // If you have to put author's name in front of date string, check comments about
256 // argument swapping at http://php.net/sprintf
257 $full_reply_citation = sprintf(_("On %s, %s wrote:"), getLongDateString($orig_date), $sOrig_from);
258 break;
259 case 'user-defined':
260 $start = $reply_citation_start .
261 ($reply_citation_start == '' ? '' : ' ');
262 $end = $reply_citation_end;
263 $full_reply_citation = $start . $sOrig_from . $end;
264 break;
265 default:
266 return '';
267 }
268
269 /* Add line feed and return the citation string. */
270 return ($full_reply_citation . "\n");
271 }
272
273 /**
274 * Creates header fields in forwarded email body
275 *
276 * $default_charset global must be set correctly before you call this function.
277 * @param object $orig_header
278 * @return $string
279 */
280 function getforwardHeader($orig_header) {
281 global $editor_size, $default_charset;
282
283 // using own strlen function in order to detect correct string length
284 $display = array( _("Subject") => sq_strlen(_("Subject"),$default_charset),
285 _("From") => sq_strlen(_("From"),$default_charset),
286 _("Date") => sq_strlen(_("Date"),$default_charset),
287 _("To") => sq_strlen(_("To"),$default_charset),
288 _("Cc") => sq_strlen(_("Cc"),$default_charset) );
289 $maxsize = max($display);
290 $indent = str_pad('',$maxsize+2);
291 foreach($display as $key => $val) {
292 $display[$key] = $key .': '. str_pad('', $maxsize - $val);
293 }
294 $from = decodeHeader($orig_header->getAddr_s('from',"\n$indent"),false,false,true);
295 $from = str_replace('&nbsp;',' ',$from);
296 $to = decodeHeader($orig_header->getAddr_s('to',"\n$indent"),false,false,true);
297 $to = str_replace('&nbsp;',' ',$to);
298 $subject = decodeHeader($orig_header->subject,false,false,true);
299 $subject = str_replace('&nbsp;',' ',$subject);
300
301 // using own str_pad function in order to create correct string pad
302 $bodyTop = sq_str_pad(' '._("Original Message").' ',$editor_size -2,'-',STR_PAD_BOTH,$default_charset) .
303 "\n". $display[_("Subject")] . $subject . "\n" .
304 $display[_("From")] . $from . "\n" .
305 $display[_("Date")] . getLongDateString( $orig_header->date, $orig_header->date_unparsed ). "\n" .
306 $display[_("To")] . $to . "\n";
307 if ($orig_header->cc != array() && $orig_header->cc !='') {
308 $cc = decodeHeader($orig_header->getAddr_s('cc',"\n$indent"),false,false,true);
309 $cc = str_replace('&nbsp;',' ',$cc);
310 $bodyTop .= $display[_("Cc")] .$cc . "\n";
311 }
312 $bodyTop .= str_pad('', $editor_size -2 , '-') .
313 "\n\n";
314 return $bodyTop;
315 }
316 /* ----------------------------------------------------------------------- */
317
318 /*
319 * If the session is expired during a post this restores the compose session
320 * vars.
321 */
322 $session_expired = false;
323 if (sqsession_is_registered('session_expired_post')) {
324 sqgetGlobalVar('session_expired_post', $session_expired_post, SQ_SESSION);
325 /*
326 * extra check for username so we don't display previous post data from
327 * another user during this session.
328 */
329 if (!empty($session_expired_post['username'])
330 && $session_expired_post['username'] == $username) {
331 // these are the vars that we can set from the expired composed session
332 $compo_var_list = array ('send_to', 'send_to_cc', 'body',
333 'startMessage', 'passed_body', 'use_signature', 'signature',
334 'subject', 'newmail', 'send_to_bcc', 'passed_id', 'mailbox',
335 'from_htmladdr_search', 'identity', 'draft_id', 'delete_draft',
336 'mailprio', 'edit_as_new', 'attachments', 'composesession',
337 'request_mdn', 'request_dr');
338
339 foreach ($compo_var_list as $var) {
340 if ( isset($session_expired_post[$var]) && !isset($$var) ) {
341 $$var = $session_expired_post[$var];
342 }
343 }
344
345 if (!empty($attachments))
346 $attachments = unserialize(urldecode($attachments));
347
348 sqsession_register($composesession,'composesession');
349
350 if (isset($send)) {
351 unset($send);
352 }
353 $session_expired = true;
354 }
355 unset($session_expired_post);
356 sqsession_unregister('session_expired_post');
357 session_write_close();
358 if (!isset($mailbox)) {
359 $mailbox = '';
360 }
361 if ($compose_new_win == '1') {
362 compose_Header($color, $mailbox);
363 } else {
364 $sHeaderJs = (isset($sHeaderJs)) ? $sHeaderJs : '';
365 if (strpos($action, 'reply') !== false && $reply_focus) {
366 $sBodyTagJs = 'onload="checkForm(\''.$replyfocus.'\');"';
367 } else {
368 $sBodyTagJs = 'onload="checkForm();"';
369 }
370 displayPageHeader($color, $mailbox,$sHeaderJs,$sBodyTagJs);
371 }
372 showInputForm($session, false);
373 exit();
374 }
375
376 if (!isset($composesession)) {
377 $composesession = 0;
378 sqsession_register(0,'composesession');
379 } else {
380 $composesession = (int)$composesession;
381 }
382
383 if (!isset($session) || (isset($newmessage) && $newmessage)) {
384 sqsession_unregister('composesession');
385 $session = "$composesession" +1;
386 $composesession = $session;
387 sqsession_register($composesession,'composesession');
388 }
389 if (!empty($compose_messages[$session])) {
390 $composeMessage = $compose_messages[$session];
391 } else {
392 $composeMessage = new Message();
393 $rfc822_header = new Rfc822Header();
394 $composeMessage->rfc822_header = $rfc822_header;
395 $composeMessage->reply_rfc822_header = '';
396 }
397
398 // re-add attachments that were already in this message
399 // FIXME: note that technically this is very bad form -
400 // should never directly manipulate an object like this
401 if (!empty($attachments)) {
402 $attachments = unserialize(urldecode($attachments));
403 if (!empty($attachments) && is_array($attachments))
404 $composeMessage->entities = $attachments;
405 }
406
407 if (empty($mailbox)) {
408 $mailbox = 'INBOX';
409 }
410
411 if ($draft) {
412 /*
413 * Set $default_charset to correspond with the user's selection
414 * of language interface.
415 */
416 set_my_charset();
417 if (! deliverMessage($composeMessage, true)) {
418 showInputForm($session);
419 exit();
420 } else {
421 $draft_message = _("Draft Email Saved");
422 /* If this is a resumed draft, then delete the original */
423 if(isset($delete_draft)) {
424 $imap_stream = sqimap_login($username, false, $imapServerAddress, $imapPort, false);
425 sqimap_mailbox_select($imap_stream, $draft_folder);
426 // force bypass_trash=true because message should be saved when deliverMessage() returns true.
427 // in current implementation of sqimap_msgs_list_flag() single message id can
428 // be submitted as string. docs state that it should be array.
429 sqimap_msgs_list_delete($imap_stream, $draft_folder, $delete_draft, true);
430 if ($auto_expunge) {
431 sqimap_mailbox_expunge($imap_stream, $draft_folder, true);
432 }
433 sqimap_logout($imap_stream);
434 }
435
436 $oErrorHandler->saveDelayedErrors();
437 session_write_close();
438
439 if ($compose_new_win == '1') {
440 if ( !isset($pageheader_sent) || !$pageheader_sent ) {
441 header("Location: $location/compose.php?saved_draft=yes&session=$composesession");
442 } else {
443 //FIXME: DON'T ECHO HTML FROM CORE!
444 echo ' <br><br><div style="text-align: center;"><a href="' . $location
445 . '/compose.php?saved_sent=yes&amp;session=' . $composesession . '">'
446 . _("Return") . '</a></div>';
447 }
448 exit();
449 } else {
450 if ( !isset($pageheader_sent) || !$pageheader_sent ) {
451 header("Location: $location/right_main.php?mailbox=" . urlencode($draft_folder) .
452 "&startMessage=1&note=".urlencode($draft_message));
453 } else {
454 //FIXME: DON'T ECHO HTML FROM CORE!
455 echo ' <br><br><div style="text-align: center;"><a href="' . $location
456 . '/right_main.php?mailbox=' . urlencode($draft_folder)
457 . '&amp;startMessage=1&amp;note=' . urlencode($draft_message) .'">'
458 . _("Return") . '</a></div>';
459 }
460 exit();
461 }
462 }
463 }
464
465 if ($send) {
466 if (isset($_FILES['attachfile']) &&
467 $_FILES['attachfile']['tmp_name'] &&
468 $_FILES['attachfile']['tmp_name'] != 'none') {
469 $AttachFailure = saveAttachedFiles($session);
470 }
471 if (checkInput(false) && !isset($AttachFailure)) {
472 if ($mailbox == "All Folders") {
473 /* We entered compose via the search results page */
474 $mailbox = 'INBOX'; /* Send 'em to INBOX, that's safe enough */
475 }
476 $urlMailbox = urlencode($mailbox);
477 if (! isset($passed_id)) {
478 $passed_id = 0;
479 }
480 /**
481 * Set $default_charset to correspond with the user's selection
482 * of language interface.
483 */
484 set_my_charset();
485 /**
486 * This is to change all newlines to \n
487 * We'll change them to \r\n later (in the sendMessage function)
488 */
489 $body = str_replace("\r\n", "\n", $body);
490 $body = str_replace("\r", "\n", $body);
491
492 /**
493 * Rewrap $body so that no line is bigger than $editor_size
494 */
495 $body = explode("\n", $body);
496 $newBody = '';
497 foreach ($body as $line) {
498 if( $line <> '-- ' ) {
499 $line = rtrim($line);
500 }
501 if (sq_strlen($line, $default_charset) <= $editor_size + 1) {
502 $newBody .= $line . "\n";
503 } else {
504 sqWordWrap($line, $editor_size, $default_charset);
505 $newBody .= $line . "\n";
506
507 }
508
509 }
510 $body = $newBody;
511
512 $Result = deliverMessage($composeMessage);
513
514 if ($Result)
515 $mail_sent = 'yes';
516 else
517 $mail_sent = 'no';
518
519 // NOTE: this hook changed in 1.5.2 from sending $Result and
520 // $composeMessage as args #2 and #3 to being in an array
521 // under arg #2
522 $temp = array(&$Result, &$composeMessage, &$mail_sent);
523 do_hook('compose_send_after', $temp);
524 if (! $Result) {
525 showInputForm($session);
526 exit();
527 }
528
529 /* if it is resumed draft, delete draft message */
530 if ( isset($delete_draft)) {
531 $imap_stream = sqimap_login($username, false, $imapServerAddress, $imapPort, false);
532 sqimap_mailbox_select($imap_stream, $draft_folder);
533 // bypass_trash=true because message should be saved when deliverMessage() returns true.
534 // in current implementation of sqimap_msgs_list_flag() single message id can
535 // be submitted as string. docs state that it should be array.
536 sqimap_msgs_list_delete($imap_stream, $draft_folder, $delete_draft, true);
537 if ($auto_expunge) {
538 sqimap_mailbox_expunge($imap_stream, $draft_folder, true);
539 }
540 sqimap_logout($imap_stream);
541 }
542 /*
543 * Store the error array in the session because they will be lost on a redirect
544 */
545 $oErrorHandler->saveDelayedErrors();
546 session_write_close();
547
548 if ($compose_new_win == '1') {
549 if ( !isset($pageheader_sent) || !$pageheader_sent ) {
550 header("Location: $location/compose.php?mail_sent=$mail_sent");
551 } else {
552 //FIXME: DON'T ECHO HTML FROM CORE!
553 echo ' <br><br><div style="text-align: center;"><a href="' . $location
554 . '/compose.php?mail_sent=$mail_sent">'
555 . _("Return") . '</a></div>';
556 }
557 exit();
558 } else {
559 if ( !isset($pageheader_sent) || !$pageheader_sent ) {
560 header("Location: $location/right_main.php?mailbox=$urlMailbox".
561 "&startMessage=$startMessage&mail_sent=$mail_sent");
562 } else {
563 //FIXME: DON'T ECHO HTML FROM CORE!
564 echo ' <br><br><div style="text-align: center;"><a href="' . $location
565 . "/right_main.php?mailbox=$urlMailbox"
566 . "&amp;startMessage=$startMessage&amp;mail_sent=$mail_sent\">"
567 . _("Return") . '</a></div>';
568 }
569 exit();
570 }
571 } else {
572 if ($compose_new_win == '1') {
573 compose_Header($color, $mailbox);
574 }
575 else {
576 displayPageHeader($color, $mailbox);
577 }
578 if (isset($AttachFailure)) {
579 plain_error_message(_("Could not move/copy file. File not attached"),
580 $color);
581 }
582 checkInput(true);
583 showInputForm($session);
584 /* sqimap_logout($imapConnection); */
585 }
586 } elseif (isset($html_addr_search_done)) {
587 if ($compose_new_win == '1') {
588 compose_Header($color, $mailbox);
589 }
590 else {
591 displayPageHeader($color, $mailbox);
592 }
593
594 if (isset($send_to_search) && is_array($send_to_search)) {
595 foreach ($send_to_search as $k => $v) {
596 if (substr($k, 0, 1) == 'T') {
597 if ($send_to) {
598 $send_to .= ', ';
599 }
600 $send_to .= $v;
601 }
602 elseif (substr($k, 0, 1) == 'C') {
603 if ($send_to_cc) {
604 $send_to_cc .= ', ';
605 }
606 $send_to_cc .= $v;
607 }
608 elseif (substr($k, 0, 1) == 'B') {
609 if ($send_to_bcc) {
610 $send_to_bcc .= ', ';
611 }
612 $send_to_bcc .= $v;
613 }
614 }
615 }
616 showInputForm($session);
617 } elseif (isset($html_addr_search) && !isset($html_addr_search_cancel)) {
618 if (isset($_FILES['attachfile']) &&
619 $_FILES['attachfile']['tmp_name'] &&
620 $_FILES['attachfile']['tmp_name'] != 'none') {
621 if(saveAttachedFiles($session)) {
622 plain_error_message(_("Could not move/copy file. File not attached"));
623 }
624 }
625 /*
626 * I am using an include so as to elminiate an extra unnecessary
627 * click. If you can think of a better way, please implement it.
628 */
629 include_once('./addrbook_search_html.php');
630 } elseif (isset($attach)) {
631 if ($compose_new_win == '1') {
632 compose_Header($color, $mailbox);
633 } else {
634 displayPageHeader($color, $mailbox);
635 }
636 if (saveAttachedFiles($session)) {
637 plain_error_message(_("Could not move/copy file. File not attached"));
638 }
639 showInputForm($session);
640 }
641 elseif (isset($sigappend)) {
642 $signature = $idents[$identity]['signature'];
643
644 $body .= "\n\n".($prefix_sig==true? "-- \n":'').$signature;
645 if ($compose_new_win == '1') {
646 compose_Header($color, $mailbox);
647 } else {
648 displayPageHeader($color, $mailbox);
649 }
650 showInputForm($session);
651 } elseif (isset($do_delete)) {
652 if ($compose_new_win == '1') {
653 compose_Header($color, $mailbox);
654 } else {
655 displayPageHeader($color, $mailbox);
656 }
657
658 if (isset($delete) && is_array($delete)) {
659 foreach($delete as $index) {
660 if (!empty($composeMessage->entities) && isset($composeMessage->entities[$index])) {
661 $composeMessage->entities[$index]->purgeAttachments();
662 // FIXME: one person reported that unset() didn't do anything at all here, so this is a work-around... but it triggers PHP notices if the unset() doesn't work, which should be fixed... but bigger question is if unset() doesn't work here, what about everywhere else? Anyway, uncomment this if you think you need it
663 //$composeMessage->entities[$index] = NULL;
664 unset ($composeMessage->entities[$index]);
665 }
666 }
667 $new_entities = array();
668 foreach ($composeMessage->entities as $entity) {
669 $new_entities[] = $entity;
670 }
671 $composeMessage->entities = $new_entities;
672 }
673 showInputForm($session);
674 } else {
675 /*
676 * This handles the default case as well as the error case
677 * (they had the same code) --> if (isset($smtpErrors))
678 */
679
680 if ($compose_new_win == '1') {
681 compose_Header($color, $mailbox);
682 } else {
683 displayPageHeader($color, $mailbox);
684 }
685
686 $newmail = true;
687
688 if (!isset($passed_ent_id)) {
689 $passed_ent_id = '';
690 }
691 if (!isset($passed_id)) {
692 $passed_id = '';
693 }
694 if (!isset($mailbox)) {
695 $mailbox = '';
696 }
697 if (!isset($action)) {
698 $action = '';
699 }
700
701 $values = newMail($mailbox,$passed_id,$passed_ent_id, $action, $session);
702
703 /* in case the origin is not read_body.php */
704 if (isset($send_to)) {
705 $values['send_to'] = $send_to;
706 }
707 if (isset($send_to_cc)) {
708 $values['send_to_cc'] = $send_to_cc;
709 }
710 if (isset($send_to_bcc)) {
711 $values['send_to_bcc'] = $send_to_bcc;
712 }
713 if (isset($subject)) {
714 $values['subject'] = $subject;
715 }
716 showInputForm($session, $values);
717 }
718
719 exit();
720
721 /**************** Only function definitions go below *************/
722
723 function getforwardSubject($subject)
724 {
725 if ((substr(strtolower($subject), 0, 4) != 'fwd:') &&
726 (substr(strtolower($subject), 0, 5) != '[fwd:') &&
727 (substr(strtolower($subject), 0, 6) != '[ fwd:')) {
728 $subject = '[Fwd: ' . $subject . ']';
729 }
730 return $subject;
731 }
732
733 /* This function is used when not sending or adding attachments */
734 function newMail ($mailbox='', $passed_id='', $passed_ent_id='', $action='', $session='') {
735 global $editor_size, $default_use_priority, $body, $idents,
736 $use_signature, $data_dir, $username,
737 $key, $imapServerAddress, $imapPort,
738 $composeMessage, $body_quote, $request_mdn, $request_dr,
739 $mdn_user_support, $languages, $squirrelmail_language,
740 $default_charset;
741
742 /*
743 * Set $default_charset to correspond with the user's selection
744 * of language interface. $default_charset global is not correct,
745 * if message is composed in new window.
746 */
747 set_my_charset();
748
749 $send_to = $send_to_cc = $send_to_bcc = $subject = $identity = '';
750 $mailprio = 3;
751
752 if ($passed_id) {
753 $imapConnection = sqimap_login($username, false, $imapServerAddress,
754 $imapPort, 0);
755
756 sqimap_mailbox_select($imapConnection, $mailbox);
757 $message = sqimap_get_message($imapConnection, $passed_id, $mailbox);
758
759 $body = '';
760 if ($passed_ent_id) {
761 /* redefine the messsage in case of message/rfc822 */
762 $message = $message->getEntity($passed_ent_id);
763 /* message is an entity which contains the envelope and type0=message
764 * and type1=rfc822. The actual entities are childs from
765 * $message->entities[0]. That's where the encoding and is located
766 */
767
768 $entities = $message->entities[0]->findDisplayEntity
769 (array(), $alt_order = array('text/plain'));
770 if (!count($entities)) {
771 $entities = $message->entities[0]->findDisplayEntity
772 (array(), $alt_order = array('text/plain','text/html'));
773 }
774 $orig_header = $message->rfc822_header; /* here is the envelope located */
775 /* redefine the message for picking up the attachments */
776 $message = $message->entities[0];
777
778 } else {
779 $entities = $message->findDisplayEntity (array(), $alt_order = array('text/plain'));
780 if (!count($entities)) {
781 $entities = $message->findDisplayEntity (array(), $alt_order = array('text/plain','text/html'));
782 }
783 $orig_header = $message->rfc822_header;
784 }
785
786 $type0 = $message->type0;
787 $type1 = $message->type1;
788 foreach ($entities as $ent) {
789 $msg = $message->getEntity($ent);
790 $type0 = $msg->type0;
791 $type1 = $msg->type1;
792 $unencoded_bodypart = mime_fetch_body($imapConnection, $passed_id, $ent);
793 $body_part_entity = $message->getEntity($ent);
794 $bodypart = decodeBody($unencoded_bodypart,
795 $body_part_entity->header->encoding);
796 if ($type1 == 'html') {
797 $bodypart = str_replace("\n", ' ', $bodypart);
798 $bodypart = preg_replace(array('/<\/?p>/i','/<div><\/div>/i','/<br\s*(\/)*>/i','/<\/?div>/i'), "\n", $bodypart);
799 $bodypart = str_replace(array('&nbsp;','&gt;','&lt;'),array(' ','>','<'),$bodypart);
800 $bodypart = strip_tags($bodypart);
801 }
802 if (isset($languages[$squirrelmail_language]['XTRA_CODE']) &&
803 function_exists($languages[$squirrelmail_language]['XTRA_CODE'] . '_decode')) {
804 if (mb_detect_encoding($bodypart) != 'ASCII') {
805 $bodypart = call_user_func($languages[$squirrelmail_language]['XTRA_CODE'] . '_decode', $bodypart);
806 }
807 }
808
809 // charset encoding in compose form stuff
810 if (isset($body_part_entity->header->parameters['charset'])) {
811 $actual = $body_part_entity->header->parameters['charset'];
812 } else {
813 $actual = 'us-ascii';
814 }
815
816 if ( $actual && is_conversion_safe($actual) && $actual != $default_charset){
817 $bodypart = charset_convert($actual,$bodypart,$default_charset,false);
818 }
819 // end of charset encoding in compose
820
821 $body .= $bodypart;
822 }
823 if ($default_use_priority) {
824 $mailprio = substr($orig_header->priority,0,1);
825 if (!$mailprio) {
826 $mailprio = 3;
827 }
828 } else {
829 $mailprio = '';
830 }
831
832 $from_o = $orig_header->from;
833 if (is_array($from_o)) {
834 if (isset($from_o[0])) {
835 $from_o = $from_o[0];
836 }
837 }
838 if (is_object($from_o)) {
839 $orig_from = $from_o->getAddress();
840 } else {
841 $orig_from = '';
842 }
843
844 $identities = array();
845 if (count($idents) > 1) {
846 foreach($idents as $nr=>$data) {
847 $enc_from_name = '"'.$data['full_name'].'" <'. $data['email_address'].'>';
848 if(strtolower($enc_from_name) == strtolower($orig_from)) {
849 $identity = $nr;
850 // don't stop! need to build $identities array for idents match below
851 //break;
852 }
853 $identities[] = $enc_from_name;
854 }
855
856 $identity_match = $orig_header->findAddress($identities);
857 if ($identity_match) {
858 $identity = $identity_match;
859 }
860 }
861
862 switch ($action) {
863 case ('draft'):
864 $use_signature = FALSE;
865 $composeMessage->rfc822_header = $orig_header;
866 $send_to = decodeHeader($orig_header->getAddr_s('to'),false,false,true);
867 $send_to_cc = decodeHeader($orig_header->getAddr_s('cc'),false,false,true);
868 $send_to_bcc = decodeHeader($orig_header->getAddr_s('bcc'),false,false,true);
869 $send_from = $orig_header->getAddr_s('from');
870 $send_from_parts = new AddressStructure();
871 $send_from_parts = $orig_header->parseAddress($send_from);
872 $send_from_add = $send_from_parts->mailbox . '@' . $send_from_parts->host;
873 $identity = find_identity(array($send_from_add));
874 $subject = decodeHeader($orig_header->subject,false,false,true);
875
876 // Remember the receipt settings
877 $request_mdn = $mdn_user_support && !empty($orig_header->dnt) ? '1' : '0';
878 $request_dr = $mdn_user_support && !empty($orig_header->drnt) ? '1' : '0';
879
880 /* remember the references and in-reply-to headers in case of an reply */
881 //FIXME: it would be better to fiddle with headers inside of the message object or possibly when delivering the message to its destination (drafts folder?); is this possible?
882 $composeMessage->rfc822_header->more_headers['References'] = $orig_header->references;
883 $composeMessage->rfc822_header->more_headers['In-Reply-To'] = $orig_header->in_reply_to;
884 // rewrap the body to clean up quotations and line lengths
885 sqBodyWrap($body, $editor_size);
886 $composeMessage = getAttachments($message, $composeMessage, $passed_id, $entities, $imapConnection);
887 break;
888 case ('edit_as_new'):
889 $send_to = decodeHeader($orig_header->getAddr_s('to'),false,false,true);
890 $send_to_cc = decodeHeader($orig_header->getAddr_s('cc'),false,false,true);
891 $send_to_bcc = decodeHeader($orig_header->getAddr_s('bcc'),false,false,true);
892 $subject = decodeHeader($orig_header->subject,false,false,true);
893 $mailprio = $orig_header->priority;
894 $orig_from = '';
895 $composeMessage = getAttachments($message, $composeMessage, $passed_id, $entities, $imapConnection);
896 // rewrap the body to clean up quotations and line lengths
897 sqBodyWrap($body, $editor_size);
898 break;
899 case ('forward'):
900 $send_to = '';
901 $subject = getforwardSubject(decodeHeader($orig_header->subject,false,false,true));
902 $body = getforwardHeader($orig_header) . $body;
903 // the logic for calling sqUnWordWrap here would be to allow the browser to wrap the lines
904 // forwarded message text should be as undisturbed as possible, so commenting out this call
905 // sqUnWordWrap($body);
906 $composeMessage = getAttachments($message, $composeMessage, $passed_id, $entities, $imapConnection);
907
908 //add a blank line after the forward headers
909 $body = "\n" . $body;
910 break;
911 case ('forward_as_attachment'):
912 $subject = getforwardSubject(decodeHeader($orig_header->subject,false,false,true));
913 $composeMessage = getMessage_RFC822_Attachment($message, $composeMessage, $passed_id, $passed_ent_id, $imapConnection);
914 $body = '';
915 break;
916 case ('reply_all'):
917 if(isset($orig_header->mail_followup_to) && $orig_header->mail_followup_to) {
918 $send_to = $orig_header->getAddr_s('mail_followup_to');
919 } else {
920 $send_to_cc = replyAllString($orig_header);
921 $send_to_cc = decodeHeader($send_to_cc,false,false,true);
922 }
923 case ('reply'):
924 // skip this if send_to was already set right above here
925 if(!$send_to) {
926 $send_to = $orig_header->reply_to;
927 if (is_array($send_to) && count($send_to)) {
928 $send_to = $orig_header->getAddr_s('reply_to');
929 } else if (is_object($send_to)) { /* unneccesarry, just for failsafe purpose */
930 $send_to = $orig_header->getAddr_s('reply_to');
931 } else {
932 $send_to = $orig_header->getAddr_s('from');
933 }
934 }
935 $send_to = decodeHeader($send_to,false,false,true);
936 $subject = decodeHeader($orig_header->subject,false,false,true);
937 $subject = str_replace('"', "'", $subject);
938 $subject = trim($subject);
939 if (substr(strtolower($subject), 0, 3) != 're:') {
940 $subject = 'Re: ' . $subject;
941 }
942 /* this corrects some wrapping/quoting problems on replies */
943 $rewrap_body = explode("\n", $body);
944 $from = (is_array($orig_header->from) && !empty($orig_header->from)) ? $orig_header->from[0] : $orig_header->from;
945 $body = '';
946 $strip_sigs = getPref($data_dir, $username, 'strip_sigs');
947 foreach ($rewrap_body as $line) {
948 if ($strip_sigs && substr($line,0,3) == '-- ') {
949 break;
950 }
951 if (preg_match("/^(>+)/", $line, $matches)) {
952 $gt = $matches[1];
953 $body .= $body_quote . str_replace("\n", "\n$body_quote$gt ", rtrim($line)) ."\n";
954 } else {
955 $body .= $body_quote . (!empty($body_quote) ? ' ' : '') . str_replace("\n", "\n$body_quote" . (!empty($body_quote) ? ' ' : ''), rtrim($line)) . "\n";
956 }
957 }
958
959 //rewrap the body to clean up quotations and line lengths
960 $body = sqBodyWrap ($body, $editor_size);
961
962 $body = getReplyCitation($from , $orig_header->date) . $body;
963 $composeMessage->reply_rfc822_header = $orig_header;
964
965 break;
966 default:
967 break;
968 }
969 //FIXME: we used to register $compose_messages in the session here, but not any more - so do we still need the session_write_close() and sqimap_logout() here? We probably need the IMAP logout, but what about the session closure?
970 session_write_close();
971 sqimap_logout($imapConnection);
972 }
973 $ret = array( 'send_to' => $send_to,
974 'send_to_cc' => $send_to_cc,
975 'send_to_bcc' => $send_to_bcc,
976 'subject' => $subject,
977 'mailprio' => $mailprio,
978 'body' => $body,
979 'identity' => $identity );
980
981 return ($ret);
982 } /* function newMail() */
983
984 /**
985 * downloads attachments from original message, stores them in attachment directory and adds
986 * them to composed message.
987 * @param object $message
988 * @param object $composeMessage
989 * @param integer $passed_id
990 * @param mixed $entities
991 * @param mixed $imapConnection
992 * @return object
993 */
994 function getAttachments($message, &$composeMessage, $passed_id, $entities, $imapConnection) {
995 global $squirrelmail_language, $languages, $username, $attachment_dir;
996
997 if (!count($message->entities) ||
998 ($message->type0 == 'message' && $message->type1 == 'rfc822')) {
999 if ( !in_array($message->entity_id, $entities) && $message->entity_id) {
1000 switch ($message->type0) {
1001 case 'message':
1002 if ($message->type1 == 'rfc822') {
1003 $filename = $message->rfc822_header->subject;
1004 if ($filename == "") {
1005 $filename = "untitled-".$message->entity_id;
1006 }
1007 $filename .= '.eml';
1008 } else {
1009 $filename = $message->getFilename();
1010 }
1011 break;
1012 default:
1013 if (!$message->mime_header) { /* temporary hack */
1014 $message->mime_header = $message->header;
1015 }
1016 $filename = $message->getFilename();
1017 break;
1018 }
1019 $filename = str_replace('&#32;', ' ', decodeHeader($filename));
1020 if (isset($languages[$squirrelmail_language]['XTRA_CODE']) &&
1021 function_exists($languages[$squirrelmail_language]['XTRA_CODE'] . '_encode')) {
1022 $filename = call_user_func($languages[$squirrelmail_language]['XTRA_CODE'] . '_encode', $filename);
1023 }
1024
1025 $hashed_attachment_dir = getHashedDir($username, $attachment_dir);
1026 $localfilename = sq_get_attach_tempfile();
1027 $message->att_local_name = $localfilename;
1028
1029 $composeMessage->initAttachment($message->type0.'/'.$message->type1,$filename,
1030 $localfilename);
1031
1032 /* Write Attachment to file */
1033 $fp = fopen ($hashed_attachment_dir . '/' . $localfilename, 'wb');
1034 mime_print_body_lines ($imapConnection, $passed_id, $message->entity_id, $message->header->encoding, $fp);
1035 fclose ($fp);
1036 }
1037 } else {
1038 for ($i=0, $entCount=count($message->entities); $i<$entCount;$i++) {
1039 $composeMessage=getAttachments($message->entities[$i], $composeMessage, $passed_id, $entities, $imapConnection);
1040 }
1041 }
1042 return $composeMessage;
1043 }
1044
1045 function getMessage_RFC822_Attachment($message, $composeMessage, $passed_id,
1046 $passed_ent_id='', $imapConnection) {
1047 if (!$passed_ent_id) {
1048 $body_a = sqimap_run_command($imapConnection,
1049 'FETCH '.$passed_id.' RFC822',
1050 TRUE, $response, $readmessage,
1051 TRUE);
1052 } else {
1053 $body_a = sqimap_run_command($imapConnection,
1054 'FETCH '.$passed_id.' BODY['.$passed_ent_id.']',
1055 TRUE, $response, $readmessage, TRUE);
1056 $message = $message->parent;
1057 }
1058 if ($response == 'OK') {
1059 $subject = encodeHeader($message->rfc822_header->subject);
1060 array_shift($body_a);
1061 array_pop($body_a);
1062 $body = implode('', $body_a) . "\r\n";
1063
1064 global $username, $attachment_dir;
1065 $hashed_attachment_dir = getHashedDir($username, $attachment_dir);
1066 $localfilename = sq_get_attach_tempfile();
1067 $fp = fopen($hashed_attachment_dir . '/' . $localfilename, 'wb');
1068 fwrite ($fp, $body);
1069 fclose($fp);
1070 $composeMessage->initAttachment('message/rfc822',$subject.'.eml',
1071 $localfilename);
1072 }
1073 return $composeMessage;
1074 }
1075
1076 function showInputForm ($session, $values=false) {
1077 global $send_to, $send_to_cc, $send_to_bcc,
1078 $body, $startMessage, $action, $attachments,
1079 $use_signature, $signature, $prefix_sig, $session_expired,
1080 $editor_size, $editor_height, $subject, $newmail,
1081 $use_javascript_addr_book, $passed_id, $mailbox,
1082 $from_htmladdr_search, $location_of_buttons, $attachment_dir,
1083 $username, $data_dir, $identity, $idents, $delete_draft,
1084 $mailprio, $compose_new_win, $saved_draft, $mail_sent, $sig_first,
1085 $composeMessage, $composesession, $default_charset,
1086 $compose_onsubmit, $oTemplate, $oErrorHandler;
1087
1088 if (checkForJavascript()) {
1089 $onfocus = ' onfocus="alreadyFocused=true;"';
1090 $onfocus_array = array('onfocus' => 'alreadyFocused=true;');
1091 }
1092 else {
1093 $onfocus = '';
1094 $onfocus_array = array();
1095 }
1096
1097 if ($values) {
1098 $send_to = $values['send_to'];
1099 $send_to_cc = $values['send_to_cc'];
1100 $send_to_bcc = $values['send_to_bcc'];
1101 $subject = $values['subject'];
1102 $mailprio = $values['mailprio'];
1103 $body = $values['body'];
1104 $identity = (int) $values['identity'];
1105 } else {
1106 $send_to = decodeHeader($send_to, true, false);
1107 $send_to_cc = decodeHeader($send_to_cc, true, false);
1108 $send_to_bcc = decodeHeader($send_to_bcc, true, false);
1109 }
1110
1111 if ($use_javascript_addr_book) {
1112 //FIXME: NO HTML IN CORE!
1113 echo "\n". '<script type="text/javascript">'."\n<!--\n" .
1114 'function open_abook() { ' . "\n" .
1115 ' var nwin = window.open("addrbook_popup.php","abookpopup",' .
1116 '"width=670,height=300,resizable=yes,scrollbars=yes");' . "\n" .
1117 ' if((!nwin.opener) && (document.windows != null))' . "\n" .
1118 ' nwin.opener = document.windows;' . "\n" .
1119 "}\n" .
1120 "// -->\n</script>\n\n";
1121 }
1122
1123 //FIXME: NO HTML IN CORE!
1124 echo "\n" . '<form name="compose" action="compose.php" method="post" ' .
1125 'enctype="multipart/form-data"';
1126
1127 $compose_onsubmit = array();
1128 global $null;
1129 do_hook('compose_form', $null);
1130
1131 // Plugins that use compose_form hook can add an array entry
1132 // to the globally scoped $compose_onsubmit; we add them up
1133 // here and format the form tag's full onsubmit handler.
1134 // Each plugin should use "return false" if they need to
1135 // stop form submission but otherwise should NOT use "return
1136 // true" to give other plugins the chance to do what they need
1137 // to do; SquirrelMail itself will add the final "return true".
1138 // Onsubmit text is enclosed inside of double quotes, so plugins
1139 // need to quote accordingly.
1140 if (checkForJavascript()) {
1141 $onsubmit_text = ' onsubmit="';
1142 if (empty($compose_onsubmit))
1143 $compose_onsubmit = array();
1144 else if (!is_array($compose_onsubmit))
1145 $compose_onsubmit = array($compose_onsubmit);
1146
1147 foreach ($compose_onsubmit as $text) {
1148 $text = trim($text);
1149 if (substr($text, -1) != ';' && substr($text, -1) != '}')
1150 $text .= '; ';
1151 $onsubmit_text .= $text;
1152 }
1153
1154 //FIXME: DON'T ECHO HTML FROM CORE!
1155 echo $onsubmit_text . ' return true;"';
1156 }
1157
1158
1159 //FIXME: NO HTML IN CORE!
1160 echo ">\n";
1161
1162 //FIXME: DON'T ECHO HTML FROM CORE!
1163 echo addHidden('startMessage', $startMessage);
1164
1165 if ($action == 'draft') {
1166 //FIXME: DON'T ECHO HTML FROM CORE!
1167 echo addHidden('delete_draft', $passed_id);
1168 }
1169 if (isset($delete_draft)) {
1170 //FIXME: DON'T ECHO HTML FROM CORE!
1171 echo addHidden('delete_draft', $delete_draft);
1172 }
1173 if (isset($session)) {
1174 //FIXME: DON'T ECHO HTML FROM CORE!
1175 echo addHidden('session', $session);
1176 }
1177
1178 if (isset($passed_id)) {
1179 //FIXME: DON'T ECHO HTML FROM CORE!
1180 echo addHidden('passed_id', $passed_id);
1181 }
1182
1183 if ($saved_draft == 'yes') {
1184 $oTemplate->assign('note', _("Your draft has been saved."));
1185 $oTemplate->display('note.tpl');
1186 }
1187 if ($mail_sent == 'yes') {
1188 $oTemplate->assign('note', _("Your mail has been sent."));
1189 $oTemplate->display('note.tpl');
1190 }
1191 if ($compose_new_win == '1') {
1192 $oTemplate->display('compose_newwin_close.tpl');
1193 }
1194
1195 if ($location_of_buttons == 'top') {
1196 //FIXME: DON'T ECHO HTML FROM CORE!
1197 showComposeButtonRow();
1198 }
1199
1200 $identities = array();
1201 if (count($idents) > 1) {
1202 reset($idents);
1203 foreach($idents as $id => $data) {
1204 $identities[$id] = $data['full_name'].' &lt;'.$data['email_address'].'&gt;';
1205 }
1206 }
1207
1208 $oTemplate->assign('identities', $identities);
1209 $oTemplate->assign('identity_def', $identity);
1210 $oTemplate->assign('input_onfocus', 'onfocus="'.join(' ', $onfocus_array).'"');
1211
1212 $oTemplate->assign('to', htmlspecialchars($send_to));
1213 $oTemplate->assign('cc', htmlspecialchars($send_to_cc));
1214 $oTemplate->assign('bcc', htmlspecialchars($send_to_bcc));
1215 $oTemplate->assign('subject', htmlspecialchars($subject));
1216
1217 $oTemplate->display('compose_header.tpl');
1218
1219 if ($location_of_buttons == 'between') {
1220 //FIXME: DON'T ECHO HTML FROM CORE!
1221 showComposeButtonRow();
1222 }
1223
1224 $body_str = '';
1225 if ($use_signature == true && $newmail == true && !isset($from_htmladdr_search)) {
1226 $signature = $idents[$identity]['signature'];
1227
1228 if ($sig_first == '1') {
1229 /*
1230 * FIXME: test is specific to ja_JP translation implementation.
1231 * This test might apply incorrect conversion to other translations, but
1232 * use of 7bit iso-2022-jp charset in other translations might have other
1233 * issues too.
1234 */
1235 if ($default_charset == 'iso-2022-jp') {
1236 $body_str = "\n\n".($prefix_sig==true? "-- \n":'').mb_convert_encoding($signature, 'EUC-JP');
1237 } else {
1238 $body_str = "\n\n".($prefix_sig==true? "-- \n":'').decodeHeader($signature,false,false);
1239 }
1240 $body_str .= "\n\n".htmlspecialchars(decodeHeader($body,false,false));
1241 } else {
1242 $body_str = "\n\n".htmlspecialchars(decodeHeader($body,false,false));
1243 // FIXME: test is specific to ja_JP translation implementation. See above comments.
1244 if ($default_charset == 'iso-2022-jp') {
1245 $body_str .= "\n\n".($prefix_sig==true? "-- \n":'').mb_convert_encoding($signature, 'EUC-JP');
1246 } else {
1247 $body_str .= "\n\n".($prefix_sig==true? "-- \n":'').decodeHeader($signature,false,false);
1248 }
1249 }
1250 } else {
1251 $body_str = htmlspecialchars(decodeHeader($body,false,false));
1252 }
1253
1254 $oTemplate->assign('editor_width', (int)$editor_size);
1255 $oTemplate->assign('editor_height', (int)$editor_height);
1256 $oTemplate->assign('input_onfocus', 'onfocus="'.join(' ', $onfocus_array).'"');
1257 $oTemplate->assign('body', $body_str);
1258 $oTemplate->assign('show_bottom_send', $location_of_buttons!='bottom');
1259
1260 $oTemplate->display ('compose_body.tpl');
1261
1262 if ($location_of_buttons == 'bottom') {
1263 //FIXME: DON'T ECHO HTML FROM CORE!
1264 showComposeButtonRow();
1265 }
1266
1267 // composeMessage can be empty when coming from a restored session
1268 if (is_object($composeMessage) && $composeMessage->entities)
1269 $attach_array = $composeMessage->entities;
1270 if ($session_expired && !empty($attachments) && is_array($attachments))
1271 $attach_array = $attachments;
1272
1273 /* This code is for attachments */
1274 if ((bool) ini_get('file_uploads')) {
1275
1276 /* Calculate the max size for an uploaded file.
1277 * This is advisory for the user because we can't actually prevent
1278 * people to upload too large files. */
1279 $sizes = array();
1280 /* php.ini vars which influence the max for uploads */
1281 $configvars = array('post_max_size', 'memory_limit', 'upload_max_filesize');
1282 foreach($configvars as $var) {
1283 /* skip 0 or empty values, and -1 which means 'unlimited' */
1284 if( $size = getByteSize(ini_get($var)) ) {
1285 if ( $size != '-1' ) {
1286 $sizes[] = $size;
1287 }
1288 }
1289 }
1290
1291 $attach = array();
1292 global $username, $attachment_dir;
1293 $hashed_attachment_dir = getHashedDir($username, $attachment_dir);
1294 if (!empty($attach_array)) {
1295 foreach ($attach_array as $key => $attachment) {
1296 $attached_file = $attachment->att_local_name;
1297 if ($attachment->att_local_name || $attachment->body_part) {
1298 $attached_filename = decodeHeader($attachment->mime_header->getParameter('name'));
1299 $type = $attachment->mime_header->type0.'/'.
1300 $attachment->mime_header->type1;
1301
1302 $a = array();
1303 $a['Key'] = $key;
1304 $a['FileName'] = $attached_filename;
1305 $a['ContentType'] = $type;
1306 $a['Size'] = filesize($hashed_attachment_dir . '/' . $attached_file);
1307 $attach[$key] = $a;
1308 }
1309 }
1310 }
1311
1312 $max = min($sizes);
1313 $oTemplate->assign('max_file_size', empty($max) ? -1 : $max);
1314 $oTemplate->assign('attachments', $attach);
1315
1316 $oTemplate->display('compose_attachments.tpl');
1317 } // End of file_uploads if-block
1318 /* End of attachment code */
1319
1320 //FIXME: no direct echoing to browser, no HTML output in core!
1321 echo addHidden('username', $username).
1322 addHidden('smaction', $action).
1323 addHidden('mailbox', $mailbox);
1324 sqgetGlobalVar('QUERY_STRING', $queryString, SQ_SERVER);
1325 //FIXME: no direct echoing to browser, no HTML output in core!
1326 echo addHidden('composesession', $composesession).
1327 addHidden('querystring', $queryString).
1328 (!empty($attach_array) ?
1329 addHidden('attachments', urlencode(serialize($attach_array))) : '').
1330 "</form>\n";
1331 if (!(bool) ini_get('file_uploads')) {
1332 /* File uploads are off, so we didn't show that part of the form.
1333 To avoid bogus bug reports, tell the user why. */
1334 //FIXME: no direct echoing to browser, no HTML output in core!
1335 echo '<p style="text-align:center">'
1336 . _("Because PHP file uploads are turned off, you can not attach files to this message. Please see your system administrator for details.")
1337 . "</p>\r\n";
1338 }
1339
1340 if ($compose_new_win=='1') {
1341 $oTemplate->display('compose_newwin_close.tpl');
1342 }
1343
1344 do_hook('compose_bottom', $null);
1345
1346 $oErrorHandler->setDelayedErrors(false);
1347 $oTemplate->display('footer.tpl');
1348 }
1349
1350
1351 function showComposeButtonRow() {
1352 global $use_javascript_addr_book, $save_as_draft,
1353 $default_use_priority, $mailprio, $default_use_mdn,
1354 $request_mdn, $request_dr,
1355 $data_dir, $username;
1356
1357 global $oTemplate, $buffer_hook;
1358
1359 if ($default_use_priority) {
1360 $priorities = array('1'=>_("High"), '3'=>_("Normal"), '5'=>_("Low"));
1361 $priority = isset($mailprio) ? $mailprio : 3;
1362 } else {
1363 $priorities = array();
1364 $priority = NULL;
1365 }
1366
1367 $mdn_user_support=getPref($data_dir, $username, 'mdn_user_support',$default_use_mdn);
1368
1369 if ($use_javascript_addr_book && checkForJavascript()) {
1370 $addr_book = addButton(_("Addresses"), null, array('onclick' => 'javascript:open_abook();'));
1371 } else {
1372 $addr_book = addSubmit(_("Addresses"), 'html_addr_search');
1373 }
1374
1375 $oTemplate->assign('allow_priority', $default_use_priority==1);
1376 $oTemplate->assign('priority_list', $priorities);
1377 $oTemplate->assign('current_priority', $priority);
1378
1379 $oTemplate->assign('notifications_enabled', $mdn_user_support==1);
1380 $oTemplate->assign('read_receipt', $request_mdn=='1');
1381 $oTemplate->assign('delivery_receipt', $request_dr=='1');
1382
1383 $oTemplate->assign('drafts_enabled', $save_as_draft);
1384 $oTemplate->assign('address_book_button', $addr_book);
1385
1386 $oTemplate->display('compose_buttons.tpl');
1387 }
1388
1389 function checkInput ($show) {
1390 /*
1391 * I implemented the $show variable because the error messages
1392 * were getting sent before the page header. So, I check once
1393 * using $show=false, and then when i'm ready to display the error
1394 * message, show=true
1395 */
1396 global $send_to, $send_to_cc, $send_to_bcc;
1397
1398 $send_to = trim($send_to);
1399 $send_to_cc = trim($send_to_cc);
1400 $send_to_bcc = trim($send_to_bcc);
1401 if (empty($send_to) && empty($send_to_cc) && empty($send_to_bcc)) {
1402 if ($show) {
1403 plain_error_message(_("You have not filled in the \"To:\" field."));
1404 }
1405 return false;
1406 }
1407 return true;
1408 } /* function checkInput() */
1409
1410
1411 /* True if FAILURE */
1412 function saveAttachedFiles($session) {
1413 global $composeMessage, $username, $attachment_dir;
1414
1415 /* get out of here if no file was attached at all */
1416 if (! is_uploaded_file($_FILES['attachfile']['tmp_name']) ) {
1417 return true;
1418 }
1419
1420 $hashed_attachment_dir = getHashedDir($username, $attachment_dir);
1421 $localfilename = sq_get_attach_tempfile();
1422 $fullpath = $hashed_attachment_dir . '/' . $localfilename;
1423
1424 // m_u_f works better with restricted PHP installs (safe_mode, open_basedir),
1425 // if that doesn't work, try a simple rename.
1426 if (!sq_call_function_suppress_errors('move_uploaded_file', array($_FILES['attachfile']['tmp_name'], $fullpath))) {
1427 if (!sq_call_function_suppress_errors('rename', array($_FILES['attachfile']['tmp_name'], $fullpath))) {
1428 return true;
1429 }
1430 }
1431 $type = strtolower($_FILES['attachfile']['type']);
1432 $name = $_FILES['attachfile']['name'];
1433 $composeMessage->initAttachment($type, $name, $localfilename);
1434 }
1435
1436 /* parse values like 8M and 2k into bytes */
1437 function getByteSize($ini_size) {
1438
1439 if(!$ini_size) {
1440 return FALSE;
1441 }
1442
1443 $ini_size = trim($ini_size);
1444
1445 // if there's some kind of letter at the end of the string we need to multiply.
1446 if(!is_numeric(substr($ini_size, -1))) {
1447
1448 switch(strtoupper(substr($ini_size, -1))) {
1449 case 'G':
1450 $bytesize = 1073741824;
1451 break;
1452 case 'M':
1453 $bytesize = 1048576;
1454 break;
1455 case 'K':
1456 $bytesize = 1024;
1457 break;
1458 }
1459
1460 return ($bytesize * (int)substr($ini_size, 0, -1));
1461 }
1462
1463 return $ini_size;
1464 }
1465
1466
1467 /**
1468 * temporary function to make use of the deliver class.
1469 * In the future the responsible backend should be automaticly loaded
1470 * and conf.pl should show a list of available backends.
1471 * The message also should be constructed by the message class.
1472 *
1473 * @param object $composeMessage The message being sent. Please note
1474 * that it is passed by reference and
1475 * will be returned modified, with additional
1476 * headers, such as Message-ID, Date, In-Reply-To,
1477 * References, and so forth.
1478 *
1479 * @return boolean FALSE if delivery failed, or some non-FALSE value
1480 * upon success.
1481 *
1482 */
1483 function deliverMessage(&$composeMessage, $draft=false) {
1484 global $send_to, $send_to_cc, $send_to_bcc, $mailprio, $subject, $body,
1485 $username, $identity, $idents, $data_dir,
1486 $request_mdn, $request_dr, $default_charset, $useSendmail,
1487 $domain, $action, $default_move_to_sent, $move_to_sent,
1488 $imapServerAddress, $imapPort, $sent_folder, $key;
1489
1490 $rfc822_header = $composeMessage->rfc822_header;
1491
1492 $abook = addressbook_init(false, true);
1493 $rfc822_header->to = $rfc822_header->parseAddress($send_to,true, array(), '', $domain, array(&$abook,'lookup'));
1494 $rfc822_header->cc = $rfc822_header->parseAddress($send_to_cc,true,array(), '',$domain, array(&$abook,'lookup'));
1495 $rfc822_header->bcc = $rfc822_header->parseAddress($send_to_bcc,true, array(), '',$domain, array(&$abook,'lookup'));
1496 $rfc822_header->priority = $mailprio;
1497 $rfc822_header->subject = $subject;
1498
1499 $special_encoding='';
1500 if (strtolower($default_charset) == 'iso-2022-jp') {
1501 if (mb_detect_encoding($body) == 'ASCII') {
1502 $special_encoding = '8bit';
1503 } else {
1504 $body = mb_convert_encoding($body, 'JIS');
1505 $special_encoding = '7bit';
1506 }
1507 }
1508 $composeMessage->setBody($body);
1509
1510 $reply_to = '';
1511 $reply_to = $idents[$identity]['reply_to'];
1512
1513 $from_addr = build_from_header($identity);
1514 $rfc822_header->from = $rfc822_header->parseAddress($from_addr,true);
1515 if ($reply_to) {
1516 $rfc822_header->reply_to = $rfc822_header->parseAddress($reply_to,true);
1517 }
1518 /* Receipt: On Read */
1519 if (isset($request_mdn) && $request_mdn) {
1520 $rfc822_header->dnt = $rfc822_header->parseAddress($from_addr,true);
1521 } elseif (isset($rfc822_header->dnt)) {
1522 unset($rfc822_header->dnt);
1523 }
1524
1525 /* Receipt: On Delivery */
1526 if (!empty($request_dr)) {
1527 //FIXME: it would be better to fiddle with headers inside of the message object or possibly when delivering the message to its destination; is this possible?
1528 $rfc822_header->more_headers['Return-Receipt-To'] = $from_addr;
1529 } elseif (isset($rfc822_header->more_headers['Return-Receipt-To'])) {
1530 unset($rfc822_header->more_headers['Return-Receipt-To']);
1531 }
1532
1533 /* multipart messages */
1534 if (count($composeMessage->entities)) {
1535 $message_body = new Message();
1536 $message_body->body_part = $composeMessage->body_part;
1537 $composeMessage->body_part = '';
1538 $mime_header = new MessageHeader;
1539 $mime_header->type0 = 'text';
1540 $mime_header->type1 = 'plain';
1541 if ($special_encoding) {
1542 $mime_header->encoding = $special_encoding;
1543 } else {
1544 $mime_header->encoding = '8bit';
1545 }
1546 if ($default_charset) {
1547 $mime_header->parameters['charset'] = $default_charset;
1548 }
1549 $message_body->mime_header = $mime_header;
1550 array_unshift($composeMessage->entities, $message_body);
1551 $content_type = new ContentType('multipart/mixed');
1552 } else {
1553 $content_type = new ContentType('text/plain');
1554 if ($special_encoding) {
1555 $rfc822_header->encoding = $special_encoding;
1556 } else {
1557 $rfc822_header->encoding = '8bit';
1558 }
1559 if ($default_charset) {
1560 $content_type->properties['charset']=$default_charset;
1561 }
1562 }
1563
1564 $rfc822_header->content_type = $content_type;
1565 $composeMessage->rfc822_header = $rfc822_header;
1566 if ($action == 'reply' || $action == 'reply_all') {
1567 global $passed_id, $passed_ent_id;
1568 $reply_id = $passed_id;
1569 $reply_ent_id = $passed_ent_id;
1570 } else {
1571 $reply_id = '';
1572 $reply_ent_id = '';
1573 }
1574
1575 /* Here you can modify the message structure just before we hand
1576 it over to deliver; plugin authors note that $composeMessage
1577 is sent and modified by reference since 1.5.2 */
1578 do_hook('compose_send', $composeMessage);
1579
1580 if (!$useSendmail && !$draft) {
1581 require_once(SM_PATH . 'class/deliver/Deliver_SMTP.class.php');
1582 $deliver = new Deliver_SMTP();
1583 global $smtpServerAddress, $smtpPort, $pop_before_smtp, $pop_before_smtp_host;
1584
1585 $authPop = (isset($pop_before_smtp) && $pop_before_smtp) ? true : false;
1586 if (empty($pop_before_smtp_host)) $pop_before_smtp_host = $smtpServerAddress;
1587 get_smtp_user($user, $pass);
1588 $stream = $deliver->initStream($composeMessage,$domain,0,
1589 $smtpServerAddress, $smtpPort, $user, $pass, $authPop, $pop_before_smtp_host);
1590 } elseif (!$draft) {
1591 require_once(SM_PATH . 'class/deliver/Deliver_SendMail.class.php');
1592 global $sendmail_path, $sendmail_args;
1593 // Check for outdated configuration
1594 if (!isset($sendmail_args)) {
1595 if ($sendmail_path=='/var/qmail/bin/qmail-inject') {
1596 $sendmail_args = '';
1597 } else {
1598 $sendmail_args = '-i -t';
1599 }
1600 }
1601 $deliver = new Deliver_SendMail(array('sendmail_args'=>$sendmail_args));
1602 $stream = $deliver->initStream($composeMessage,$sendmail_path);
1603 } elseif ($draft) {
1604 global $draft_folder;
1605 $imap_stream = sqimap_login($username, false, $imapServerAddress,
1606 $imapPort, 0);
1607 if (sqimap_mailbox_exists ($imap_stream, $draft_folder)) {
1608 require_once(SM_PATH . 'class/deliver/Deliver_IMAP.class.php');
1609 $imap_deliver = new Deliver_IMAP();
1610 $success = $imap_deliver->mail($composeMessage, $imap_stream, $reply_id, $reply_ent_id, $imap_stream, $draft_folder);
1611 sqimap_logout($imap_stream);
1612 unset ($imap_deliver);
1613 $composeMessage->purgeAttachments();
1614 return $success;
1615 } else {
1616 $msg = '<br />'.sprintf(_("Error: Draft folder %s does not exist."), htmlspecialchars($draft_folder));
1617 plain_error_message($msg);
1618 return false;
1619 }
1620 }
1621 $success = false;
1622 if ($stream) {
1623 $deliver->mail($composeMessage, $stream, $reply_id, $reply_ent_id);
1624 $success = $deliver->finalizeStream($stream);
1625 }
1626 if (!$success) {
1627 // $deliver->dlv_server_msg is not always server's reply
1628 $msg = _("Message not sent.") . "<br />\n" .
1629 $deliver->dlv_msg;
1630 if (!empty($deliver->dlv_server_msg)) {
1631 // add 'server replied' part only when it is not empty.
1632 // Delivery error can be generated by delivery class itself
1633 $msg.='<br />' .
1634 _("Server replied:") . ' ' . $deliver->dlv_ret_nr . ' ' .
1635 nl2br(htmlspecialchars($deliver->dlv_server_msg));
1636 }
1637 plain_error_message($msg);
1638 } else {
1639 unset ($deliver);
1640 $imap_stream = sqimap_login($username, false, $imapServerAddress, $imapPort, 0);
1641
1642
1643 // mark as replied or forwarded if applicable
1644 //
1645 global $what, $iAccount, $startMessage, $passed_id, $mailbox;
1646
1647 if ($action=='reply' || $action=='reply_all' || $action=='forward' || $action=='forward_as_attachment') {
1648 require(SM_PATH . 'functions/mailbox_display.php');
1649 $aMailbox = sqm_api_mailbox_select($imap_stream, $iAccount, $mailbox,array('setindex' => $what, 'offset' => $startMessage),array());
1650 switch($action) {
1651 case 'reply':
1652 case 'reply_all':
1653 // check if we are allowed to set the \\Answered flag
1654 if (in_array('\\answered',$aMailbox['PERMANENTFLAGS'], true)) {
1655 $aUpdatedMsgs = sqimap_toggle_flag($imap_stream, array($passed_id), '\\Answered', true, false);
1656 if (isset($aUpdatedMsgs[$passed_id]['FLAGS'])) {
1657 /**
1658 * Only update the cached headers if the header is
1659 * cached.
1660 */
1661 if (isset($aMailbox['MSG_HEADERS'][$passed_id])) {
1662 $aMailbox['MSG_HEADERS'][$passed_id]['FLAGS'] = $aMsg['FLAGS'];
1663 }
1664 }
1665 }
1666 break;
1667 case 'forward':
1668 case 'forward_as_attachment':
1669 // check if we are allowed to set the $Forwarded flag (RFC 4550 paragraph 2.8)
1670 if (in_array('$forwarded',$aMailbox['PERMANENTFLAGS'], true) ||
1671 in_array('\\*',$aMailbox['PERMANENTFLAGS'])) {
1672
1673 $aUpdatedMsgs = sqimap_toggle_flag($imap_stream, array($passed_id), '$Forwarded', true, false);
1674 if (isset($aUpdatedMsgs[$passed_id]['FLAGS'])) {
1675 if (isset($aMailbox['MSG_HEADERS'][$passed_id])) {
1676 $aMailbox['MSG_HEADERS'][$passed_id]['FLAGS'] = $aMsg['FLAGS'];
1677 }
1678 }
1679 }
1680 break;
1681 }
1682
1683 /**
1684 * Write mailbox with updated seen flag information back to cache.
1685 */
1686 if(isset($aUpdatedMsgs[$passed_id])) {
1687 $mailbox_cache[$iAccount.'_'.$aMailbox['NAME']] = $aMailbox;
1688 sqsession_register($mailbox_cache,'mailbox_cache');
1689 }
1690
1691 }
1692
1693
1694 // move to sent folder
1695 //
1696 $move_to_sent = getPref($data_dir,$username,'move_to_sent');
1697 if (isset($default_move_to_sent) && ($default_move_to_sent != 0)) {
1698 $svr_allow_sent = true;
1699 } else {
1700 $svr_allow_sent = false;
1701 }
1702
1703 if (isset($sent_folder) && (($sent_folder != '') || ($sent_folder != 'none'))
1704 && sqimap_mailbox_exists( $imap_stream, $sent_folder)) {
1705 $fld_sent = true;
1706 } else {
1707 $fld_sent = false;
1708 }
1709
1710 if ((isset($move_to_sent) && ($move_to_sent != 0)) || (!isset($move_to_sent))) {
1711 $lcl_allow_sent = true;
1712 } else {
1713 $lcl_allow_sent = false;
1714 }
1715
1716 if (($fld_sent && $svr_allow_sent && !$lcl_allow_sent) || ($fld_sent && $lcl_allow_sent)) {
1717 if ($action == 'reply' || $action == 'reply_all') {
1718 $save_reply_with_orig=getPref($data_dir,$username,'save_reply_with_orig');
1719 if ($save_reply_with_orig) {
1720 $sent_folder = $mailbox;
1721 }
1722 }
1723 require_once(SM_PATH . 'class/deliver/Deliver_IMAP.class.php');
1724 $imap_deliver = new Deliver_IMAP();
1725 $imap_deliver->mail($composeMessage, $imap_stream, $reply_id, $reply_ent_id, $imap_stream, $sent_folder);
1726 unset ($imap_deliver);
1727 }
1728
1729
1730 // final cleanup
1731 //
1732 $composeMessage->purgeAttachments();
1733 sqimap_logout($imap_stream);
1734
1735 }
1736 return $success;
1737 }