| +-----------------------------------------------------------------------+ $Id$ */ // define constants for message compose mode define('RCUBE_COMPOSE_REPLY', 0x0106); define('RCUBE_COMPOSE_FORWARD', 0x0107); define('RCUBE_COMPOSE_DRAFT', 0x0108); define('RCUBE_COMPOSE_EDIT', 0x0109); $MESSAGE_FORM = NULL; $MESSAGE = NULL; // Nothing below is called during message composition, only at "new/forward/reply/draft" initialization or // if a compose-ID is given (i.e. when the compose step is opened in a new window/tab). // Since there are many ways to leave the compose page improperly, it seems necessary to clean-up an old // compose when a "new/forward/reply/draft" is called - otherwise the old session attachments will appear if (!is_array($_SESSION['compose']) || $_SESSION['compose']['id'] != get_input_value('_id', RCUBE_INPUT_GET)) { rcmail_compose_cleanup(); $_SESSION['compose'] = array( 'id' => uniqid(mt_rand()), 'param' => request2param(RCUBE_INPUT_GET), 'mailbox' => $IMAP->get_mailbox_name(), ); // process values like "mailto:foo@bar.com?subject=new+message&cc=another" if ($_SESSION['compose']['param']['to']) { // #1486037: remove "mailto:" prefix $_SESSION['compose']['param']['to'] = preg_replace('/^mailto:/i', '', $_SESSION['compose']['param']['to']); $mailto = explode('?', $_SESSION['compose']['param']['to']); if (count($mailto) > 1) { $_SESSION['compose']['param']['to'] = $mailto[0]; parse_str($mailto[1], $query); foreach ($query as $f => $val) $_SESSION['compose']['param'][$f] = $val; } } // select folder where to save the sent message $_SESSION['compose']['param']['sent_mbox'] = $RCMAIL->config->get('sent_mbox'); // pipe compose parameters thru plugins $plugin = $RCMAIL->plugins->exec_hook('message_compose', $_SESSION['compose']); $_SESSION['compose']['param'] = array_merge($_SESSION['compose']['param'], $plugin['param']); // add attachments listed by message_compose hook if (is_array($plugin['attachments'])) { foreach ($plugin['attachments'] as $attach) { // we have structured data if (is_array($attach)) { $attachment = $attach; } // only a file path is given else { $filename = basename($attach); $attachment = array( 'name' => $filename, 'mimetype' => rc_mime_content_type($attach, $filename), 'path' => $attach ); } // save attachment if valid if (($attachment['data'] && $attachment['name']) || ($attachment['path'] && file_exists($attachment['path']))) { $attachment = rcmail::get_instance()->plugins->exec_hook('save_attachment', $attachment); } if ($attachment['status'] && !$attachment['abort']) { unset($attachment['data'], $attachment['status'], $attachment['abort']); $_SESSION['compose']['attachments'][$attachment['id']] = $attachment; } } } // redirect to a unique URL with all parameters stored in session $OUTPUT->redirect(array('_action' => 'compose', '_id' => $_SESSION['compose']['id'])); } // add some labels to client $OUTPUT->add_label('nosubject', 'nosenderwarning', 'norecipientwarning', 'nosubjectwarning', 'cancel', 'nobodywarning', 'notsentwarning', 'notuploadedwarning', 'savingmessage', 'sendingmessage', 'messagesaved', 'converting', 'editorwarning', 'searching', 'uploading', 'fileuploaderror'); // add config parameters to client script if (!empty($CONFIG['drafts_mbox'])) { $OUTPUT->set_env('drafts_mailbox', $CONFIG['drafts_mbox']); $OUTPUT->set_env('draft_autosave', $CONFIG['draft_autosave']); } // set current mailbox in client environment $OUTPUT->set_env('mailbox', $IMAP->get_mailbox_name()); $OUTPUT->set_env('sig_above', $CONFIG['sig_above']); $OUTPUT->set_env('top_posting', $CONFIG['top_posting']); // get reference message and set compose mode if ($msg_uid = $_SESSION['compose']['param']['reply_uid']) $compose_mode = RCUBE_COMPOSE_REPLY; else if ($msg_uid = $_SESSION['compose']['param']['forward_uid']) $compose_mode = RCUBE_COMPOSE_FORWARD; else if ($msg_uid = $_SESSION['compose']['param']['uid']) $compose_mode = RCUBE_COMPOSE_EDIT; else if ($msg_uid = $_SESSION['compose']['param']['draft_uid']) { $RCMAIL->imap->set_mailbox($CONFIG['drafts_mbox']); $compose_mode = RCUBE_COMPOSE_DRAFT; } $config_show_sig = $RCMAIL->config->get('show_sig', 1); if ($config_show_sig == 1) $OUTPUT->set_env('show_sig', true); else if ($config_show_sig == 2 && (empty($compose_mode) || $compose_mode == RCUBE_COMPOSE_EDIT || $compose_mode == RCUBE_COMPOSE_DRAFT)) $OUTPUT->set_env('show_sig', true); else if ($config_show_sig == 3 && ($compose_mode == RCUBE_COMPOSE_REPLY || $compose_mode == RCUBE_COMPOSE_FORWARD)) $OUTPUT->set_env('show_sig', true); else $OUTPUT->set_env('show_sig', false); // set line length for body wrapping $LINE_LENGTH = $RCMAIL->config->get('line_length', 72); if (!empty($msg_uid)) { // similar as in program/steps/mail/show.inc // re-set 'prefer_html' to have possibility to use html part for compose $CONFIG['prefer_html'] = $CONFIG['prefer_html'] || $CONFIG['htmleditor'] || $compose_mode == RCUBE_COMPOSE_DRAFT || $compose_mode == RCUBE_COMPOSE_EDIT; $MESSAGE = new rcube_message($msg_uid); // make sure message is marked as read if ($MESSAGE && $MESSAGE->headers && !$MESSAGE->headers->seen) $IMAP->set_flag($msg_uid, 'SEEN'); if (!empty($MESSAGE->headers->charset)) $IMAP->set_charset($MESSAGE->headers->charset); if ($compose_mode == RCUBE_COMPOSE_REPLY) { $_SESSION['compose']['reply_uid'] = $msg_uid; $_SESSION['compose']['reply_msgid'] = $MESSAGE->headers->messageID; $_SESSION['compose']['references'] = trim($MESSAGE->headers->references . " " . $MESSAGE->headers->messageID); if (!empty($_SESSION['compose']['param']['all'])) $MESSAGE->reply_all = 1; $OUTPUT->set_env('compose_mode', 'reply'); } else if ($compose_mode == RCUBE_COMPOSE_DRAFT) { if ($MESSAGE->headers->others['x-draft-info']) { // get reply_uid/forward_uid to flag the original message when sending $info = rcmail_draftinfo_decode($MESSAGE->headers->others['x-draft-info']); if ($info['type'] == 'reply') $_SESSION['compose']['reply_uid'] = $info['uid']; else if ($info['type'] == 'forward') $_SESSION['compose']['forward_uid'] = $info['uid']; $_SESSION['compose']['mailbox'] = $info['folder']; } if ($MESSAGE->headers->in_reply_to) $_SESSION['compose']['reply_msgid'] = '<'.$MESSAGE->headers->in_reply_to.'>'; $_SESSION['compose']['references'] = $MESSAGE->headers->references; } else if ($compose_mode == RCUBE_COMPOSE_FORWARD) { $_SESSION['compose']['forward_uid'] = $msg_uid; $OUTPUT->set_env('compose_mode', 'forward'); } } /****** compose mode functions ********/ function rcmail_compose_headers($attrib) { global $IMAP, $MESSAGE, $DB, $compose_mode; static $sa_recipients = array(); list($form_start, $form_end) = get_form_tags($attrib); $out = ''; $part = strtolower($attrib['part']); switch ($part) { case 'from': return $form_start . rcmail_compose_header_from($attrib); case 'to': $fname = '_to'; $header = $param = 'to'; // we have a set of recipients stored is session if (($mailto_id = $_SESSION['compose']['param']['mailto']) && $_SESSION['mailto'][$mailto_id]) $fvalue = urldecode($_SESSION['mailto'][$mailto_id]); case 'cc': if (!$fname) { $fname = '_cc'; $header = $param = 'cc'; } case 'bcc': if (!$fname) { $fname = '_bcc'; $header = $param = 'bcc'; } $allow_attrib = array('id', 'class', 'style', 'cols', 'rows', 'tabindex'); $field_type = 'html_textarea'; break; case 'replyto': case 'reply-to': $fname = '_replyto'; $param = 'replyto'; $allow_attrib = array('id', 'class', 'style', 'size', 'tabindex'); $field_type = 'html_inputfield'; break; } if ($fname && !empty($_POST[$fname])) { $fvalue = get_input_value($fname, RCUBE_INPUT_POST, TRUE); } else if ($fname && !$fvalue && !empty($_SESSION['compose']['param'][$param])) { $fvalue = $_SESSION['compose']['param'][$param]; } else if ($header && $compose_mode == RCUBE_COMPOSE_REPLY) { // get recipent address(es) out of the message headers if ($header=='to' && !empty($MESSAGE->headers->replyto)) $fvalue = $MESSAGE->headers->replyto; else if ($header=='to' && !empty($MESSAGE->headers->from)) $fvalue = $MESSAGE->headers->from; // add recipent of original message if reply to all else if ($header=='cc' && !empty($MESSAGE->reply_all)) { if ($v = $MESSAGE->headers->to) $fvalue .= $v; if ($v = $MESSAGE->headers->cc) $fvalue .= (!empty($fvalue) ? ', ' : '') . $v; } // split recipients and put them back together in a unique way if (!empty($fvalue)) { $to_addresses = $IMAP->decode_address_list($fvalue); $fvalue = ''; foreach ($to_addresses as $addr_part) { if (!empty($addr_part['mailto']) && !in_array($addr_part['mailto'], $sa_recipients) && (!$MESSAGE->compose_from || !in_array_nocase($addr_part['mailto'], $MESSAGE->compose_from) || (count($to_addresses)==1 && $header=='to'))) // allow reply to yourself { $fvalue .= (strlen($fvalue) ? ', ':'').$addr_part['string']; $sa_recipients[] = $addr_part['mailto']; } } } } else if ($header && in_array($compose_mode, array(RCUBE_COMPOSE_DRAFT, RCUBE_COMPOSE_EDIT))) { // get drafted headers if ($header=='to' && !empty($MESSAGE->headers->to)) $fvalue = $MESSAGE->get_header('to'); if ($header=='cc' && !empty($MESSAGE->headers->cc)) $fvalue = $MESSAGE->get_header('cc'); if ($header=='bcc' && !empty($MESSAGE->headers->bcc)) $fvalue = $MESSAGE->get_header('bcc'); } if ($fname && $field_type) { // pass the following attributes to the form class $field_attrib = array('name' => $fname, 'spellcheck' => 'false'); foreach ($attrib as $attr => $value) if (in_array($attr, $allow_attrib)) $field_attrib[$attr] = $value; // create teaxtarea object $input = new $field_type($field_attrib); $out = $input->show($fvalue); } if ($form_start) $out = $form_start.$out; return $out; } function rcmail_compose_header_from($attrib) { global $IMAP, $MESSAGE, $DB, $USER, $OUTPUT, $compose_mode; // pass the following attributes to the form class $field_attrib = array('name' => '_from'); foreach ($attrib as $attr => $value) if (in_array($attr, array('id', 'class', 'style', 'size', 'tabindex'))) $field_attrib[$attr] = $value; // extract all recipients of the reply-message $a_recipients = array(); if ($compose_mode == RCUBE_COMPOSE_REPLY && is_object($MESSAGE->headers)) { $MESSAGE->compose_from = array(); $a_to = $IMAP->decode_address_list($MESSAGE->headers->to); foreach ($a_to as $addr) { if (!empty($addr['mailto'])) $a_recipients[] = mb_strtolower($addr['mailto']); } if (!empty($MESSAGE->headers->cc)) { $a_cc = $IMAP->decode_address_list($MESSAGE->headers->cc); foreach ($a_cc as $addr) { if (!empty($addr['mailto'])) $a_recipients[] = mb_strtolower($addr['mailto']); } } } // get this user's identities $user_identities = $USER->list_identities(); if (count($user_identities)) { $from_id = 0; $a_signatures = array(); $field_attrib['onchange'] = JS_OBJECT_NAME.".change_identity(this)"; $select_from = new html_select($field_attrib); foreach ($user_identities as $sql_arr) { $identity_id = $sql_arr['identity_id']; $select_from->add(format_email_recipient($sql_arr['email'], $sql_arr['name']), $identity_id); // add signature to array if (!empty($sql_arr['signature']) && empty($_SESSION['compose']['param']['nosig'])) { $a_signatures[$identity_id]['text'] = $sql_arr['signature']; $a_signatures[$identity_id]['is_html'] = ($sql_arr['html_signature'] == 1) ? true : false; if ($a_signatures[$identity_id]['is_html']) { $h2t = new html2text($a_signatures[$identity_id]['text'], false, false); $a_signatures[$identity_id]['plain_text'] = trim($h2t->get_text()); } } if ($compose_mode == RCUBE_COMPOSE_REPLY && is_array($MESSAGE->compose_from)) $MESSAGE->compose_from[] = $sql_arr['email']; if (empty($_POST['_from']) && empty($_SESSION['compose']['param']['from'])) { // set draft's identity if ($compose_mode == RCUBE_COMPOSE_DRAFT && strstr($MESSAGE->headers->from, $sql_arr['email'])) $from_id = $sql_arr['identity_id']; // set identity if it's one of the reply-message recipients (with prio for default identity) else if (in_array(mb_strtolower($sql_arr['email']), $a_recipients) && (empty($from_id) || $sql_arr['standard'])) $from_id = $sql_arr['identity_id']; } } // overwrite identity selection with post parameter if (!empty($_POST['_from'])) $from_id = get_input_value('_from', RCUBE_INPUT_POST); else if (!empty($_SESSION['compose']['param']['from'])) $from_id = $_SESSION['compose']['param']['from']; $out = $select_from->show($from_id); // add signatures to client $OUTPUT->set_env('signatures', $a_signatures); } else { $input_from = new html_inputfield($field_attrib); $out = $input_from->show($_POST['_from']); } return $out; } function rcmail_compose_body($attrib) { global $RCMAIL, $CONFIG, $OUTPUT, $MESSAGE, $compose_mode, $LINE_LENGTH; list($form_start, $form_end) = get_form_tags($attrib); unset($attrib['form']); if (empty($attrib['id'])) $attrib['id'] = 'rcmComposeBody'; $attrib['name'] = '_message'; if ($CONFIG['htmleditor'] || (($compose_mode == RCUBE_COMPOSE_DRAFT || $compose_mode == RCUBE_COMPOSE_EDIT) && $MESSAGE->has_html_part())) $isHtml = true; else $isHtml = false; $body = ''; // use posted message body if (!empty($_POST['_message'])) { $body = get_input_value('_message', RCUBE_INPUT_POST, true); } else if ($_SESSION['compose']['param']['body']) { $body = $_SESSION['compose']['param']['body']; $isHtml = false; } else if ($compose_mode) { $has_html_part = $MESSAGE->has_html_part(); if (($isHtml || $compose_mode == RCUBE_COMPOSE_DRAFT) && $has_html_part) { $body = $MESSAGE->first_html_part(); $isHtml = true; } else if ($has_html_part) { // use html part if it has been used for message (pre)viewing // decrease line length for quoting $len = $compose_mode == RCUBE_COMPOSE_REPLY ? $LINE_LENGTH-2 : $LINE_LENGTH; $txt = new html2text($MESSAGE->first_html_part(), false, true, $len); $body = $txt->get_text(); $isHtml = false; } else { $body = $MESSAGE->first_text_part(); $isHtml = false; } // compose reply-body if ($compose_mode == RCUBE_COMPOSE_REPLY) $body = rcmail_create_reply_body($body, $isHtml); // forward message body inline else if ($compose_mode == RCUBE_COMPOSE_FORWARD) $body = rcmail_create_forward_body($body, $isHtml); // load draft message body else if ($compose_mode == RCUBE_COMPOSE_DRAFT || $compose_mode == RCUBE_COMPOSE_EDIT) $body = rcmail_create_draft_body($body, $isHtml); } $plugin = $RCMAIL->plugins->exec_hook('message_compose_body', array('body' => $body, 'html' => $isHtml, 'mode' => $compose_mode)); $body = $plugin['body']; unset($plugin); // add blocked.gif attachment (#1486516) if ($isHtml && preg_match('#comm_path.'&_action=display-attachment&_file=rcmfile'.$attachment['id'], $body); } } $out = $form_start ? "$form_start\n" : ''; $saveid = new html_hiddenfield(array('name' => '_draft_saveid', 'value' => $compose_mode==RCUBE_COMPOSE_DRAFT ? str_replace(array('<','>'), "", $MESSAGE->headers->messageID) : '')); $out .= $saveid->show(); $drafttoggle = new html_hiddenfield(array('name' => '_draft', 'value' => 'yes')); $out .= $drafttoggle->show(); $msgtype = new html_hiddenfield(array('name' => '_is_html', 'value' => ($isHtml?"1":"0"))); $out .= $msgtype->show(); // If desired, set this textarea to be editable by TinyMCE if ($isHtml) $attrib['class'] = 'mce_editor'; $textarea = new html_textarea($attrib); $out .= $textarea->show($body); $out .= $form_end ? "\n$form_end" : ''; $OUTPUT->set_env('composebody', $attrib['id']); // include HTML editor rcube_html_editor(); // include GoogieSpell if (!empty($CONFIG['enable_spellcheck'])) { $engine = $RCMAIL->config->get('spellcheck_engine','googie'); $spellcheck_langs = (array) $RCMAIL->config->get('spellcheck_languages', array('da'=>'Dansk', 'de'=>'Deutsch', 'en' => 'English', 'es'=>'Español', 'fr'=>'Français', 'it'=>'Italiano', 'nl'=>'Nederlands', 'pl'=>'Polski', 'pt'=>'Português', 'fi'=>'Suomi', 'sv'=>'Svenska')); // googie works only with two-letter codes if ($engine == 'googie') { $lang = strtolower(substr($_SESSION['language'], 0, 2)); $spellcheck_langs_googie = array(); foreach ($spellcheck_langs as $key => $name) $spellcheck_langs_googie[strtolower(substr($key,0,2))] = $name; $spellcheck_langs = $spellcheck_langs_googie; } else { $lang = $_SESSION['language']; // if not found in the list, try with two-letter code if (!$spellcheck_langs[$lang]) $lang = strtolower(substr($lang, 0, 2)); } if (!$spellcheck_langs[$lang]) $lang = 'en'; $editor_lang_set = array(); foreach ($spellcheck_langs as $key => $name) { $editor_lang_set[] = ($key == $lang ? '+' : '') . JQ($name).'='.JQ($key); } $OUTPUT->include_script('googiespell.js'); $OUTPUT->add_script(sprintf( "var googie = new GoogieSpell('\$__skin_path/images/googiespell/','?_task=utils&_action=spell&lang=');\n". "googie.lang_chck_spell = \"%s\";\n". "googie.lang_rsm_edt = \"%s\";\n". "googie.lang_close = \"%s\";\n". "googie.lang_revert = \"%s\";\n". "googie.lang_no_error_found = \"%s\";\n". "googie.setLanguages(%s);\n". "googie.setCurrentLanguage('%s');\n". "googie.setSpellContainer('spellcheck-control');\n". "googie.decorateTextarea('%s');\n". "%s.set_env('spellcheck', googie);", JQ(Q(rcube_label('checkspelling'))), JQ(Q(rcube_label('resumeediting'))), JQ(Q(rcube_label('close'))), JQ(Q(rcube_label('revertto'))), JQ(Q(rcube_label('nospellerrors'))), json_serialize($spellcheck_langs), $lang, $attrib['id'], JS_OBJECT_NAME), 'foot'); $OUTPUT->add_label('checking'); $OUTPUT->set_env('spellcheck_langs', join(',', $editor_lang_set)); } $out .= "\n".''; return $out; } function rcmail_create_reply_body($body, $bodyIsHtml) { global $RCMAIL, $MESSAGE, $LINE_LENGTH; if (!$bodyIsHtml) { // try to remove the signature if ($RCMAIL->config->get('strip_existing_sig', true) && ($sp = strrpos($body, '-- ')) !== false && ($sp == 0 || $body{$sp-1} == "\n")) { if ($body{$sp+3}==' ' || $body{$sp+3}=="\n" || $body{$sp+3}=="\r") $body = substr($body, 0, max(0, $sp-1)); } // soft-wrap and quote message text $body = rcmail_wrap_and_quote(rtrim($body, "\r\n"), $LINE_LENGTH); // add title line(s) $prefix = sprintf("On %s, %s wrote:\n", $MESSAGE->headers->date, $MESSAGE->get_header('from')); $suffix = ''; if ($RCMAIL->config->get('top_posting')) $prefix = "\n\n\n" . $prefix; } else { // save inline images to files $cid_map = rcmail_write_inline_attachments($MESSAGE); // set is_safe flag (we need this for html body washing) rcmail_check_safe($MESSAGE); // clean up html tags $body = rcmail_wash_html($body, array('safe' => $MESSAGE->is_safe), $cid_map); // build reply (quote content) $prefix = sprintf("
On %s, %s wrote:
\n", $MESSAGE->headers->date, htmlspecialchars(Q($MESSAGE->get_header('from'), 'replace'), ENT_COMPAT, $RCMAIL->output->get_charset())); $prefix .= ''; if ($RCMAIL->config->get('top_posting')) { $prefix = ''; } else { $suffix = ''; } } return $prefix.$body.$suffix; } function rcmail_create_forward_body($body, $bodyIsHtml) { global $IMAP, $MESSAGE, $OUTPUT; // add attachments if (!isset($_SESSION['compose']['forward_attachments']) && is_array($MESSAGE->mime_parts)) $cid_map = rcmail_write_compose_attachments($MESSAGE, $bodyIsHtml); if (!$bodyIsHtml) { $prefix = "\n\n\n-------- Original Message --------\n"; $prefix .= 'Subject: ' . $MESSAGE->subject . "\n"; $prefix .= 'Date: ' . $MESSAGE->headers->date . "\n"; $prefix .= 'From: ' . $MESSAGE->get_header('from') . "\n"; $prefix .= 'To: ' . $MESSAGE->get_header('to') . "\n"; if ($MESSAGE->headers->cc) $prefix .= 'Cc: ' . $MESSAGE->get_header('cc') . "\n"; if ($MESSAGE->headers->replyto && $MESSAGE->headers->replyto != $MESSAGE->headers->from) $prefix .= 'Reply-To: ' . $MESSAGE->get_header('replyto') . "\n"; $prefix .= "\n"; } else { // set is_safe flag (we need this for html body washing) rcmail_check_safe($MESSAGE); // clean up html tags $body = rcmail_wash_html($body, array('safe' => $MESSAGE->is_safe), $cid_map); $prefix = sprintf( "
' . $prefix; $suffix = '
-------- Original Message --------
" . "Subject: | %s |
---|---|
Date: | %s |
From: | %s |
To: | %s |
Cc: | %s |
Reply-To: | %s |