/** * 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); }
/** * Method used to add a new support email to the system. * * @access public * @param array $row The support email details * @param object $structure The email structure object * @param integer $sup_id The support ID to be passed out * @param boolean $closing If this email comes from closing the issue * @return integer 1 if the insert worked, -1 otherwise */ function insertEmail($row, &$structure, &$sup_id, $closing = false) { // get usr_id from FROM header $usr_id = User::getUserIDByEmail(Mail_API::getEmailAddress($row['from'])); if (!empty($usr_id) && !empty($row["customer_id"])) { $row["customer_id"] = User::getCustomerID($usr_id); } if (empty($row['customer_id'])) { $row['customer_id'] = "NULL"; } // try to get the parent ID $reference_message_id = Mail_API::getReferenceMessageID($row['full_email']); $parent_id = ''; if (!empty($reference_message_id)) { $parent_id = Support::getIDByMessageID($reference_message_id); // make sure it is in the same issue if (!empty($parent_id) && (empty($row['issue_id']) || @$row['issue_id'] != Support::getIssueFromEmail($parent_id))) { $parent_id = ''; } } $stmt = "INSERT INTO\n " . APP_DEFAULT_DB . "." . APP_TABLE_PREFIX . "support_email\n (\n sup_ema_id,"; if (!empty($parent_id)) { $stmt .= "\nsup_parent_id,"; } $stmt .= "\n sup_iss_id,"; if (!empty($usr_id)) { $stmt .= "\nsup_usr_id,\n"; } $stmt .= " sup_customer_id,\n sup_message_id,\n sup_date,\n sup_from,\n sup_to,\n sup_cc,\n sup_subject,\n sup_has_attachment\n ) VALUES (\n " . Misc::escapeInteger($row["ema_id"]) . ",\n"; if (!empty($parent_id)) { $stmt .= "{$parent_id},\n"; } $stmt .= Misc::escapeInteger($row["issue_id"]) . ","; if (!empty($usr_id)) { $stmt .= "\n{$usr_id},\n"; } $stmt .= "\n " . Misc::escapeInteger($row["customer_id"]) . ",\n '" . Misc::escapeString($row["message_id"]) . "',\n '" . Misc::escapeString($row["date"]) . "',\n '" . Misc::escapeString($row["from"]) . "',\n '" . Misc::escapeString(@$row["to"]) . "',\n '" . Misc::escapeString(@$row["cc"]) . "',\n '" . Misc::escapeString($row["subject"]) . "',\n '" . Misc::escapeString($row["has_attachment"]) . "'\n )"; $res = $GLOBALS["db_api"]->dbh->query($stmt); if (PEAR::isError($res)) { Error_Handler::logError(array($res->getMessage(), $res->getDebugInfo()), __FILE__, __LINE__); return -1; } else { $new_sup_id = $GLOBALS["db_api"]->get_last_insert_id(); $sup_id = $new_sup_id; // now add the body and full email to the separate table $stmt = "INSERT INTO\n " . APP_DEFAULT_DB . "." . APP_TABLE_PREFIX . "support_email_body\n (\n seb_sup_id,\n seb_body,\n seb_full_email\n ) VALUES (\n {$new_sup_id},\n '" . Misc::escapeString($row["body"]) . "',\n '" . Misc::escapeString($row["full_email"]) . "'\n )"; $res = $GLOBALS["db_api"]->dbh->query($stmt); if (PEAR::isError($res)) { Error_Handler::logError(array($res->getMessage(), $res->getDebugInfo()), __FILE__, __LINE__); return -1; } else { Workflow::handleNewEmail(Email_Account::getProjectID($row["ema_id"]), @$row["issue_id"], $structure, $row, $closing); return 1; } } }
/** * Routes a note to the correct issue * * @param string $full_message The full note */ function route_notes($full_message) { global $HTTP_POST_VARS; // save the full message for logging purposes Note::saveRoutedNote($full_message); if (preg_match("/^(boundary=).*/m", $full_message)) { $pattern = "/(Content-Type: multipart\\/)(.+); ?\r?\n(boundary=)(.*)\$/im"; $replacement = '$1$2; $3$4'; $full_message = preg_replace($pattern, $replacement, $full_message); } list($headers, ) = Mime_Helper::splitHeaderBody($full_message); // need some validation here if (empty($full_message)) { return array(66, "Error: The email message was empty.\n"); } // // DON'T EDIT ANYTHING BELOW THIS LINE // // remove the reply-to: header if (preg_match("/^(reply-to:).*/im", $full_message)) { $full_message = preg_replace("/^(reply-to:).*\n/im", '', $full_message, 1); } // check if the email routing interface is even supposed to be enabled $setup = Setup::load(); if (@$setup['note_routing']['status'] != 'enabled') { return array(78, "Error: The internal note routing interface is disabled.\n"); } $prefix = $setup['note_routing']['address_prefix']; // escape plus signs so '*****@*****.**' becomes a valid routing address $prefix = str_replace('+', '\\+', $prefix); $mail_domain = quotemeta($setup['note_routing']['address_host']); if (empty($prefix)) { return array(78, "Error: Please configure the email address prefix.\n"); } if (empty($mail_domain)) { return array(78, "Error: Please configure the email address domain.\n"); } $structure = Mime_Helper::decode($full_message, true, true); // find which issue ID this email refers to @preg_match("/{$prefix}(\\d*)@{$mail_domain}/i", $structure->headers['to'], $matches); @($issue_id = $matches[1]); // validation is always a good idea if (empty($issue_id)) { // we need to try the Cc header as well @preg_match("/{$prefix}(\\d*)@{$mail_domain}/i", $structure->headers['cc'], $matches); if (!empty($matches[1])) { $issue_id = $matches[1]; } else { return array(65, "Error: The routed note had no associated Eventum issue ID or had an invalid recipient address.\n"); } } $prj_id = Issue::getProjectID($issue_id); // check if the sender is allowed in this issue' project and if it is an internal user $users = Project::getUserEmailAssocList($prj_id, 'active', User::getRoleID('Customer')); $sender_email = strtolower(Mail_API::getEmailAddress($structure->headers['from'])); $user_emails = array_map('strtolower', array_values($users)); if (!in_array($sender_email, $user_emails)) { return array(77, "Error: The sender of this email is not allowed in the project associated with issue #{$issue_id}.\n"); } Auth::createFakeCookie(User::getUserIDByEmail($sender_email), $prj_id); // parse the Cc: list, if any, and add these internal users to the issue notification list $users = array_flip($users); $addresses = array(); $to_addresses = Mail_API::getEmailAddresses(@$structure->headers['to']); if (count($to_addresses)) { $addresses = $to_addresses; } $cc_addresses = Mail_API::getEmailAddresses(@$structure->headers['cc']); if (count($cc_addresses)) { $addresses = array_merge($addresses, $cc_addresses); } $cc_users = array(); foreach ($addresses as $email) { if (in_array(strtolower($email), $user_emails)) { $cc_users[] = $users[$email]; } } $body = Mime_Helper::getMessageBody($structure); $reference_msg_id = Mail_API::getReferenceMessageID($headers); if (!empty($reference_msg_id)) { $parent_id = Note::getIDByMessageID($reference_msg_id); } else { $parent_id = false; } // insert the new note and send notification about it $HTTP_POST_VARS = array('title' => @$structure->headers['subject'], 'note' => $body, 'note_cc' => $cc_users, 'add_extra_recipients' => 'yes', 'message_id' => @$structure->headers['message-id'], 'parent_id' => $parent_id); // add the full email to the note if there are any attachments // this is needed because the front end code will display attachment links if (Mime_Helper::hasAttachments($full_message)) { $HTTP_POST_VARS['blocked_msg'] = $full_message; } $res = Note::insert(Auth::getUserID(), $issue_id, false, false); // need to handle attachments coming from notes as well if ($res != -1) { Support::extractAttachments($issue_id, $full_message, true, $res); } History::add($issue_id, Auth::getUserID(), History::getTypeID('note_routed'), "Note routed from " . $structure->headers['from']); return true; }