<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 encoding=utf-8: */
// +----------------------------------------------------------------------+
// | Eventum - Issue Tracking System |
// +----------------------------------------------------------------------+
// | Copyright (c) 2003 - 2008 MySQL AB |
// | Copyright (c) 2008 - 2009 Sun Microsystem Inc. |
// | |
// | This program is free software; you can redistribute it and/or modify |
// | it under the terms of the GNU General Public License as published by |
// | the Free Software Foundation; either version 2 of the License, or |
// | (at your option) any later version. |
// | |
// | This program is distributed in the hope that it will be useful, |
// | but WITHOUT ANY WARRANTY; without even the implied warranty of |
// | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
// | GNU General Public License for more details. |
// | |
// | You should have received a copy of the GNU General Public License |
// | along with this program; if not, write to: |
// | |
// | Free Software Foundation, Inc. |
// | 59 Temple Place - Suite 330 |
// | Boston, MA 02111-1307, USA. |
// +----------------------------------------------------------------------+
// | Authors: João Prado Maia <hide@address.com> |
// +----------------------------------------------------------------------+
//
require_once(APP_INC_PATH . "class.error_handler.php");
require_once(APP_INC_PATH . "class.setup.php");
require_once(APP_INC_PATH . "class.mail_queue.php");
require_once(APP_INC_PATH . "class.user.php");
require_once(APP_INC_PATH . "class.mime_helper.php");
require_once(APP_INC_PATH . "class.reminder.php");
require_once(APP_PEAR_PATH . "Mail/RFC822.php");
/**
* Class to handle the business logic related to sending email to
* outside recipients. This class utilizes the PEAR::Mail
* infrastructure to deliver email in a compatible way across
* different platforms.
*
* @version 1.0
* @author João Prado Maia <hide@address.com>
*/
class Mail_API
{
// variable to keep the Mail_mime object
var $mime;
// variable to keep the headers to be used in the email
var $headers = '';
// text version of this message
var $text_body = '';
/**
* Class constructor. It includes and initializes the required
* PEAR::Mail related objects
*
* @access public
*/
function Mail_API()
{
@require_once(APP_PEAR_PATH . 'Mail.php');
@require_once(APP_PEAR_PATH . 'Mail/mime.php');
$this->mime = new Mail_mime("\r\n");
}
/**
* Correctly formats the subject line of outgoing emails/notes
*
* @access public
* @param integer $issue_id The issue ID
* @param string $subject The subject to be formatted
* @return string The formatted subject
*/
function formatSubject($issue_id, $subject)
{
return "[#$issue_id] " . trim(preg_replace("/\[#$issue_id\] {0,1}/", '', $subject));
}
/**
* Believe it or not, this is a method that will remove excess occurrences
* of 'Re:' that commonly are found in email subject lines.
* If the second parameter is true, issue #'s will also be stripped.
*
* @access public
* @param string $subject The subject line
* @param boolean $remove_issue_id If the issue ID should be removed
* @return string The subject line with the extra occurrences removed from it
*/
function removeExcessRe($subject, $remove_issue_id = false)
{
if ($remove_issue_id) {
$subject = trim(preg_replace("/\[#\d+\] {0,1}/", '', $subject));
}
$re_pattern = "/(\[#\d+\] ){0,1}(([Rr][Ee][Ss]?|Ãòâåò|Antwort|SV|[Aa][Ww])(\[[0-9]+\])?[ \t]*: ){2}(.*)/";
if (preg_match($re_pattern, $subject, $matches)) {
$subject = preg_replace($re_pattern, '$1Re: $5', $subject);
return Mail_API::removeExcessRe($subject);
} else {
return $subject;
}
}
/**
* Returns the canned explanation about why an email message was blocked
* and saved into an internal note.
*
* @access public
* @return string The canned explanation
*/
function getCannedBlockedMsgExplanation()
{
$msg = ev_gettext("WARNING: This message was blocked because the sender was not allowed to send emails to the associated issue.") . " ";
$msg .= ev_gettext("Only staff members listed in the assignment or authorized replier fields can send emails.") . "\n";
$msg .= str_repeat('-', 70) . "\n\n";
return $msg;
}
/**
* Checks whether the given headers are from a vacation
* auto-responder message or not.
*
* @access public
* @param array $headers The list of headers
* @return boolean
*/
function isVacationAutoResponder($headers)
{
// loop through the headers and make sure they are all lowercase.
foreach ($headers as $key => $value) {
$headers[strtolower($key)] = $value;
}
if ((@$headers['x-vacationmessage'] == 'Yes') || ((isset($headers['auto-submitted'])) && (!empty($headers['auto-submitted'])))) {
return true;
} else {
return false;
}
}
/**
* Method used to parse a string and return all email addresses contained
* within it.
*
* @access public
* @param string $str The string containing email addresses
* @return array The list of email addresses
*/
function getEmailAddresses($str)
{
$str = Mail_API::fixAddressQuoting($str);
$str = Mime_Helper::encode($str);
$structs = Mail_RFC822::parseAddressList($str);
$addresses = array();
foreach ($structs as $structure) {
if ((!empty($structure->mailbox)) && (!empty($structure->host))) {
$addresses[] = $structure->mailbox . '@' . $structure->host;
}
}
return $addresses;
}
/**
* Method used to build a properly quoted email address, in the form of
* "Sender Name" <hide@address.com>.
*
* @access public
* @param string $address The email address value
* @return array The address information
*/
function fixAddressQuoting($address)
{
// split multiple addresses if needed
$addresses = Mail_API::splitAddresses($address);
$return = array();
foreach ($addresses as $address) {
// check if we have a <
if ((strstr($address, '<')) && (!Mime_Helper::isQuotedPrintable($address))) {
$address = stripslashes(trim($address));
// is the address in the format 'name' <address> ?
if ((strstr($address, "'")) || (strstr($address, "."))) {
$bracket_pos = strpos($address, '<');
if ($bracket_pos != 0) {
$bracket_pos = $bracket_pos - 1;
}
$first_part = substr($address, 0, $bracket_pos);
if (!empty($first_part)) {
$first_part = '"' . str_replace('"', '\"', preg_replace("/(^\")|(\"$)/", '', $first_part)) . '"';
}
$second_part = substr($address, strpos($address, '<'));
$address = $first_part . ' ' . $second_part;
// if the address was already in the format "'name'" <address>, then this code
// will end up adding even more double quotes, so let's remove any excess
$return[] = str_replace('""', '"', $address);
} else {
$return[] = $address;
}
} else {
$return[] = $address;
}
}
return join(',', $return);
}
/**
* Method used to break down the email address information and
* return it for easy manipulation.
*
* @access public
* @param string $address The email address value
* @param boolean $multiple If multiple addresses should be returned
* @return array The address information
*/
function getAddressInfo($address, $multiple = false)
{
$address = Mail_API::fixAddressQuoting($address);
$t = Mail_RFC822::parseAddressList($address, null, null, false);
if (PEAR::isError($t)) {
return $t;
}
if ($multiple) {
$returns = array();
for ($i = 0; $i < count($t); $i++) {
$returns[] = array(
'sender_name' => $t[$i]->personal,
'email' => $t[$i]->mailbox . '@' . $t[$i]->host,
'username' => $t[$i]->mailbox,
'host' => $t[$i]->host
);
}
return $returns;
} else {
return array(
'sender_name' => $t[0]->personal,
'email' => $t[0]->mailbox . '@' . $t[0]->host,
'username' => $t[0]->mailbox,
'host' => $t[0]->host
);
}
}
/**
* Method used to get the email address portion of a given
* recipient information.
*
* @access public
* @param string $address The email address value
* @return string The email address
*/
function getEmailAddress($address)
{
$address = Mime_Helper::encodeAddress($address);
$info = Mail_API::getAddressInfo($address);
if (PEAR::isError($info)) {
return $info;
}
return $info['email'];
}
/**
* Method used to get the name portion of a given recipient information.
*
* @access public
* @param string $address The email address value
* @param boolean $multiple If multiple addresses should be returned
* @return mixed The name or an array of names if multiple is true
*/
function getName($address, $multiple = false)
{
$info = Mail_API::getAddressInfo($address, true);
if (PEAR::isError($info)) {
return $info;
}
$returns = array();
foreach ($info as $row) {
if (!empty($row['sender_name'])) {
if ((substr($row['sender_name'], 0, 1) == '"') && (substr($row['sender_name'], -1) == '"')) {
$row['sender_name'] = substr($row['sender_name'], 1, -1);
}
$returns[] = Mime_Helper::fixEncoding($row['sender_name']);
} else {
$returns[] = $row['email'];
}
}
if ($multiple) {
return $returns;
} else {
return $returns[0];
}
}
/**
* Method used to get the formatted name of the passed address
* information.
*
* @access public
* @param string $name The name of the recipient
* @param string $email The email of the recipient
* @return string
*/
function getFormattedName($name, $email)
{
return $name . " <" . $email . ">";
}
/**
* Method used to get the application specific settings regarding
* which SMTP server to use, such as login and server information.
*
* @access public
* @return array
*/
function getSMTPSettings()
{
$settings = Setup::load();
settype($settings['smtp']['auth'], 'boolean');
return $settings["smtp"];
}
/**
* Method used to set the text version of the body of the MIME
* multipart message that you wish to send.
*
* @access public
* @param string $text The text-based message
* @return void
*/
function setTextBody($text)
{
$this->text_body = $text;
$this->mime->setTXTBody($text);
}
/**
* Method used to set the HTML version of the body of the MIME
* multipart message that you wish to send.
*
* @access public
* @param string $html The HTML-based message
* @return void
*/
function setHTMLBody($html)
{
$this->mime->setHTMLBody($html);
}
/**
* Method used to add an embedded image to a MIME message.
*
* @access public
* @param string $filename The full path to the image
* @return void
*/
function addHTMLImage($filename)
{
$this->mime->addHTMLImage($filename);
}
/**
* Method used to set extra headers that you may wish to use when
* sending the email.
*
* @access public
* @param mixed $header The header(s) to set
* @param mixed $value The value of the header to be set
* @return void
*/
function setHeaders($header, $value = FALSE)
{
if (is_array($header)) {
foreach ($header as $key => $value) {
$this->headers[$key] = Mime_Helper::encode($value);
}
} else {
$this->headers[$header] = Mime_Helper::encode($value);
}
}
/**
* Method used to add an email address in the Cc list.
*
* @access public
* @param string $email The email address to be added
* @return void
*/
function addCc($email)
{
$this->mime->addCc($email);
}
/**
* Method used to add an attachment to the message.
*
* @access public
* @param string $name The attachment name
* @param string $data The attachment data
* @param string $content_type The content type of the attachment
* @return void
*/
function addAttachment($name, $data, $content_type)
{
$this->mime->addAttachment($data, $content_type, $name, false);
}
/**
* Method used to add a message/rfc822 attachment to the message.
*
* @access public
* @param string $message_body The attachment data
* @return void
*/
function addMessageRfc822($message_body)
{
$this->mime->addMessageRfc822($message_body, '8bit');
}
/**
* Removes the warning message contained in a message, so that certain users
* don't receive that extra information as it may not be relevant to them.
*
* @access public
* @param string $str The body of the email
* @return string The body of the email, without the warning message
*/
function stripWarningMessage($str)
{
$str = str_replace(Mail_API::getWarningMessage('allowed'), '', $str);
$str = str_replace(Mail_API::getWarningMessage('blocked'), '', $str);
return $str;
}
/**
* Returns the warning message that needs to be added to the top of routed
* issue emails to alert the recipient that he can (or not) send emails to
* the issue notification list.
*
* @access public
* @param string $type Whether the warning message is of an allowed recipient or not
* @return string The warning message
*/
function getWarningMessage($type)
{
if ($type == 'allowed') {
$str = ev_gettext("ADVISORY: Your reply will be sent to the notification list.");
} else {
$str = ev_gettext("WARNING: If replying, add yourself to Authorized Repliers list first.");
}
return $str;
}
/**
* Method used to add a customized warning message to the body
* of outgoing emails.
*
* @access public
* @param integer $issue_id The issue ID
* @param string $to The recipient of the message
* @param string $body The body of the message
* @param headers $headers The headers of the message
* @return string The body of the message with the warning message, if appropriate
*/
function addWarningMessage($issue_id, $to, $body, $headers)
{
$setup = Setup::load();
if ((@$setup['email_routing']['status'] == 'enabled') &&
($setup['email_routing']['warning']['status'] == 'enabled')) {
// check if the recipient can send emails to the customer
$recipient_email = Mail_API::getEmailAddress($to);
$recipient_usr_id = User::getUserIDByEmail($recipient_email);
// don't add the warning message if the recipient is an unknown email address
if (empty($recipient_usr_id)) {
return $body;
} else {
// don't add anything if the recipient is a known customer contact
$recipient_role_id = User::getRoleByUser($recipient_usr_id, Issue::getProjectID($issue_id));
if ($recipient_role_id == User::getRoleID('Customer')) {
return $body;
} else {
if (!Support::isAllowedToEmail($issue_id, $recipient_email)) {
$warning = Mail_API::getWarningMessage('blocked');
} else {
$warning = Mail_API::getWarningMessage('allowed');
}
if (@$headers['Content-Transfer-Encoding'] == 'base64') {
return base64_encode($warning . "\n\n" . trim(base64_decode($body)));
} else {
return $warning . "\n\n" . $body;
}
}
}
} else {
return $body;
}
}
/**
* Strips out email headers that should not be sent over to the recipient
* of the routed email. The 'Received:' header was sometimes being used to
* validate the sender of the message, and because of that some emails were
* not being delivered correctly.
*
* @access public
* @param array $headers An array of headers for this email
* @return array The headers of the email, without the stripped ones
*/
function stripHeaders($headers)
{
$ignore_headers = array(
'to',
'cc',
'bcc',
'return-path',
'received',
'Disposition-Notification-To',
);
$ignore_pattern = '/^resent.*/';
foreach ($headers as $name => $value) {
$lower_name = strtolower($name);
if ((in_array($lower_name, $ignore_headers)) || (preg_match($ignore_pattern, $lower_name))) {
unset($headers[$name]);
}
}
return $headers;
}
/**
* Method used to send the SMTP based email message.
*
* @access public
* @param string $from The originator of the message
* @param string $to The recipient of the message
* @param string $subject The subject of the message
* @param integer $issue_id The ID of the issue. If false, email will not be associated with issue.
* @param string $type The type of message this is
* @param integer $sender_usr_id The id of the user sending this email.
* @param integer $type_id The ID of the event that triggered this notification (issue_id, sup_id, not_id, etc)
* @return string The full body of the message that was sent
*/
function send($from, $to, $subject, $save_email_copy = 0, $issue_id = false, $type = '', $sender_usr_id = false, $type_id = false)
{
static $support_levels;
// encode the addresses
$from = MIME_Helper::encodeAddress($from);
$to = MIME_Helper::encodeAddress($to);
$subject = MIME_Helper::encode($subject);
$body = $this->mime->get(array('text_charset' => APP_CHARSET, 'head_charset' => APP_CHARSET, 'text_encoding' => APP_EMAIL_ENCODING));
$headers = array(
'From' => $from,
'To' => Mail_API::fixAddressQuoting($to),
'Subject' => $subject
);
$this->setHeaders($headers);
$hdrs = $this->mime->headers($this->headers);
$res = Mail_Queue::add($to, $hdrs, $body, $save_email_copy, $issue_id, $type, $sender_usr_id, $type_id);
if ((PEAR::isError($res)) || ($res == false)) {
return $res;
} else {
// RFC 822 formatted date
$header = 'Date: ' . date('D, j M Y H:i:s O') . "\r\n";
// return the full dump of the email
foreach ($hdrs as $name => $value) {
$header .= "$name: $value\r\n";
}
$header .= "\r\n";
return $header . $body;
}
}
/**
* Returns the full headers for the email properly encoded.
*
* @access public
* @param string $from The sender of the email
* @param string $to The recipient of the email
* @param string $subject The subject of this email
* @return string The full header version of the email
*/
function getFullHeaders($from, $to, $subject)
{
// encode the addresses
$from = MIME_Helper::encodeAddress($from);
$to = MIME_Helper::encodeAddress($to);
$subject = MIME_Helper::encode($subject);
$body = $this->mime->get(array('text_charset' => APP_CHARSET, 'head_charset' => APP_CHARSET, 'text_encoding' => APP_EMAIL_ENCODING));
$this->setHeaders(array(
'From' => $from,
'To' => $to,
'Subject' => $subject
));
$hdrs = $this->mime->headers($this->headers);
// RFC 822 formatted date
$header = 'Date: ' . gmdate('D, j M Y H:i:s O') . "\r\n";
// return the full dump of the email
foreach ($hdrs as $name => $value) {
$header .= "$name: $value\r\n";
}
$header .= "\r\n";
return $header . $body;
}
/**
* Method used to save a copy of the given email to a configurable address.
*
* @access public
* @param array $email The email to save.
*/
function saveEmailInformation($email)
{
static $subjects = array();
$hdrs = $email['headers'];
$body = $email['body'];
$issue_id = $email['maq_iss_id'];
$sender_usr_id = $email['maq_usr_id'];
// do we really want to save every outgoing email?
$setup = Setup::load();
if ((@$setup['smtp']['save_outgoing_email'] != 'yes') || (empty($setup['smtp']['save_address']))) {
return false;
}
// ok, now parse the headers text and build the assoc array
$full_email = $hdrs . "\n\n" . $body;
$structure = Mime_Helper::decode($full_email, FALSE, FALSE);
$_headers =& $structure->headers;
$header_names = Mime_Helper::getHeaderNames($hdrs);
$headers = array();
foreach ($_headers as $lowercase_name => $value) {
// need to remove the quotes to avoid a parsing problem
// on senders that have extended characters in the first
// or last words in their sender name
if ($lowercase_name == 'from') {
$value = Mime_Helper::removeQuotes($value);
}
$value = Mime_Helper::encode($value);
// add the quotes back
if ($lowercase_name == 'from') {
$value = Mime_Helper::quoteSender($value);
}
$headers[$header_names[$lowercase_name]] = $value;
}
// remove any Reply-To:/Return-Path: values from outgoing messages
unset($headers['Reply-To']);
unset($headers['Return-Path']);
// prevent duplicate emails from being sent out...
$subject = @$headers['Subject'];
if (@in_array($subject, $subjects)) {
return false;
}
// replace the To: header with the requested address
$address = $setup['smtp']['save_address'];
$headers['To'] = $address;
// add specialized headers if they are not already added
if (empty($headers['X-Eventum-Type'])) {
$headers += Mail_API::getSpecializedHeaders($issue_id, $email['maq_type'], $headers, $sender_usr_id);
}
$params = Mail_API::getSMTPSettings($address);
$mail =& Mail::factory('smtp', $params);
$res = $mail->send($address, $headers, $body);
if (PEAR::isError($res)) {
Error_Handler::logError(array($res->getMessage(), $res->getDebugInfo()), __FILE__, __LINE__);
}
$subjects[] = $subject;
}
/**
* Since Mail::prepareHeaders() is not supposed to be called statically, this method
* instantiates an instance of the mail class and calls prepareHeaders on it.
*
* @param array $headers The array of headers to prepare, in an associative
* array, where the array key is the header name (ie,
* 'Subject'), and the array value is the header
* value (ie, 'test'). The header produced from those
* values would be 'Subject: test'.
* @return mixed Returns false if it encounters a bad address,
* otherwise returns an array containing two
* elements: Any From: address found in the headers,
* and the plain text version of the headers.
*/
function prepareHeaders($headers)
{
$params = Mail_API::getSMTPSettings();
$mail =& Mail::factory('smtp', $params);
return $mail->prepareHeaders($headers);
}
/**
* Generates the specialized headers for an email.
*
* @access public
* @param integer $issue_id The issue ID
* @param string $type The type of message this is
* @param string $headers The existing headers of this message.
* @param integer $sender_usr_id The id of the user sending this email.
* @return array An array of specialized headers
*/
function getSpecializedHeaders($issue_id, $type, $headers, $sender_usr_id)
{
$new_headers = array();
if (!empty($issue_id)) {
$prj_id = Issue::getProjectID($issue_id);
if (count(Group::getAssocList($prj_id)) > 0) {
// group issue is currently assigned too
$new_headers['X-Eventum-Group-Issue'] = Group::getName(Issue::getGroupID($issue_id));
// group of whoever is sending this message.
if (empty($sender_usr_id)) {
$new_headers['X-Eventum-Group-Replier'] = $new_headers['X-Eventum-Group-Issue'];
} else {
$new_headers['X-Eventum-Group-Replier'] = Group::getName(User::getGroupID($sender_usr_id));
}
// group of current assignee
$assignees = Issue::getAssignedUserIDs($issue_id);
if (empty($assignees[0])) {
$new_headers['X-Eventum-Group-Assignee'] = '';
} else {
$new_headers['X-Eventum-Group-Assignee'] = @Group::getName(User::getGroupID($assignees[0]));
}
}
if (Customer::hasCustomerIntegration($prj_id)) {
if (empty($support_levels)) {
$support_levels = Customer::getSupportLevelAssocList($prj_id);
}
$customer_id = Issue::getCustomerID($issue_id);
$contract_id = Issue::getContractID($issue_id);
if (!empty($customer_id)) {
$customer_details = Customer::getDetails($prj_id, $customer_id, false, $contract_id);
$new_headers['X-Eventum-Customer'] = $customer_details['customer_name'];
}
if (count($support_levels) > 0) {
$new_headers['X-Eventum-Level'] = $support_levels[Customer::getSupportLevelID($prj_id, $customer_id, $contract_id)];
}
}
// add assignee header
$new_headers['X-Eventum-Assignee'] = join(',', User::getEmail(Issue::getAssignedUserIDs($issue_id)));
$new_headers['X-Eventum-Category'] = Category::getTitle(Issue::getCategory($issue_id));
$new_headers['X-Eventum-Project'] = Project::getName($prj_id);
}
$new_headers['X-Eventum-Type'] = $type;
return $new_headers;
}
/**
* Method used to get the appropriate Message-ID header for a
* given issue.
*
* @access public
* @return string The Message-ID header
*/
function generateMessageID()
{
list($usec, $sec) = explode(" ", microtime());
$time = ((float)$usec + (float)$sec);
$first = base_convert($time, 10, 36);
mt_srand(hexdec(substr(md5(microtime()), -8)) & 0x7fffffff);
$rand = mt_rand();
$second = base_convert($rand, 10, 36);
return "<eventum." . $first . "." . $second . "@" . APP_HOSTNAME . ">";
}
/**
* Returns the referenced message-id for a given reply.
*
* @access public
* @param string $text_headers The full headers of the reply
* @return string The message-id of the original email
*/
function getReferenceMessageID($text_headers)
{
$references = array();
if (preg_match('/^In-Reply-To: (.*)/mi', $text_headers, $matches)) {
return trim($matches[1]);
}
if (preg_match('/^References: (.+?)(\r?\n\r?\n|\r?\n\r?\S)/smi', $text_headers, $matches)) {
$references = explode(" ", Mail_API::unfold(trim($matches[1])));
$references = array_map('trim', $references);
// return the first message-id in the list of references
return $references[0];
}
return '';
}
/**
* Returns the message IDs of all emails this message references.
*
* @access public
* @param string $text_headers The full headers of the message
* @return array An array of message-ids
*/
function getAllReferences($text_headers)
{
$references = array();
if (preg_match('/^In-Reply-To: (.*)/mi', $text_headers, $matches)) {
$references[] = trim($matches[1]);
}
if (preg_match('/^References: (.+?)(\r?\n\r?\n|\r?\n\r?\S)/smi', $text_headers, $matches)) {
$references = array_merge($references, explode(" ", Mail_API::unfold(trim($matches[1]))));
$references = array_map('trim', $references);
$references = array_unique($references);
}
foreach ($references as $key => $reference) {
if (empty($reference)) {
unset($references[$key]);
}
}
return $references;
}
/**
* Checks to make sure In-Reply-To and References headers are correct.
*
*/
function rewriteThreadingHeaders($issue_id, $full_email, $headers, $type = 'email')
{
list($text_headers, $body) = Mime_Helper::splitHeaderBody($full_email);
if ($type == 'note') {
$class = 'Note';
} else {
$class = 'Support';
}
$msg_id = Mail_API::getMessageID($text_headers, $body);
// check if the In-Reply-To header exists and if so, does it relate to a message stored in Eventum
// if it does not, set new In-Reply-To header
$reference_msg_id = Mail_API::getReferenceMessageID($text_headers);
$reference_issue_id = false;
if (!empty($reference_msg_id)) {
// check if referenced msg id is associated with this issue
$reference_issue_id = call_user_func(array($class, 'getIssueByMessageID'), $reference_msg_id);
}
if ((empty($reference_msg_id)) || ($reference_issue_id != $issue_id)) {
$reference_msg_id = Issue::getRootMessageID($issue_id);
}
$references = Mail_API::getReferences($issue_id, $reference_msg_id, $type);
// now the fun part, re-writing the email headers
if (empty($headers['message-id'])) {
// add Message-ID since it doesn't exist (curses on Outlook 2003)
$text_headers .= "\r\nMessage-ID: $msg_id";
$headers['message-id'] = $msg_id;
}
if (preg_match('/^In-Reply-To: (.*)/mi', $text_headers) > 0) {
// replace existing header
$text_headers = preg_replace('/^In-Reply-To: (.*)/mi', 'In-Reply-To: ' . $reference_msg_id, $text_headers, 1);
} else {
// add new header after message ID
$text_headers = preg_replace('/^Message-ID: (.*)$/mi', "Message-ID: $1\r\nIn-Reply-To: $reference_msg_id", $text_headers, 1);
}
$headers['in-reply-to'] = $reference_msg_id;
if (preg_match('/^References: (.*)/mi', $text_headers) > 0) {
// replace existing header
$text_headers = preg_replace('/^References: (.*)/mi', 'References: ' . Mail_API::fold(join(' ', $references)), $text_headers, 1);
} else {
// add new header after In-Reply-To
$text_headers = preg_replace('/^In-Reply-To: (.*)$/mi', "In-Reply-To: $1\r\nReferences: " . Mail_API::fold(join(' ', $references)), $text_headers, 1);
}
$headers['references'] = Mail_API::fold(join(' ', $references));
return array($text_headers . "\r\n\r\n" . $body, $headers);
}
/**
* Returns a complete list of references for an email/note, including
* the issue root message ID
*
* @access private
* @param integer $issue_id The ID of the issue
* @param string $msg_id The ID of the message
* @param string $type If this is a note or an email
* @return array An array of message IDs
*/
function getReferences($issue_id, $msg_id, $type)
{
$references = array();
Mail_API::_getReferences($msg_id, $type, $references);
$references[] = Issue::getRootMessageID($issue_id);
$references = array_reverse(array_unique($references));
return $references;
}
/**
* Method to get the list of messages an email/note references
*
* @access private
* @param string $msg_id The ID of the parent message
* @param string $type If this is a note or an email
* @param array $references The array the references will be stored in.
*/
function _getReferences($msg_id, $type, &$references)
{
$references[] = $msg_id;
if ($type == 'note') {
$class = 'Note';
} else {
$class = 'Support';
}
$parent_msg_id = call_user_func(array($class, 'getParentMessageIDbyMessageID'), $msg_id);
if (!empty($parent_msg_id)) {
Mail_API::_getReferences($parent_msg_id, $type, $references);
}
}
function getBaseThreadingHeaders($issue_id)
{
$root_msg_id = Issue::getRootMessageID($issue_id);
return array(
"Message-ID" => Mail_API::generateMessageID(),
"In-Reply-To" => $root_msg_id,
"References" => $root_msg_id
);
}
/**
* Unfolds message headers
*
* @access public
* @param string $input The headers to unfold
* @return string The unfolded headers
*/
function unfold($input)
{
$input = preg_replace("/\r?\n/", "\r\n", $input);
$input = preg_replace("/\r\n(\t| )+/", ' ', $input);
return $input;
}
/**
* Folds message headers
*
* @access public
* @param string $input The headers to fold
* @return string The folded headers
*/
function fold($input)
{
return wordwrap($input, 70, "\r\n ");
}
/**
* Returns the Message-ID from an email. If no message ID is found (Outlook 2003 doesn't
* generate them in some cases) a "fake" message-id will be calculated.
*
* @access public
* @param string $headers The message headers
* @param string $body The message body
*/
function getMessageID($headers, $body)
{
// try to parse out actual message-id header
if (preg_match('/^Message-ID: (.*)/mi', $headers, $matches)) {
return trim($matches[1]);
} else {
// no match, calculate hash to make fake message ID
$first = base_convert(md5($headers), 10, 36);
$second = base_convert(md5($body), 10, 36);
return "<eventum.md5." . $first . "." . $second . "@" . APP_HOSTNAME . ">";
}
}
function splitAddresses($addresses)
{
$mail = new Mail_RFC822($addresses);
$mail->parseAddressList();
$return = array();
if (is_array($mail->addresses)) {
foreach ($mail->addresses as $address) {
$return[] = $address['address'];
}
}
return $return;
}
}
// benchmarking the included file (aka setup time)
if (APP_BENCHMARK) {
$GLOBALS['bench']->setMarker('Included Mail_API Class');
}