Email Resent (Bounce) feature (#4985)
parent
16f84cc9c0
commit
1b2d3c0ac2
@ -0,0 +1,250 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
+-----------------------------------------------------------------------+
|
||||
| program/steps/mail/bounce.inc |
|
||||
| |
|
||||
| This file is part of the Roundcube Webmail client |
|
||||
| Copyright (C) 2005-2017, The Roundcube Dev Team |
|
||||
| |
|
||||
| Licensed under the GNU General Public License version 3 or |
|
||||
| any later version with exceptions for skins & plugins. |
|
||||
| See the README file for a full license statement. |
|
||||
| |
|
||||
| PURPOSE: |
|
||||
| Bounce/resend an email message |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||
| Author: Aleksander Machniak <alec@alec.pl> |
|
||||
+-----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
/**
|
||||
* Mail_mime wrapper to handle mail bounce
|
||||
*
|
||||
* @FIXME: For some reason this class definition
|
||||
* cannot be put at the end of the file
|
||||
*/
|
||||
class rcmail_bounce_mail extends Mail_mime
|
||||
{
|
||||
/**
|
||||
* Constructor function
|
||||
*/
|
||||
public function __construct($params = array())
|
||||
{
|
||||
// To make the code simpler always use delay_file_io=true
|
||||
$params['delay_file_io'] = true;
|
||||
$params['eol'] = "\r\n";
|
||||
|
||||
parent::__construct($params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns/Sets message headers
|
||||
*/
|
||||
public function headers($headers = array(), $overwrite = false, $skip_content = false)
|
||||
{
|
||||
// headers() wrapper that returns Resent-Cc, Resent-Bcc instead of Cc,Bcc
|
||||
// it's also called to re-add Resent-Bcc after it has been sent (to store in Sent)
|
||||
|
||||
if (array_key_exists('Bcc', $headers)) {
|
||||
$this->build_params['bounce_headers']['Resent-Bcc'] = $headers['Bcc'];
|
||||
}
|
||||
|
||||
foreach ($this->build_params['bounce_headers'] as $key => $val) {
|
||||
$headers[str_replace('Resent-', '', $key)] = $val;
|
||||
}
|
||||
|
||||
return $headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all message headers as string
|
||||
*/
|
||||
public function txtHeaders($headers = array(), $overwrite = false, $skip_content = false)
|
||||
{
|
||||
// i.e. add Resent-* headers on top of the original message head
|
||||
$this->init_message();
|
||||
|
||||
$result = array();
|
||||
|
||||
foreach ($this->build_params['bounce_headers'] as $name => $value) {
|
||||
$key = str_replace('Resent-', '', $name);
|
||||
|
||||
// txtHeaders() can be used to unset Bcc header
|
||||
if (array_key_exists($key, $headers)) {
|
||||
$value = $headers[$key];
|
||||
$this->build_params['bounce_headers']['Resent-'.$key] = $value;
|
||||
}
|
||||
|
||||
if ($value) {
|
||||
$result[] = "$name: $value";
|
||||
}
|
||||
}
|
||||
|
||||
$result = implode($this->build_params['eol'], $result);
|
||||
|
||||
if (strlen($this->bounce_head)) {
|
||||
$result .= $this->build_params['eol'] . $this->bounce_head;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the message body to a file (if delay_file_io=true)
|
||||
*/
|
||||
public function saveMessageBody($file, $params = null)
|
||||
{
|
||||
$this->init_message();
|
||||
|
||||
// this will be called only once, so let just move the file
|
||||
rename($this->bounce_body, $file);
|
||||
|
||||
$this->bounce_head = null;
|
||||
}
|
||||
|
||||
protected function init_message()
|
||||
{
|
||||
if ($this->bounce_head !== null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$rcmail = rcmail::get_instance();
|
||||
$storage = $rcmail->get_storage();
|
||||
$message = $this->build_params['bounce_message'];
|
||||
$temp_dir = unslashify($rcmail->config->get('temp_dir'));
|
||||
$path = tempnam($temp_dir, 'rcmBounce');
|
||||
|
||||
// We'll write the body to the file and the headers to a variable
|
||||
if ($fp = fopen($path, 'w')) {
|
||||
stream_filter_register('bounce_source', 'rcmail_bounce_stream_filter');
|
||||
stream_filter_append($fp, 'bounce_source');
|
||||
|
||||
// message part
|
||||
if ($message->context) {
|
||||
$message->get_part_body($message->context, false, 0, $fp);
|
||||
}
|
||||
// complete message
|
||||
else {
|
||||
$storage->set_folder($message->folder);
|
||||
$storage->get_raw_body($message->uid, $fp);
|
||||
}
|
||||
|
||||
fclose($fp);
|
||||
|
||||
$this->bounce_head = rcmail_bounce_stream_filter::$headers;
|
||||
$this->bounce_body = $path;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stream filter to remove message headers from the streamed
|
||||
* message source (and store them in a variable)
|
||||
*/
|
||||
class rcmail_bounce_stream_filter extends php_user_filter
|
||||
{
|
||||
public static $headers;
|
||||
|
||||
protected $in_body = false;
|
||||
|
||||
public function onCreate()
|
||||
{
|
||||
self::$headers = '';
|
||||
}
|
||||
|
||||
public function filter($in, $out, &$consumed, $closing)
|
||||
{
|
||||
while ($bucket = stream_bucket_make_writeable($in)) {
|
||||
if (!$this->in_body) {
|
||||
self::$headers .= $bucket->data;
|
||||
if (($pos = strpos(self::$headers, "\r\n\r\n")) === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$bucket->data = substr(self::$headers, $pos + 4);
|
||||
$bucket->datalen = strlen($bucket->data);
|
||||
|
||||
self::$headers = substr(self::$headers, 0, $pos);
|
||||
$this->in_body = true;
|
||||
}
|
||||
|
||||
$consumed += $bucket->datalen;
|
||||
stream_bucket_append($out, $bucket);
|
||||
}
|
||||
|
||||
return PSFS_PASS_ON;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$msg_uid = rcube_utils::get_input_value('_uid', rcube_utils::INPUT_GP);
|
||||
$msg_folder = rcube_utils::get_input_value('_mbox', rcube_utils::INPUT_GP, true);
|
||||
$MESSAGE = new rcube_message($msg_uid, $msg_folder);
|
||||
|
||||
if (!$MESSAGE->headers) {
|
||||
$OUTPUT->show_message('messageopenerror', 'error');
|
||||
$OUTPUT->send('iframe');
|
||||
}
|
||||
|
||||
// Display Bounce form
|
||||
if (empty($_POST)) {
|
||||
if (!empty($MESSAGE->headers->charset)) {
|
||||
$RCMAIL->storage->set_charset($MESSAGE->headers->charset);
|
||||
}
|
||||
|
||||
// Initialize helper class to build the UI
|
||||
$SENDMAIL = new rcmail_sendmail(
|
||||
array('mode' => rcmail_sendmail::MODE_FORWARD),
|
||||
array('message' => $MESSAGE)
|
||||
);
|
||||
|
||||
$OUTPUT->set_env('mailbox', $msg_folder);
|
||||
$OUTPUT->set_env('uid', $msg_uid);
|
||||
|
||||
$OUTPUT->send('bounce');
|
||||
}
|
||||
|
||||
// Initialize helper class to send the message
|
||||
$SENDMAIL = new rcmail_sendmail(array('mode' => rcmail_sendmail::MODE_FORWARD), array(
|
||||
'sendmail' => true,
|
||||
'error_handler' => function() use ($OUTPUT) {
|
||||
call_user_func_array(array($OUTPUT, 'show_message'), func_get_args());
|
||||
$OUTPUT->send('iframe');
|
||||
}
|
||||
));
|
||||
|
||||
// Handle the form input
|
||||
$input_headers = $SENDMAIL->headers_input();
|
||||
|
||||
// Set Resent-* headers, these will be added on top of the bounced message
|
||||
$headers = array_filter(array(
|
||||
// 'Received' => $input_headers['Received'],
|
||||
'Resent-From' => $input_headers['From'],
|
||||
'Resent-To' => $input_headers['To'],
|
||||
'Resent-Cc' => $input_headers['Cc'],
|
||||
'Resent-Bcc' => $input_headers['Bcc'],
|
||||
'Resent-Date' => $input_headers['Date'],
|
||||
'Resent-Message-ID' => $input_headers['Message-ID'],
|
||||
));
|
||||
|
||||
// Create the bounce message
|
||||
$BOUNCE = new rcmail_bounce_mail(array(
|
||||
'bounce_message' => $MESSAGE,
|
||||
'bounce_headers' => $headers,
|
||||
));
|
||||
|
||||
// Send the bounce message
|
||||
$SENDMAIL->deliver_message($BOUNCE);
|
||||
|
||||
// Save in Sent (if requested)
|
||||
$saved = $SENDMAIL->save_message($BOUNCE);
|
||||
|
||||
if (!$saved && strlen($SENDMAIL->options['store_target'])) {
|
||||
$RCMAIL->display_server_error('errorsaving');
|
||||
}
|
||||
|
||||
$OUTPUT->show_message('messagesent', 'confirmation', null, false);
|
||||
|
||||
$OUTPUT->send('iframe');
|
@ -0,0 +1,42 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<title><roundcube:object name="pagetitle" /></title>
|
||||
<roundcube:include file="/includes/links.html" />
|
||||
</head>
|
||||
<body class="iframe">
|
||||
|
||||
<div class="compose-headers-div" id="bounceheaders" role="region" aria-labelledby="aria-label-composeheaders">
|
||||
<h2 id="aria-label-composeheaders" class="voice"><roundcube:label name="arialabelmessageheaders" /></h2>
|
||||
<roundcube:object name="composeFormHead" role="main" />
|
||||
<table id="compose-headers"><tbody>
|
||||
<tr>
|
||||
<td class="title"><label for="_from"><roundcube:label name="from" /></label></td>
|
||||
<td class="editfield formlinks">
|
||||
<roundcube:object name="composeHeaders" part="from" form="form" id="_from" tabindex="1" />
|
||||
<a href="#identities" onclick="return rcmail.command('identities')" tabindex="1"><roundcube:label name="editidents" /></a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="title top"><label for="_to"><roundcube:label name="to" /></label></td>
|
||||
<td class="editfield"><roundcube:object name="composeHeaders" part="to" form="form" id="_to" cols="70" rows="1" tabindex="1" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="title top"><label for="_cc"><roundcube:label name="cc" /></label></td>
|
||||
<td class="editfield"><roundcube:object name="composeHeaders" part="cc" form="form" id="_cc" cols="70" rows="1" tabindex="1" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="title top"><label for="_bcc"><roundcube:label name="bcc" /></label></td>
|
||||
<td class="editfield"><roundcube:object name="composeHeaders" part="bcc" form="form" id="_bcc" cols="70" rows="1" tabindex="1" /></td>
|
||||
</tr>
|
||||
<roundcube:if condition="!config:no_save_sent_messages" />
|
||||
<tr><td colspan="2" class="bounceopts">
|
||||
<label><roundcube:label name="savesentmessagein" /> <roundcube:object name="storetarget" maxlength="30" style="max-width:12em" tabindex="1" /></label>
|
||||
</td></tr>
|
||||
<roundcube:endif />
|
||||
</tbody></table>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,46 @@
|
||||
<roundcube:object name="doctype" value="html5" />
|
||||
<html>
|
||||
<head>
|
||||
<title><roundcube:object name="pagetitle" /></title>
|
||||
<roundcube:include file="/includes/links.html" />
|
||||
</head>
|
||||
<body class="iframe fullheight">
|
||||
|
||||
<h1 class="voice"><roundcube:object name="pagetitle" /></h1>
|
||||
|
||||
<div class="boxcontent" id="bounceheaders" role="region" aria-labelledby="aria-label-composeheaders">
|
||||
<h2 id="aria-label-composeheaders" class="voice"><roundcube:label name="arialabelmessageheaders" /></h2>
|
||||
<roundcube:object name="composeFormHead" role="main" />
|
||||
<table class="headers-table compose-headers"><tbody>
|
||||
<tr>
|
||||
<td class="title"><label for="_from"><roundcube:label name="from" /></label></td>
|
||||
<td class="editfield">
|
||||
<roundcube:object name="composeHeaders" part="from" form="form" id="_from" tabindex="1" />
|
||||
<a href="#identities" onclick="return rcmail.command('identities')" class="iconlink edit" tabindex="1"><roundcube:label name="editidents" /></a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="title top"><label for="_to"><roundcube:label name="to" /></label></td>
|
||||
<td class="editfield"><roundcube:object name="composeHeaders" part="to" form="form" id="_to" cols="70" rows="1" tabindex="1" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="title top"><label for="_cc"><roundcube:label name="cc" /></label></td>
|
||||
<td class="editfield"><roundcube:object name="composeHeaders" part="cc" form="form" id="_cc" cols="70" rows="1" tabindex="1" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="title top"><label for="_bcc"><roundcube:label name="bcc" /></label></td>
|
||||
<td class="editfield"><roundcube:object name="composeHeaders" part="bcc" form="form" id="_bcc" cols="70" rows="1" tabindex="1" /></td>
|
||||
</tr>
|
||||
<roundcube:if condition="!config:no_save_sent_messages" />
|
||||
<tr><td colspan="2" class="bounceopts">
|
||||
<label><roundcube:label name="savesentmessagein" /> <roundcube:object name="storetarget" maxlength="30" style="max-width:12em" tabindex="1" /></label>
|
||||
</td></tr>
|
||||
<roundcube:endif />
|
||||
</tbody></table>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<roundcube:include file="/includes/footer.html" />
|
||||
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in New Issue