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