From 2ce94499c003c05fe3bab5beb3fcb26f93bbc7e5 Mon Sep 17 00:00:00 2001 From: Ralf Becker Date: Mon, 19 Oct 2015 19:48:52 +0000 Subject: [PATCH] * Calendar/Mail: send meeting requests including html body like current Exchange server does it --- calendar/inc/class.calendar_uiforms.inc.php | 17 +++- phpgwapi/inc/class.egw_mailer.inc.php | 105 +++++++++++++++----- 2 files changed, 95 insertions(+), 27 deletions(-) diff --git a/calendar/inc/class.calendar_uiforms.inc.php b/calendar/inc/class.calendar_uiforms.inc.php index df036a60d4..870f9b20a0 100644 --- a/calendar/inc/class.calendar_uiforms.inc.php +++ b/calendar/inc/class.calendar_uiforms.inc.php @@ -1142,7 +1142,20 @@ class calendar_uiforms extends calendar_ui //error_log(__METHOD__.__LINE__.array2string($to)); } } - list($subject,$body) = $this->bo->get_update_message($event,$added ? MSG_ADDED : MSG_MODIFIED); // update-message is in TZ of the user + // prefer event description over standard notification text + if (empty($event['description'])) + { + list(,$body) = $this->bo->get_update_message($event,$added ? MSG_ADDED : MSG_MODIFIED); // update-message is in TZ of the user + } + else + { + $body = $event['description']; + } + // respect user preference about html mail + if ($GLOBALS['egw_info']['user']['preferences']['mail']['composeOptions'] != 'text') + { + $body = '
'.$body.'
'; + } //error_log(__METHOD__.print_r($event,true)); $boical = new calendar_ical(); // we need to pass $event[id] so iCal class reads event again, @@ -1158,7 +1171,7 @@ class calendar_uiforms extends calendar_ui //error_log(__METHOD__.__LINE__.array2string($to)); $vars = array( 'menuaction' => 'mail.mail_compose.compose', - 'mimeType' => 'plain', // force type to plain as thunderbird seems to try to be smart while parsing html messages with ics attachments + 'mimeType' => $GLOBALS['egw_info']['user']['preferences']['mail']['composeOptions'] != 'text' ? 'html' : 'plain', 'preset[to]' => $to, 'preset[subject]' => $event['title'], 'preset[body]' => $body, diff --git a/phpgwapi/inc/class.egw_mailer.inc.php b/phpgwapi/inc/class.egw_mailer.inc.php index 6c51697e90..1a265512ce 100644 --- a/phpgwapi/inc/class.egw_mailer.inc.php +++ b/phpgwapi/inc/class.egw_mailer.inc.php @@ -330,21 +330,21 @@ class egw_mailer extends Horde_Mime_Mail { throw new egw_exception_not_found("File '$data' not found!"); } - $part = new Horde_Mime_Part(); - if ($type || !is_resource($data)) $part->setType($type ? $type : egw_vfs::mime_content_type($data)); - $matches = null; - if (preg_match('/^([^;]+);\s*([^=]+)=([^;]+)$/', $type, $matches)) - { - $part->setContentTypeParameter($matches[2], $matches[3]); - } - $part->setContents($resource); - // store "text/calendar" as _htmlBody, to trigger "multipart/alternative" - if (stripos($type,"text/calendar; method=") !== false) + if (empty($type) && !is_resource($data)) $type = egw_vfs::mime_content_type($data); + + // set "text/calendar; method=*" as alternativ body + $matches = null; + if (preg_match('|^text/calendar; method=([^;]+)|i', $type, $matches)) { - $this->_htmlBody = $part; + $this->setAlternativBody($resource, $type, array('method' => $matches[1]), 'utf-8'); return; } + + $part = new Horde_Mime_Part(); + $part->setType($type); + $part->setContents($resource); + // setting name, also sets content-disposition attachment (!), therefore we have to do it after "text/calendar; method=" handling if ($name || !is_resource($data)) $part->setName($name ? $name : egw_vfs::basename($data)); @@ -375,7 +375,7 @@ class egw_mailer extends Horde_Mime_Mail } $part_id = $this->addAttachment($data, $name, $type); - error_log(__METHOD__."(".array2string($data).", '$cid', '$name', '$type') added with (temp.) part_id=$part_id"); + //error_log(__METHOD__."(".array2string($data).", '$cid', '$name', '$type') added with (temp.) part_id=$part_id"); $part = $this->_parts[$part_id]; $part->setDisposition('inline'); @@ -403,22 +403,19 @@ class egw_mailer extends Horde_Mime_Mail $type = func_get_arg(3); } + // set "text/calendar; method=*" as alternativ body + $matches = null; + if (preg_match('|^text/calendar; method=([^;]+)|i', $type, $matches)) + { + $this->setAlternativBody($content, $type, array('method' => $matches[1]), 'utf-8'); + return; + } + $part = new Horde_Mime_Part(); $part->setType($type); - $matches = null; - if (preg_match('/^([^;]+);\s*([^=]+)=([^;]+)$/', $type, $matches)) - { - $part->setContentTypeParameter($matches[2], $matches[3]); - } $part->setCharset('utf-8'); $part->setContents($content); - // store "text/calendar" as _htmlBody, to trigger "multipart/alternative" - if (stripos($type,"text/calendar; method=") !== false) - { - $this->_htmlBody = $part; - return; - } // this should not be necessary, because binary data get detected by mime-type, // but at least Cyrus complains about NUL characters $part->setTransferEncoding('base64', array('send' => true)); @@ -428,6 +425,28 @@ class egw_mailer extends Horde_Mime_Mail return $this->addMimePart($part); } + /** + * Sets alternativ body, eg. text/calendar has highest / last alternativ + * + * Until pull request to Horde_Mime_Mail gets approved. + * + * @param string|resource $content + * @param string $type eg. "text/calendar" or "text/calendar; method=REQUEST" + * @param array $parameters =array() eg. array('method' => 'REQUEST') + * @param string $charset =null default to $this->_charset="utf-8" + */ + function setAlternativBody($content, $type, $parameters=array(), $charset=null) + { + $this->_alternativBody = new Horde_Mime_Part(); + $this->_alternativBody->setType($type); + foreach($parameters as $label => $data) + { + $this->_alternativBody->setContentTypeParameter($label, $data); + } + $this->_alternativBody->setCharset($charset ? $charset : $this->_charset); + $this->_alternativBody->setContents($content); + } + /** * Send mail, injecting mail transport from account * @@ -481,8 +500,44 @@ class egw_mailer extends Horde_Mime_Mail } try { - parent::send($this->account->smtpTransport(), true, // true: keep Message-ID - $this->_body && $this->_body->getType() != 'multipart/encrypted'); // no flowed for encrypted messages + // no flowed for encrypted messages + $flowed = $this->_body && $this->_body->getType() != 'multipart/encrypted'; + + // vvv until pull request to Horde_Mime_Mail gets approved vvvvvvvvv + if (!empty($this->_alternativBody) && empty($this->_htmlBody)) + { + $this->_htmlBody = $this->_alternativBody; + unset($this->_alternativBody); + } + if (!empty($this->_alternativBody)) + { + parent::send(new Horde_Mail_Transport_Null, true, $flowed); // true: keep Message-ID + + $this->_base[] = $this->_alternativBody; + + /* Build recipients. */ + $recipients = clone $this->_recipients; + foreach (array('to', 'cc') as $header) { + if (($h = $this->_headers[$header])) { + $recipients->add($h->getAddressList()); + } + } + if ($this->_bcc) { + $recipients->add($this->_bcc); + } + + /* Trick Horde_Mime_Part into re-generating the message headers. */ + $this->_headers->removeHeader('MIME-Version'); + + /* Send message. */ + $recipients->unique(); + $this->_base->send($recipients->writeAddress(), $this->_headers, $this->account->smtpTransport()); + } + else + // ^^^ until pull request to Horde_Mime_Mail gets approved ^^^^^^^^^ + { + parent::send($this->account->smtpTransport(), true, $flowed); // true: keep Message-ID + } } catch (Exception $e) { // in case of errors/exceptions call hook again with previous returned mail_id and error-message to log