4 +-----------------------------------------------------------------------+
5 | program/steps/mail/func.inc |
7 | This file is part of the RoundCube Webmail client |
8 | Copyright (C) 2005-2009, RoundCube Dev. - Switzerland |
9 | Licensed under the GNU GPL |
12 | Provide webmail functionality and GUI objects |
14 +-----------------------------------------------------------------------+
15 | Author: Thomas Bruederli <roundcube@gmail.com> |
16 +-----------------------------------------------------------------------+
18 $Id: func.inc 2483 2009-05-15 10:22:29Z thomasb $
22 require_once('include/rcube_smtp.inc');
24 $EMAIL_ADDRESS_PATTERN = '([a-z0-9][a-z0-9\-\.\+\_]*@[a-z0-9]([a-z0-9\-][.]?)*[a-z0-9]\\.[a-z]{2,5})';
26 // actions that do not require imap connection
27 $NOIMAP_ACTIONS = array('spell', 'addcontact', 'autocomplete', 'upload', 'display-attachment', 'remove-attachment');
30 // log in to imap server
31 if (!in_array($RCMAIL->action, $NOIMAP_ACTIONS) && !$RCMAIL->imap_connect()) {
32 $RCMAIL->kill_session();
34 if ($OUTPUT->ajax_call)
35 $OUTPUT->redirect(array(), 2000);
37 $OUTPUT->set_env('task', 'login');
38 $OUTPUT->send('login');
42 // set imap properties and session vars
43 if ($mbox = get_input_value('_mbox', RCUBE_INPUT_GPC))
44 $IMAP->set_mailbox(($_SESSION['mbox'] = $mbox));
46 $_SESSION['mbox'] = $IMAP->get_mailbox_name();
48 if (!empty($_GET['_page']))
49 $IMAP->set_page(($_SESSION['page'] = intval($_GET['_page'])));
51 // set default sort col/order to session
52 if (!isset($_SESSION['sort_col']))
53 $_SESSION['sort_col'] = $CONFIG['message_sort_col'];
54 if (!isset($_SESSION['sort_order']))
55 $_SESSION['sort_order'] = $CONFIG['message_sort_order'];
57 // set message set for search result
58 if (!empty($_REQUEST['_search']) && isset($_SESSION['search'][$_REQUEST['_search']]))
60 $IMAP->set_search_set($_SESSION['search'][$_REQUEST['_search']]);
61 $OUTPUT->set_env('search_request', $_REQUEST['_search']);
62 $OUTPUT->set_env('search_text', $_SESSION['last_text_search']);
65 // set main env variables, labels and page title
66 if (empty($RCMAIL->action) || $RCMAIL->action == 'list')
68 $mbox_name = $IMAP->get_mailbox_name();
70 if (empty($RCMAIL->action))
72 // initialize searching result if search_filter is used
73 if ($_SESSION['search_filter'] && $_SESSION['search_filter'] != 'ALL')
75 $search_request = md5($mbox_name.$_SESSION['search_filter']);
77 $IMAP->search($mbox_name, $_SESSION['search_filter'], RCMAIL_CHARSET, $_SESSION['sort_col']);
78 $_SESSION['search'][$search_request] = $IMAP->get_search_set();
79 $OUTPUT->set_env('search_request', $search_request);
82 // make sure the message count is refreshed (for default view)
83 $IMAP->messagecount($mbox_name, 'ALL', true);
86 // set current mailbox in client environment
87 $OUTPUT->set_env('mailbox', $mbox_name);
88 $OUTPUT->set_env('quota', $IMAP->get_capability('quota'));
89 $OUTPUT->set_env('delimiter', $IMAP->get_hierarchy_delimiter());
91 if ($CONFIG['trash_mbox'])
92 $OUTPUT->set_env('trash_mailbox', $CONFIG['trash_mbox']);
93 if ($CONFIG['drafts_mbox'])
94 $OUTPUT->set_env('drafts_mailbox', $CONFIG['drafts_mbox']);
95 if ($CONFIG['junk_mbox'])
96 $OUTPUT->set_env('junk_mailbox', $CONFIG['junk_mbox']);
98 if (!$OUTPUT->ajax_call)
99 $OUTPUT->add_label('checkingmail', 'deletemessage', 'movemessagetotrash', 'movingmessage');
101 $OUTPUT->set_pagetitle(rcmail_localize_foldername($mbox_name));
106 * return the message list as HTML table
108 function rcmail_message_list($attrib)
110 global $IMAP, $CONFIG, $COMM_PATH, $OUTPUT;
112 $skin_path = $CONFIG['skin_path'];
113 $image_tag = '<img src="%s%s" alt="%s" />';
115 // check to see if we have some settings for sorting
116 $sort_col = $_SESSION['sort_col'];
117 $sort_order = $_SESSION['sort_order'];
119 // add some labels to client
120 $OUTPUT->add_label('from', 'to');
122 // get message headers
123 $a_headers = $IMAP->list_headers('', '', $sort_col, $sort_order);
125 // add id to message list table if not specified
126 if (!strlen($attrib['id']))
127 $attrib['id'] = 'rcubemessagelist';
129 // allow the following attributes to be added to the <table> tag
130 $attrib_str = create_attrib_string($attrib, array('style', 'class', 'id', 'cellpadding', 'cellspacing', 'border', 'summary'));
132 $out = '<table' . $attrib_str . ">\n";
134 // define list of cols to be displayed based on parameter or config
135 if (empty($attrib['columns']))
136 $a_show_cols = is_array($CONFIG['list_cols']) ? $CONFIG['list_cols'] : array('subject');
138 $a_show_cols = preg_split('/[\s,;]+/', strip_quotes($attrib['columns']));
140 // store column list in a session-variable
141 $_SESSION['list_columns'] = $a_show_cols;
143 // define sortable columns
144 $a_sort_cols = array('subject', 'date', 'from', 'to', 'size');
146 $mbox = $IMAP->get_mailbox_name();
148 // show 'to' instead of from in sent messages
149 if (($mbox==$CONFIG['sent_mbox'] || $mbox==$CONFIG['drafts_mbox']) && ($f = array_search('from', $a_show_cols))
150 && !array_search('to', $a_show_cols))
151 $a_show_cols[$f] = 'to';
153 // add col definition
154 $out .= '<colgroup>';
155 $out .= '<col class="icon" />';
157 foreach ($a_show_cols as $col)
158 $out .= ($col!='attachment') ? sprintf('<col class="%s" />', $col) : '<col class="icon" />';
160 $out .= "</colgroup>\n";
163 $out .= "<thead><tr>\n<td class=\"icon\"> </td>\n";
166 foreach ($a_show_cols as $col)
172 $col_name = sprintf($image_tag, $skin_path, $attrib['unflaggedicon'], '');
175 $col_name = sprintf($image_tag, $skin_path, $attrib['attachmenticon'], '');
178 $col_name = Q(rcube_label($col));
183 if (in_array($col, $a_sort_cols))
185 // have buttons configured
186 if (!empty($attrib['sortdescbutton']) || !empty($attrib['sortascbutton']))
188 $sort = ' ';
191 if (!empty($attrib['sortascbutton']))
193 $sort .= $OUTPUT->button(array(
195 'prop' => $col.'_ASC',
196 'image' => $attrib['sortascbutton'],
197 'align' => 'absmiddle',
198 'title' => 'sortasc'));
202 if (!empty($attrib['sortdescbutton']))
204 $sort .= $OUTPUT->button(array(
206 'prop' => $col.'_DESC',
207 'image' => $attrib['sortdescbutton'],
208 'align' => 'absmiddle',
209 'title' => 'sortdesc'));
212 // just add a link tag to the header
216 '<a href="./#sort" onclick="return %s.command(\'sort\',\'%s\',this)" title="%s">%s</a>',
219 rcube_label('sortby'),
224 $sort_class = $col==$sort_col ? " sorted$sort_order" : '';
226 // put it all together
227 if ($col!='attachment')
228 $out .= '<td class="'.$col.$sort_class.'" id="rcm'.$col.'">' . "$col_name$sort</td>\n";
230 $out .= '<td class="icon" id="rcm'.$col.'">' . "$col_name$sort</td>\n";
233 $out .= "</tr></thead>\n<tbody>\n";
235 // no messages in this mailbox
236 if (!sizeof($a_headers))
237 $OUTPUT->show_message('nomessagesfound', 'notice');
240 $a_js_message_arr = array();
242 // create row for each message
243 foreach ($a_headers as $i => $header) //while (list($i, $header) = each($a_headers))
245 $message_icon = $attach_icon = $flagged_icon = '';
246 $js_row_arr = array();
247 $zebra_class = $i%2 ? ' even' : ' odd';
249 // set messag attributes to javascript array
250 if ($header->deleted)
251 $js_row_arr['deleted'] = true;
253 $js_row_arr['unread'] = true;
254 if ($header->answered)
255 $js_row_arr['replied'] = true;
256 if ($header->forwarded)
257 $js_row_arr['forwarded'] = true;
258 if ($header->flagged)
259 $js_row_arr['flagged'] = true;
262 if ($attrib['deletedicon'] && $header->deleted)
263 $message_icon = $attrib['deletedicon'];
264 else if ($attrib['repliedicon'] && $header->answered)
266 if ($attrib['forwardedrepliedicon'] && $header->forwarded)
267 $message_icon = $attrib['forwardedrepliedicon'];
269 $message_icon = $attrib['repliedicon'];
271 else if ($attrib['forwardedicon'] && $header->forwarded)
272 $message_icon = $attrib['forwardedicon'];
273 else if ($attrib['unreadicon'] && !$header->seen)
274 $message_icon = $attrib['unreadicon'];
275 else if ($attrib['messageicon'])
276 $message_icon = $attrib['messageicon'];
278 if ($attrib['flaggedicon'] && $header->flagged)
279 $flagged_icon = $attrib['flaggedicon'];
280 else if ($attrib['unflaggedicon'] && !$header->flagged)
281 $flagged_icon = $attrib['unflaggedicon'];
283 // set attachment icon
284 if ($attrib['attachmenticon'] && preg_match("/multipart\/m/i", $header->ctype))
285 $attach_icon = $attrib['attachmenticon'];
287 $out .= sprintf('<tr id="rcmrow%d" class="message%s%s%s%s">'."\n",
289 $header->seen ? '' : ' unread',
290 $header->deleted ? ' deleted' : '',
291 $header->flagged ? ' flagged' : '',
294 $out .= sprintf("<td class=\"icon\">%s</td>\n", $message_icon ? sprintf($image_tag, $skin_path, $message_icon, '') : '');
297 $IMAP->set_charset(!empty($header->charset) ? $header->charset : $CONFIG['default_charset']);
300 foreach ($a_show_cols as $col)
302 if ($col=='from' || $col=='to')
303 $cont = Q(rcmail_address_string($header->$col, 3, false, $attrib['addicon']), 'show');
304 else if ($col=='subject')
306 $action = $mbox==$CONFIG['drafts_mbox'] ? 'compose' : 'show';
307 $uid_param = $mbox==$CONFIG['drafts_mbox'] ? '_draft_uid' : '_uid';
308 $cont = abbreviate_string(trim($IMAP->decode_header($header->$col)), 160);
309 if (empty($cont)) $cont = rcube_label('nosubject');
310 $cont = sprintf('<a href="%s" onclick="return rcube_event.cancel(event)">%s</a>', Q(rcmail_url($action, array($uid_param=>$header->uid, '_mbox'=>$mbox))), Q($cont));
312 else if ($col=='flag')
313 $cont = $flagged_icon ? sprintf($image_tag, $skin_path, $flagged_icon, '') : '';
314 else if ($col=='size')
315 $cont = show_bytes($header->$col);
316 else if ($col=='date')
317 $cont = format_date($header->date);
319 $cont = Q($header->$col);
321 if ($col!='attachment')
322 $out .= '<td class="'.$col.'">' . $cont . "</td>\n";
324 $out .= sprintf("<td class=\"icon\">%s</td>\n", $attach_icon ? sprintf($image_tag, $skin_path, $attach_icon, '') : ' ');
329 if (sizeof($js_row_arr))
330 $a_js_message_arr[$header->uid] = $js_row_arr;
333 // complete message table
334 $out .= "</tbody></table>\n";
336 $message_count = $IMAP->messagecount();
339 $OUTPUT->add_gui_object('mailcontframe', 'mailcontframe');
340 $OUTPUT->add_gui_object('messagelist', $attrib['id']);
341 $OUTPUT->set_env('messagecount', $message_count);
342 $OUTPUT->set_env('current_page', $IMAP->list_page);
343 $OUTPUT->set_env('pagecount', ceil($message_count/$IMAP->page_size));
344 $OUTPUT->set_env('sort_col', $sort_col);
345 $OUTPUT->set_env('sort_order', $sort_order);
347 if ($attrib['messageicon'])
348 $OUTPUT->set_env('messageicon', $skin_path . $attrib['messageicon']);
349 if ($attrib['deletedicon'])
350 $OUTPUT->set_env('deletedicon', $skin_path . $attrib['deletedicon']);
351 if ($attrib['unreadicon'])
352 $OUTPUT->set_env('unreadicon', $skin_path . $attrib['unreadicon']);
353 if ($attrib['repliedicon'])
354 $OUTPUT->set_env('repliedicon', $skin_path . $attrib['repliedicon']);
355 if ($attrib['forwardedicon'])
356 $OUTPUT->set_env('forwardedicon', $skin_path . $attrib['forwardedicon']);
357 if ($attrib['forwardedrepliedicon'])
358 $OUTPUT->set_env('forwardedrepliedicon', $skin_path . $attrib['forwardedrepliedicon']);
359 if ($attrib['attachmenticon'])
360 $OUTPUT->set_env('attachmenticon', $skin_path . $attrib['attachmenticon']);
361 if ($attrib['flaggedicon'])
362 $OUTPUT->set_env('flaggedicon', $skin_path . $attrib['flaggedicon']);
363 if ($attrib['unflaggedicon'])
364 $OUTPUT->set_env('unflaggedicon', $skin_path . $attrib['unflaggedicon']);
366 $OUTPUT->set_env('messages', $a_js_message_arr);
367 $OUTPUT->set_env('coltypes', $a_show_cols);
369 $OUTPUT->include_script('list.js');
376 * return javascript commands to add rows to the message list
378 function rcmail_js_message_list($a_headers, $insert_top=FALSE)
380 global $CONFIG, $IMAP, $OUTPUT;
382 if (empty($_SESSION['list_columns']))
383 $a_show_cols = is_array($CONFIG['list_cols']) ? $CONFIG['list_cols'] : array('subject');
385 $a_show_cols = $_SESSION['list_columns'];
387 $mbox = $IMAP->get_mailbox_name();
389 // show 'to' instead of from in sent messages
390 if (($mbox == $CONFIG['sent_mbox'] || $mbox == $CONFIG['drafts_mbox'])
391 && (($f = array_search('from', $a_show_cols)) !== false) && array_search('to', $a_show_cols) === false)
392 $a_show_cols[$f] = 'to';
394 $OUTPUT->command('set_message_coltypes', $a_show_cols);
396 // loop through message headers
397 foreach ($a_headers as $n => $header)
399 $a_msg_cols = array();
400 $a_msg_flags = array();
405 $IMAP->set_charset(!empty($header->charset) ? $header->charset : $CONFIG['default_charset']);
407 // remove 'attachment' and 'flag' columns, we don't need them here
408 if(($key = array_search('attachment', $a_show_cols)) !== FALSE)
409 unset($a_show_cols[$key]);
410 if(($key = array_search('flag', $a_show_cols)) !== FALSE)
411 unset($a_show_cols[$key]);
413 // format each col; similar as in rcmail_message_list()
414 foreach ($a_show_cols as $col)
416 if ($col=='from' || $col=='to')
417 $cont = Q(rcmail_address_string($header->$col, 3), 'show');
418 else if ($col=='subject')
420 $action = $mbox==$CONFIG['drafts_mbox'] ? 'compose' : 'show';
421 $uid_param = $mbox==$CONFIG['drafts_mbox'] ? '_draft_uid' : '_uid';
422 $cont = abbreviate_string(trim($IMAP->decode_header($header->$col)), 160);
423 if (!$cont) $cont = rcube_label('nosubject');
424 $cont = sprintf('<a href="%s" onclick="return rcube_event.cancel(event)">%s</a>', Q(rcmail_url($action, array($uid_param=>$header->uid, '_mbox'=>$mbox))), Q($cont));
426 else if ($col=='size')
427 $cont = show_bytes($header->$col);
428 else if ($col=='date')
429 $cont = format_date($header->date);
431 $cont = Q($header->$col);
433 $a_msg_cols[$col] = $cont;
436 $a_msg_flags['deleted'] = $header->deleted ? 1 : 0;
437 $a_msg_flags['unread'] = $header->seen ? 0 : 1;
438 $a_msg_flags['replied'] = $header->answered ? 1 : 0;
439 $a_msg_flags['forwarded'] = $header->forwarded ? 1 : 0;
440 $a_msg_flags['flagged'] = $header->flagged ? 1 : 0;
442 $OUTPUT->command('add_message_row',
446 preg_match("/multipart\/m/i", $header->ctype),
453 * return an HTML iframe for loading mail content
455 function rcmail_messagecontent_frame($attrib)
459 if (empty($attrib['id']))
460 $attrib['id'] = 'rcmailcontentwindow';
462 $attrib['name'] = $attrib['id'];
464 $OUTPUT->set_env('contentframe', $attrib['id']);
465 $OUTPUT->set_env('blankpage', $attrib['src'] ? $OUTPUT->abs_url($attrib['src']) : 'program/blank.gif');
467 return html::iframe($attrib);
474 function rcmail_messagecount_display($attrib)
476 global $IMAP, $OUTPUT;
479 $attrib['id'] = 'rcmcountdisplay';
481 $OUTPUT->add_gui_object('countdisplay', $attrib['id']);
483 return html::span($attrib, rcmail_get_messagecount_text());
490 function rcmail_quota_display($attrib)
492 global $OUTPUT, $COMM_PATH;
495 $attrib['id'] = 'rcmquotadisplay';
497 if(isset($attrib['display']))
498 $_SESSION['quota_display'] = $attrib['display'];
500 $OUTPUT->add_gui_object('quotadisplay', $attrib['id']);
502 return html::span($attrib, rcmail_quota_content(NULL, $attrib));
509 function rcmail_quota_content($quota=NULL, $attrib=NULL)
511 global $IMAP, $COMM_PATH, $RCMAIL;
513 $display = isset($_SESSION['quota_display']) ? $_SESSION['quota_display'] : '';
515 if (is_array($quota) && !empty($quota['used']) && !empty($quota['total']))
517 if (!isset($quota['percent']))
518 $quota['percent'] = $quota['used'] / $quota['total'];
520 elseif (!$IMAP->get_capability('QUOTA'))
521 return rcube_label('unknown');
523 $quota = $IMAP->get_quota();
525 if ($quota && !($quota['total']==0 && $RCMAIL->config->get('quota_zero_as_unlimited')))
527 $quota_text = sprintf('%s / %s (%.0f%%)',
528 show_bytes($quota['used'] * 1024),
529 show_bytes($quota['total'] * 1024),
532 // show quota as image (by Brett Patterson)
533 if ($display == 'image' && function_exists('imagegif'))
535 if (!$attrib['width'])
536 $attrib['width'] = isset($_SESSION['quota_width']) ? $_SESSION['quota_width'] : 100;
538 $_SESSION['quota_width'] = $attrib['width'];
540 if (!$attrib['height'])
541 $attrib['height'] = isset($_SESSION['quota_height']) ? $_SESSION['quota_height'] : 14;
543 $_SESSION['quota_height'] = $attrib['height'];
545 $quota_text = sprintf('<img src="./bin/quotaimg.php?u=%s&q=%d&w=%d&h=%d" width="%d" height="%d" alt="%s" title="%s / %s" />',
546 $quota['used'], $quota['total'],
547 $attrib['width'], $attrib['height'],
548 $attrib['width'], $attrib['height'],
550 show_bytes($quota['used'] * 1024),
551 show_bytes($quota['total'] * 1024));
555 $quota_text = rcube_label('unlimited');
564 function rcmail_get_messagecount_text($count=NULL, $page=NULL)
566 global $IMAP, $MESSAGE;
568 if (isset($MESSAGE->index))
570 return rcube_label(array('name' => 'messagenrof',
571 'vars' => array('nr' => $MESSAGE->index+1,
572 'count' => $count!==NULL ? $count : $IMAP->messagecount())));
576 $page = $IMAP->list_page;
578 $start_msg = ($page-1) * $IMAP->page_size + 1;
579 $max = $count!==NULL ? $count : $IMAP->messagecount();
582 $out = rcube_label('mailboxempty');
584 $out = rcube_label(array('name' => 'messagesfromto',
585 'vars' => array('from' => $start_msg,
586 'to' => min($max, $start_msg + $IMAP->page_size - 1),
595 function rcmail_mailbox_name_display($attrib)
600 $attrib['id'] = 'rcmmailboxname';
602 $RCMAIL->output->add_gui_object('mailboxname', $attrib['id']);
604 return html::span($attrib, rcmail_get_mailbox_name_text());
607 function rcmail_get_mailbox_name_text()
610 return rcmail_localize_foldername($RCMAIL->imap->get_mailbox_name());
614 * Sets message is_safe flag according to 'show_images' option value
616 * @param object rcube_message Message
618 function rcmail_check_safe(&$message)
622 $show_images = $RCMAIL->config->get('show_images');
623 if (!$message->is_safe
624 && !empty($show_images)
625 && $message->has_html_part())
627 switch($show_images) {
628 case '1': // known senders only
629 $CONTACTS = new rcube_contacts($RCMAIL->db, $_SESSION['user_id']);
630 if ($CONTACTS->search('email', $message->sender['mailto'], true, false)->count) {
631 $message->set_safe(true);
635 $message->set_safe(true);
642 * Cleans up the given message HTML Body (for displaying)
645 * @param array Display parameters
646 * @param array CID map replaces (inline images)
647 * @return string Clean HTML
649 function rcmail_wash_html($html, $p = array(), $cid_replaces)
651 global $REMOTE_OBJECTS;
653 $p += array('safe' => false, 'inline_html' => true);
655 // special replacements (not properly handled by washtml class)
656 $html_search = array(
657 '/(<\/nobr>)(\s+)(<nobr>)/i', // space(s) between <NOBR>
658 '/(<[\/]*st1:[^>]+>)/i', // Microsoft's Smart Tags <ST1>
659 '/<\/?rte_text>/i', // Rich Text Editor tags (#1485647)
660 '/<title>.*<\/title>/i', // PHP bug #32547 workaround: remove title tag
661 '/<html[^>]*>/im', // malformed html: remove html tags (#1485139)
662 '/<\/html>/i', // malformed html: remove html tags (#1485139)
663 '/^(\0\0\xFE\xFF|\xFF\xFE\0\0|\xFE\xFF|\xFF\xFE|\xEF\xBB\xBF)/', // byte-order mark (only outlook?)
665 $html_replace = array(
666 '\\1'.' '.'\\3',
674 $html = preg_replace($html_search, $html_replace, $html);
676 // charset was converted to UTF-8 in rcube_imap::get_message_part() -> change charset specification in HTML accordingly
677 $charset_pattern = '/(\s+content=[\'"]?\w+\/\w+;\s*charset)=([a-z0-9-_]+)/i';
678 if (preg_match($charset_pattern, $html)) {
679 $html = preg_replace($charset_pattern, '\\1='.RCMAIL_CHARSET, $html);
682 // add head for malformed messages, washtml cannot work without that
683 if (!preg_match('/<head[^>]*>(.*)<\/head>/Uims', $html))
684 $html = '<head></head>'. $html;
685 $html = substr_replace($html, '<meta http-equiv="content-type" content="text/html; charset='.RCMAIL_CHARSET.'" />', intval(stripos($html, '<head>')+6), 0);
688 // turn relative into absolute urls
689 $html = rcmail_resolve_base($html);
691 // clean HTML with washhtml by Frederic Motte
693 'show_washed' => false,
694 'allow_remote' => $p['safe'],
695 'blocked_src' => "./program/blocked.gif",
696 'charset' => RCMAIL_CHARSET,
697 'cid_map' => $cid_replaces,
698 'html_elements' => array('body'),
701 if (!$p['inline_html']) {
702 $wash_opts['html_elements'] = array('html','head','title','body');
705 $wash_opts['html_elements'][] = 'link';
706 $wash_opts['html_attribs'] = array('rel','type');
709 $washer = new washtml($wash_opts);
710 $washer->add_callback('form', 'rcmail_washtml_callback');
712 if ($p['safe']) { // allow CSS styles, will be sanitized by rcmail_washtml_callback()
713 $washer->add_callback('style', 'rcmail_washtml_callback');
716 $html = $washer->wash($html);
717 $REMOTE_OBJECTS = $washer->extlinks;
724 * Convert the given message part to proper HTML
725 * which can be displayed the message view
727 * @param object rcube_message_part Message part
728 * @param array Display parameters array
729 * @return string Formatted HTML string
731 function rcmail_print_body($part, $p = array())
733 $p += array('safe' => false, 'plain' => false, 'inline_html' => true);
735 // convert html to text/plain
736 if ($part->ctype_secondary == 'html' && $p['plain']) {
737 $txt = new html2text($part->body, false, true);
738 $body = $txt->get_text();
739 $part->ctype_secondary = 'plain';
742 else if ($part->ctype_secondary == 'html') {
743 return rcmail_wash_html($part->body, $p, $part->replaces);
746 else if ($part->ctype_secondary=='enriched') {
747 $part->ctype_secondary = 'html';
748 require_once('lib/enriched.inc');
749 return Q(enriched_to_html($part->body), 'show');
755 /**** assert plaintext ****/
757 // make links and email-addresses clickable
758 $replacements = new rcube_string_replacer;
760 $url_chars = 'a-z0-9_\-\+\*\$\/&%=@#:;';
761 $url_chars_within = '\?\.~,!';
763 // search for patterns like links and e-mail addresses
764 $body = preg_replace_callback("/([\w]+):\/\/([a-z0-9\-\.]+[a-z]{2,4}([$url_chars$url_chars_within]*[$url_chars])?)/i", array($replacements, 'link_callback'), $body);
765 $body = preg_replace_callback("/([^\/:]|\s)(www\.)([a-z0-9\-]{2,}[a-z]{2,4}([$url_chars$url_chars_within]*[$url_chars])?)/i", array($replacements, 'link_callback'), $body);
766 $body = preg_replace_callback('/([a-z0-9][a-z0-9\-\.\+\_]*@[a-z0-9]([a-z0-9\-][.]?)*[a-z0-9]\\.[a-z]{2,5})/i', array($replacements, 'mailto_callback'), $body);
768 // split body into single lines
769 $a_lines = preg_split('/\r?\n/', $body);
772 // colorize quoted parts
773 for ($n=0; $n < sizeof($a_lines); $n++) {
774 $line = $a_lines[$n];
778 if (preg_match('/^(>+\s*)+/', $line, $regs)) {
779 $q = strlen(preg_replace('/\s/', '', $regs[0]));
780 $line = substr($line, strlen($regs[0]));
782 if ($q > $quote_level)
783 $quotation = str_repeat('<blockquote>', $q - $quote_level);
784 else if ($q < $quote_level)
785 $quotation = str_repeat("</blockquote>", $quote_level - $q);
787 else if ($quote_level > 0)
788 $quotation = str_repeat("</blockquote>", $quote_level);
791 $a_lines[$n] = $quotation . Q($line, 'replace', false); // htmlquote plaintext
794 // insert the links for urls and mailtos
795 $body = $replacements->resolve(join("\n", $a_lines));
797 return html::tag('pre', array(), $body);
802 * add a string to the replacement array and return a replacement string
804 function rcmail_str_replacement($str, &$rep)
807 $rep[$count] = stripslashes($str);
808 return "##string_replacement{".($count++)."}##";
813 * Callback function for washtml cleaning class
815 function rcmail_washtml_callback($tagname, $attrib, $content)
819 $out = html::div('form', $content);
823 // decode all escaped entities and reduce to ascii strings
824 $stripped = preg_replace('/[^a-zA-Z\(:]/', '', rcmail_xss_entitiy_decode($content));
826 // now check for evil strings like expression, behavior or url()
827 if (!preg_match('/expression|behavior|url\(|import/', $stripped)) {
828 $out = html::tag('style', array('type' => 'text/css'), $content);
841 * return table with message headers
843 function rcmail_message_headers($attrib, $headers=NULL)
845 global $IMAP, $OUTPUT, $MESSAGE, $PRINT_MODE, $CONFIG;
848 // keep header table attrib
849 if (is_array($attrib) && !$sa_attrib)
850 $sa_attrib = $attrib;
851 else if (!is_array($attrib) && is_array($sa_attrib))
852 $attrib = $sa_attrib;
855 if (!isset($MESSAGE))
858 // get associative array of headers object
860 $headers = is_object($MESSAGE->headers) ? get_object_vars($MESSAGE->headers) : $MESSAGE->headers;
864 // allow the following attributes to be added to the <table> tag
865 $attrib_str = create_attrib_string($attrib, array('style', 'class', 'id', 'cellpadding', 'cellspacing', 'border', 'summary'));
866 $out = '<table' . $attrib_str . ">\n";
868 // show these headers
869 $standard_headers = array('subject', 'from', 'to', 'cc', 'bcc', 'replyto', 'date');
871 foreach ($standard_headers as $hkey)
873 if (!$headers[$hkey])
879 $header_value = format_date($headers[$hkey], $CONFIG['date_long'] ? $CONFIG['date_long'] : 'x');
881 $header_value = format_date($headers[$hkey]);
883 else if ($hkey == 'replyto')
885 if ($headers['replyto'] != $headers['from'])
886 $header_value = Q(rcmail_address_string($headers['replyto'], null, true, $attrib['addicon']), 'show');
890 else if (in_array($hkey, array('from', 'to', 'cc', 'bcc')))
891 $header_value = Q(rcmail_address_string($headers[$hkey], null, true, $attrib['addicon']), 'show');
892 else if ($hkey == 'subject' && empty($headers[$hkey]))
893 $header_value = Q(rcube_label('nosubject'));
895 $header_value = Q(trim($IMAP->decode_header($headers[$hkey])));
898 $out .= '<td class="header-title">'.Q(rcube_label($hkey)).": </td>\n";
899 $out .= '<td class="'.$hkey.'" width="90%">'.$header_value."</td>\n</tr>";
903 // all headers division
904 $out .= "\n".'<tr><td colspan="2" class="more-headers show-headers"
905 onclick="return '.JS_OBJECT_NAME.'.command(\'load-headers\', \'\', this)"></td></tr>';
906 $out .= "\n".'<tr id="all-headers"><td colspan="2" class="all"><div id="headers-source"></div></td></tr>';
908 $OUTPUT->add_gui_object('all_headers_row', 'all-headers');
909 $OUTPUT->add_gui_object('all_headers_box', 'headers-source');
911 $out .= "\n</table>\n\n";
913 return $header_count ? $out : '';
918 * Handler for the 'messagebody' GUI object
920 * @param array Named parameters
921 * @return string HTML content showing the message body
923 function rcmail_message_body($attrib)
925 global $CONFIG, $OUTPUT, $MESSAGE, $IMAP, $REMOTE_OBJECTS;
927 if (!is_array($MESSAGE->parts) && empty($MESSAGE->body))
931 $attrib['id'] = 'rcmailMsgBody';
933 $safe_mode = $MESSAGE->is_safe || intval($_GET['_safe']);
936 $header_attrib = array();
937 foreach ($attrib as $attr => $value)
938 if (preg_match('/^headertable([a-z]+)$/i', $attr, $regs))
939 $header_attrib[$regs[1]] = $value;
941 if (!empty($MESSAGE->parts))
943 foreach ($MESSAGE->parts as $i => $part)
945 if ($part->type == 'headers')
946 $out .= rcmail_message_headers(sizeof($header_attrib) ? $header_attrib : NULL, $part->headers);
947 else if ($part->type == 'content')
949 if (empty($part->ctype_parameters) || empty($part->ctype_parameters['charset']))
950 $part->ctype_parameters['charset'] = $MESSAGE->headers->charset;
952 // fetch part if not available
953 if (!isset($part->body))
954 $part->body = $MESSAGE->get_part_content($part->mime_id);
956 $body = rcmail_print_body($part, array('safe' => $safe_mode, 'plain' => !$CONFIG['prefer_html']));
958 if ($part->ctype_secondary == 'html')
959 $out .= html::div('message-htmlpart', rcmail_html4inline($body, $attrib['id']));
961 $out .= html::div('message-part', $body);
966 $out .= html::div('message-part', html::tag('pre', array(), Q($MESSAGE->body)));
969 $ctype_primary = strtolower($MESSAGE->structure->ctype_primary);
970 $ctype_secondary = strtolower($MESSAGE->structure->ctype_secondary);
972 // list images after mail body
973 if ($CONFIG['inline_images']
974 && $ctype_primary == 'multipart'
975 && !empty($MESSAGE->attachments)
976 && !strstr($message_body, '<html'))
978 foreach ($MESSAGE->attachments as $attach_prop) {
979 if (strpos($attach_prop->mimetype, 'image/') === 0) {
980 $out .= html::tag('hr') . html::p(array('align' => "center"),
982 'src' => $MESSAGE->get_part_url($attach_prop->mime_id),
983 'title' => $attach_prop->filename,
984 'alt' => $attach_prop->filename,
990 // tell client that there are blocked remote objects
991 if ($REMOTE_OBJECTS && !$safe_mode)
992 $OUTPUT->set_env('blockedobjects', true);
994 return html::div($attrib, $out);
999 * Convert all relative URLs according to a <base> in HTML
1001 function rcmail_resolve_base($body)
1003 // check for <base href=...>
1004 if (preg_match('!(<base.*href=["\']?)([hftps]{3,5}://[a-z0-9/.%-]+)!i', $body, $regs)) {
1005 $replacer = new rcube_base_replacer($regs[2]);
1007 // replace all relative paths
1008 $body = preg_replace_callback('/(src|background|href)=(["\']?)([\.\/]+[^"\'\s]+)(\2|\s|>)/Ui', array($replacer, 'callback'), $body);
1009 $body = preg_replace_callback('/(url\s*\()(["\']?)([\.\/]+[^"\'\)\s]+)(\2)\)/Ui', array($replacer, 'callback'), $body);
1016 * modify a HTML message that it can be displayed inside a HTML page
1018 function rcmail_html4inline($body, $container_id)
1020 $last_style_pos = 0;
1021 $body_lc = strtolower($body);
1024 while (($pos = strpos($body_lc, '<style', $last_style_pos)) && ($pos2 = strpos($body_lc, '</style>', $pos)))
1026 $pos = strpos($body_lc, '>', $pos)+1;
1028 // replace all css definitions with #container [def]
1029 $styles = rcmail_mod_css_styles(substr($body, $pos, $pos2-$pos), $container_id);
1031 $body = substr($body, 0, $pos) . $styles . substr($body, $pos2);
1032 $body_lc = strtolower($body);
1033 $last_style_pos = $pos2;
1036 // modify HTML links to open a new window if clicked
1037 $GLOBALS['rcmail_html_container_id'] = $container_id;
1038 $body = preg_replace_callback('/<(a|link)\s+([^>]+)>/Ui', 'rcmail_alter_html_link', $body);
1039 unset($GLOBALS['rcmail_html_container_id']);
1041 // add comments arround html and other tags
1042 $out = preg_replace(array(
1043 '/(<!DOCTYPE[^>]*>)/i',
1044 '/(<\?xml[^>]*>)/i',
1045 '/(<\/?html[^>]*>)/i',
1046 '/(<\/?head[^>]*>)/i',
1047 '/(<title[^>]*>.*<\/title>)/Ui',
1048 '/(<\/?meta[^>]*>)/i'),
1052 $out = preg_replace(
1053 array('/<body([^>]*)>/i', '/<\/body>/i'),
1054 array('<div class="rcmBody"\\1>', '</div>'),
1057 // quote <? of php and xml files that are specified as text/html
1058 $out = preg_replace(array('/<\?/', '/\?>/'), array('<?', '?>'), $out);
1065 * parse link attributes and set correct target
1067 function rcmail_alter_html_link($matches)
1069 global $EMAIL_ADDRESS_PATTERN;
1072 $attrib = parse_attrib_string($matches[2]);
1075 if ($tag == 'link' && preg_match('/^https?:\/\//i', $attrib['href'])) {
1076 $attrib['href'] = "./bin/modcss.php?u=" . urlencode($attrib['href']) . "&c=" . urlencode($GLOBALS['rcmail_html_container_id']);
1079 else if (preg_match("/^mailto:$EMAIL_ADDRESS_PATTERN/i", $attrib['href'], $mailto)) {
1080 $attrib['href'] = $mailto[0];
1081 $attrib['onclick'] = sprintf(
1082 "return %s.command('compose','%s',this)",
1086 else if (!empty($attrib['href']) && $attrib['href'][0] != '#') {
1087 $attrib['target'] = '_blank';
1090 return "<$tag" . html::attrib_string($attrib, array('href','name','target','onclick','id','class','style','title','rel','type','media')) . $end;
1095 * decode address string and re-format it as HTML links
1097 function rcmail_address_string($input, $max=null, $linked=false, $addicon=null)
1099 global $IMAP, $PRINT_MODE, $CONFIG, $OUTPUT, $EMAIL_ADDRESS_PATTERN;
1101 $a_parts = $IMAP->decode_address_list($input);
1103 if (!sizeof($a_parts))
1106 $c = count($a_parts);
1110 foreach ($a_parts as $part) {
1113 $out .= sprintf('%s <%s>', Q($part['name']), $part['mailto']);
1115 else if (preg_match("/$EMAIL_ADDRESS_PATTERN/i", $part['mailto'])) {
1117 $out .= html::a(array(
1118 'href' => 'mailto:'.$part['mailto'],
1119 'onclick' => sprintf("return %s.command('compose','%s',this)", JS_OBJECT_NAME, JQ($part['mailto'])),
1120 'title' => $part['mailto'],
1121 'class' => "rcmContactAddress",
1126 $out .= html::span(array('title' => $part['mailto'], 'class' => "rcmContactAddress"), Q($part['name']));
1130 $out .= ' ' . html::a(array(
1132 'onclick' => sprintf("return %s.command('add-contact','%s',this)", JS_OBJECT_NAME, urlencode($part['string'])),
1133 'title' => rcube_label('addtoaddressbook'),
1136 'src' => $CONFIG['skin_path'] . $addicon,
1137 'alt' => "Add contact",
1143 $out .= Q($part['name']);
1144 if ($part['mailto'])
1145 $out .= (strlen($out) ? ' ' : '') . sprintf('<%s>', Q($part['mailto']));
1149 $out .= ','.($max ? ' ' : ' ');
1151 if ($max && $j==$max && $c>$j) {
1162 * Wrap text to a given number of characters per line
1163 * but respect the mail quotation of replies messages (>)
1165 * @param string Text to wrap
1166 * @param int The line width
1167 * @return string The wrapped text
1169 function rcmail_wrap_quoted($text, $max = 76)
1171 // Rebuild the message body with a maximum of $max chars, while keeping quoted message.
1172 $lines = preg_split('/\r?\n/', trim($text));
1175 foreach ($lines as $line) {
1176 if (strlen($line) > $max) {
1177 if (preg_match('/^([>\s]+)/', $line, $regs)) {
1178 $length = strlen($regs[0]);
1179 $prefix = substr($line, 0, $length);
1181 // Remove '> ' from the line, then wordwrap() the line
1182 $line = rc_wordwrap(substr($line, $length), $max - $length);
1184 // Rebuild the line with '> ' at the beginning of each 'subline'
1186 foreach (explode("\n", $line) as $l) {
1187 $newline .= $prefix . $l . "\n";
1190 // Remove the righest newline char
1191 $line = rtrim($newline);
1194 $line = rc_wordwrap($line, $max);
1199 $out .= $line . "\n";
1206 function rcmail_message_part_controls()
1210 $part = asciiwords(get_input_value('_part', RCUBE_INPUT_GPC));
1211 if (!is_object($MESSAGE) || !is_array($MESSAGE->parts) || !($_GET['_uid'] && $_GET['_part']) || !$MESSAGE->mime_parts[$part])
1214 $part = $MESSAGE->mime_parts[$part];
1215 $table = new html_table(array('cols' => 3));
1217 if (!empty($part->filename)) {
1218 $table->add('title', Q(rcube_label('filename')));
1219 $table->add(null, Q($part->filename));
1220 $table->add(null, '[' . html::a('?'.str_replace('_frame=', '_download=', $_SERVER['QUERY_STRING']), Q(rcube_label('download'))) . ']');
1223 if (!empty($part->size)) {
1224 $table->add('title', Q(rcube_label('filesize')));
1225 $table->add(null, Q(show_bytes($part->size)));
1228 return $table->show($attrib);
1233 function rcmail_message_part_frame($attrib)
1237 $part = $MESSAGE->mime_parts[asciiwords(get_input_value('_part', RCUBE_INPUT_GPC))];
1238 $ctype_primary = strtolower($part->ctype_primary);
1240 $attrib['src'] = './?' . str_replace('_frame=', ($ctype_primary=='text' ? '_show=' : '_preload='), $_SERVER['QUERY_STRING']);
1242 return html::iframe($attrib);
1247 * clear message composing settings
1249 function rcmail_compose_cleanup()
1251 if (!isset($_SESSION['compose']))
1254 // remove attachment files from temp dir
1255 if (is_array($_SESSION['compose']['attachments']))
1256 foreach ($_SESSION['compose']['attachments'] as $attachment)
1257 @unlink($attachment['path']);
1259 unset($_SESSION['compose']);
1264 * Send the given message compose object using the configured method
1266 function rcmail_deliver_message(&$message, $from, $mailto)
1268 global $CONFIG, $RCMAIL;
1270 $msg_body = $message->get();
1271 $headers = $message->headers();
1273 // send thru SMTP server using custom SMTP library
1274 if ($CONFIG['smtp_server'])
1276 // generate list of recipients
1277 $a_recipients = array($mailto);
1279 if (strlen($headers['Cc']))
1280 $a_recipients[] = $headers['Cc'];
1281 if (strlen($headers['Bcc']))
1282 $a_recipients[] = $headers['Bcc'];
1284 // clean Bcc from header for recipients
1285 $send_headers = $headers;
1286 unset($send_headers['Bcc']);
1287 // here too, it because txtHeaders() below use $message->_headers not only $send_headers
1288 unset($message->_headers['Bcc']);
1291 $smtp_response = array();
1292 $sent = smtp_mail($from, $a_recipients, ($foo = $message->txtHeaders($send_headers, true)), $msg_body, $smtp_response);
1296 raise_error(array('code' => 800, 'type' => 'smtp', 'line' => __LINE__, 'file' => __FILE__,
1297 'message' => "SMTP error: ".join("\n", $smtp_response)), TRUE, FALSE);
1300 // send mail using PHP's mail() function
1303 // unset some headers because they will be added by the mail() function
1304 $headers_enc = $message->headers($headers);
1305 $headers_php = $message->_headers;
1306 unset($headers_php['To'], $headers_php['Subject']);
1308 // reset stored headers and overwrite
1309 $message->_headers = array();
1310 $header_str = $message->txtHeaders($headers_php);
1313 if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
1314 if (preg_match_all('/<([^@]+@[^>]+)>/', $headers_enc['To'], $m)) {
1315 $headers_enc['To'] = implode(', ', $m[1]);
1319 if (ini_get('safe_mode'))
1320 $sent = mail($headers_enc['To'], $headers_enc['Subject'], $msg_body, $header_str);
1322 $sent = mail($headers_enc['To'], $headers_enc['Subject'], $msg_body, $header_str, "-f$from");
1327 // remove MDN headers after sending
1328 unset($headers['Return-Receipt-To'], $headers['Disposition-Notification-To']);
1330 if ($CONFIG['smtp_log'])
1331 write_log('sendmail', sprintf("User %s [%s]; Message for %s; %s",
1332 $RCMAIL->user->get_username(),
1333 $_SERVER['REMOTE_ADDR'],
1335 !empty($smtp_response) ? join('; ', $smtp_response) : ''));
1338 $message->_headers = array();
1339 $message->headers($headers);
1345 function rcmail_send_mdn($uid)
1347 global $RCMAIL, $IMAP;
1349 $message = new rcube_message($uid);
1351 if ($message->headers->mdn_to && !$message->headers->mdn_sent &&
1352 ($IMAP->check_permflag('MDNSENT') || $IMAP->check_permflag('*')))
1354 $identity = $RCMAIL->user->get_identity();
1355 $sender = format_email_recipient($identity['email'], $identity['name']);
1356 $recipient = array_shift($IMAP->decode_address_list($message->headers->mdn_to));
1357 $mailto = $recipient['mailto'];
1359 $compose = new rcube_mail_mime($RCMAIL->config->header_delimiter());
1360 $compose->setParam(array(
1361 'text_encoding' => 'quoted-printable',
1362 'html_encoding' => 'quoted-printable',
1363 'head_encoding' => 'quoted-printable',
1364 'head_charset' => RCMAIL_CHARSET,
1365 'html_charset' => RCMAIL_CHARSET,
1366 'text_charset' => RCMAIL_CHARSET,
1369 // compose headers array
1371 'Date' => date('r'),
1373 'To' => $message->headers->mdn_to,
1374 'Subject' => rcube_label('receiptread') . ': ' . $message->subject,
1375 'Message-ID' => sprintf('<%s@%s>', md5(uniqid('rcmail'.rand(),true)), $RCMAIL->config->mail_domain($_SESSION['imap_host'])),
1376 'X-Sender' => $identity['email'],
1377 'Content-Type' => 'multipart/report; report-type=disposition-notification',
1380 if ($agent = $RCMAIL->config->get('useragent'))
1381 $headers['User-Agent'] = $agent;
1383 $body = rcube_label("yourmessage") . "\r\n\r\n" .
1384 "\t" . rcube_label("to") . ': ' . rcube_imap::decode_mime_string($message->headers->to, $message->headers->charset) . "\r\n" .
1385 "\t" . rcube_label("subject") . ': ' . $message->subject . "\r\n" .
1386 "\t" . rcube_label("sent") . ': ' . format_date($message->headers->date, $RCMAIL->config->get('date_long')) . "\r\n" .
1387 "\r\n" . rcube_label("receiptnote") . "\r\n";
1389 $ua = $RCMAIL->config->get('useragent', "RoundCube Webmail (Version ".RCMAIL_VERSION.")");
1390 $report = "Reporting-UA: $ua\r\n";
1392 if ($message->headers->to)
1393 $report .= "Original-Recipient: {$message->headers->to}\r\n";
1395 $report .= "Final-Recipient: rfc822; {$identity['email']}\r\n" .
1396 "Original-Message-ID: {$message->headers->messageID}\r\n" .
1397 "Disposition: manual-action/MDN-sent-manually; displayed\r\n";
1399 $compose->headers($headers);
1400 $compose->setTXTBody(rc_wordwrap($body, 75, "\r\n"));
1401 $compose->addAttachment($report, 'message/disposition-notification', 'MDNPart2.txt', false, '7bit', 'inline');
1403 $sent = rcmail_deliver_message($compose, $identity['email'], $mailto);
1407 $IMAP->set_flag($message->uid, 'MDNSENT');
1416 function rcmail_search_filter($attrib)
1420 if (!strlen($attrib['id']))
1421 $attrib['id'] = 'rcmlistfilter';
1423 $attrib['onchange'] = JS_OBJECT_NAME.'.filter_mailbox(this.value)';
1426 RFC3501 (6.4.4): 'ALL', 'RECENT',
1427 'ANSWERED', 'DELETED', 'FLAGGED', 'SEEN',
1428 'UNANSWERED', 'UNDELETED', 'UNFLAGGED', 'UNSEEN',
1429 'NEW', // = (RECENT UNSEEN)
1430 'OLD' // = NOT RECENT
1433 $select_filter = new html_select($attrib);
1434 $select_filter->add(rcube_label('all'), 'ALL');
1435 $select_filter->add(rcube_label('unread'), 'UNSEEN');
1436 $select_filter->add(rcube_label('flagged'), 'FLAGGED');
1437 $select_filter->add(rcube_label('unanswered'), 'UNANSWERED');
1439 $out = $select_filter->show($_SESSION['search_filter']);
1441 $OUTPUT->add_gui_object('search_filter', $attrib['id']);
1446 // register UI objects
1447 $OUTPUT->add_handlers(array(
1448 'mailboxlist' => 'rcmail_mailbox_list',
1449 'messages' => 'rcmail_message_list',
1450 'messagecountdisplay' => 'rcmail_messagecount_display',
1451 'quotadisplay' => 'rcmail_quota_display',
1452 'mailboxname' => 'rcmail_mailbox_name_display',
1453 'messageheaders' => 'rcmail_message_headers',
1454 'messagebody' => 'rcmail_message_body',
1455 'messagecontentframe' => 'rcmail_messagecontent_frame',
1456 'messagepartframe' => 'rcmail_message_part_frame',
1457 'messagepartcontrols' => 'rcmail_message_part_controls',
1458 'searchfilter' => 'rcmail_search_filter',
1459 'searchform' => array($OUTPUT, 'search_form'),